feat(city): add airports, ATC agent, luminous signs, and AI model fallback chain
- Add two airports (north/south) with full geometry: runways, taxiways,
aprons, terminals, hangars, stands, and helipads; walkable airport
grounds extend beyond city bounds.
- Introduce ATC AI agent ("Torre de Controle City10") using AI_ATC_MODEL
to generate takeoff clearances; includes dispatch endpoint with
30-second cooldown and deterministic fallback on failure.
- Implement AI model fallback chain: preferred model → AI_FALLBACK_MODEL
before failing, applied in ChatController and PersonTalkController.
- Add luminous sign labels (letreiros) for plazas, beach, food, hospital,
school, city hall, and airports with day/night glow.
- Fix city presence cache: compute is_me per viewer instead of baking
into shared per-second cache to prevent cross-user flag leakage.
- Add tests: AirportTest, CityPresenceTest, SignLabelsTest, and
PersonTalkTest fallback model retry scenario.
This commit is contained in:
@@ -639,4 +639,50 @@ class PersonTalkTest extends TestCase
|
||||
$this->assertLessThanOrEqual(68, $character->ai_age);
|
||||
$this->assertContains($character->ai_personality, Character::AI_PERSONALITIES);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the resolved primary model fails, the call retries with
|
||||
* AI_FALLBACK_MODEL (owner-approved xiaomi/mimo-v2.6-flash).
|
||||
*/
|
||||
public function test_person_talk_retries_with_fallback_model(): void
|
||||
{
|
||||
config([
|
||||
'ai.openrouter.api_key' => 'test-key',
|
||||
'ai.models' => ['openrouter/free'],
|
||||
'ai.character_models' => [],
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'openrouter.ai/*' => Http::sequence()
|
||||
->push(['message' => 'Model overloaded'], 503)
|
||||
->push(['choices' => [['message' => ['content' => 'Tudo bem por aqui!']]]], 200),
|
||||
]);
|
||||
|
||||
[$userA, $me] = $this->makeCitizen();
|
||||
[, $them] = $this->makeCitizen(); // silent peer → AI reply path
|
||||
|
||||
$this->actingAs($userA)
|
||||
->postJson('/talk/person', [
|
||||
'kind' => 'character',
|
||||
'peer' => $them->id,
|
||||
'message' => 'Alô?',
|
||||
])
|
||||
->assertCreated();
|
||||
|
||||
$reply = PersonTalk::query()
|
||||
->where('from_character_id', $them->id)
|
||||
->where('is_fallback', false)
|
||||
->orderByDesc('id')
|
||||
->first();
|
||||
$this->assertNotNull($reply);
|
||||
$this->assertSame('Tudo bem por aqui!', $reply->body);
|
||||
|
||||
$models = [];
|
||||
Http::assertSent(function ($request) use (&$models): bool {
|
||||
$models[] = $request['model'];
|
||||
|
||||
return true;
|
||||
});
|
||||
$this->assertSame(['openrouter/free', 'xiaomi/mimo-v2.6-flash'], $models);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user