- 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.
170 lines
5.9 KiB
PHP
Executable File
170 lines
5.9 KiB
PHP
Executable File
<?php
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Character model map parser
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Turns AI_CHARACTER_MODELS entries such as "Maria=qwen/qwen3.7-flash:free"
|
|
| or "3=deepseek/deepseek-v4-flash-0731" into a lookup array keyed by
|
|
| lowercased character id or name. Invalid pairs are ignored.
|
|
|
|
|
*/
|
|
$parseCharacterModels = static function (string $raw): array {
|
|
$map = [];
|
|
|
|
foreach (explode(',', $raw) as $pair) {
|
|
$pair = trim($pair);
|
|
if ($pair === '' || ! str_contains($pair, '=')) {
|
|
continue;
|
|
}
|
|
|
|
[$key, $model] = array_map('trim', explode('=', $pair, 2));
|
|
if ($key !== '' && $model !== '') {
|
|
$map[strtolower($key)] = $model;
|
|
}
|
|
}
|
|
|
|
return $map;
|
|
};
|
|
|
|
return [
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| OpenRouter.ai Integration
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Credentials and behaviour settings used by the AI agents to take
|
|
| strategic decisions through the OpenRouter.ai chat completions API.
|
|
| The API key must be provided via the OPENROUTER_API_KEY env variable
|
|
| and must never be committed to version control.
|
|
|
|
|
*/
|
|
|
|
'openrouter' => [
|
|
'api_key' => env('OPENROUTER_API_KEY'),
|
|
'base_url' => env('OPENROUTER_BASE_URL', 'https://openrouter.ai/api/v1'),
|
|
'timeout' => (int) env('OPENROUTER_TIMEOUT', 30),
|
|
'app_name' => env('APP_NAME', 'City10'),
|
|
'app_url' => env('APP_URL', 'http://localhost:16980'),
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Allow-listed Free Models
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Only the free models listed below may be selected for AI agents.
|
|
| Administrators cannot register agents with models outside this list,
|
|
| which keeps operating costs at zero and enforces the platform policy.
|
|
|
|
|
*/
|
|
|
|
'allowed_models' => [
|
|
'poolside/laguna-m.1:free',
|
|
'cohere/north-mini-code:free',
|
|
'deepseek/deepseek-v4-flash-0731:free',
|
|
'qwen/qwen3.8-27b:free',
|
|
'nvidia/nemotron-3-ultra-550b-a55b:free',
|
|
'nvidia/nemotron-3-super-120b-a12b:free',
|
|
'google/gemma-4-31b-it:free',
|
|
'google/gemma-4-26b-a4b-it:free',
|
|
'openai/gpt-oss-20b:free',
|
|
'nvidia/nemotron-nano-12b-v2-vl:free',
|
|
'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free',
|
|
'nvidia/nemotron-3-nano-30b-a3b:free',
|
|
'nvidia/nemotron-nano-9b-v2:free',
|
|
'poolside/laguna-xs-2.1:free',
|
|
'inclusionai/ling-3.0-flash-vl:free',
|
|
'qwen/qwen3.7-flash:free',
|
|
// Verified working free chat models (live probe of the OpenRouter
|
|
// catalogue). Safe defaults for per-character assignment.
|
|
'deepseek/deepseek-v4-flash-0731',
|
|
'nvidia/nemotron-3.5-lightning:free',
|
|
'liquid/lfm-2.5-2.6b:free',
|
|
'openrouter/free',
|
|
// Owner-approved exception: paid but near-zero cost
|
|
// (1.4e-7/1k tokens), verified live on 2026-11-01.
|
|
'xiaomi/mimo-v2.6-flash',
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Per-Character Model Pool (AI_MODELS)
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Comma-separated allow-listed models distributed deterministically
|
|
| across characters (each character keeps a stable own model). Falls
|
|
| back to AI_DEFAULT_MODEL when empty.
|
|
|
|
|
*/
|
|
|
|
'models' => array_values(array_filter(array_map(
|
|
'trim',
|
|
explode(',', (string) env('AI_MODELS', ''))
|
|
))),
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Per-Character Model Map (AI_CHARACTER_MODELS)
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Explicit "id or name = model" overrides from the environment, e.g.
|
|
| "Maria=liquid/lfm-2.5-2.6b:free,7=openrouter/free". Takes priority
|
|
| over the pool; entries outside the allow-list are ignored.
|
|
|
|
|
*/
|
|
|
|
'character_models' => $parseCharacterModels((string) env('AI_CHARACTER_MODELS', '')),
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Air Traffic Control Model (AI_ATC_MODEL)
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Default model used by the airport control tower agent for movement
|
|
| clearances (takeoff authorizations). Must stay inside allowed_models.
|
|
|
|
|
*/
|
|
|
|
'atc_model' => env('AI_ATC_MODEL', 'poolside/laguna-xs-2.1:free'),
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Fallback Model (AI_FALLBACK_MODEL)
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Tried whenever the preferred model of a call fails (chain fallback).
|
|
| Owner-approved paid model with near-zero cost; must stay inside
|
|
| allowed_models.
|
|
|
|
|
*/
|
|
|
|
'fallback_model' => env('AI_FALLBACK_MODEL', 'xiaomi/mimo-v2.6-flash'),
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Default Model
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Model assigned to newly created agents when none is specified.
|
|
|
|
|
*/
|
|
|
|
'default_model' => env('AI_DEFAULT_MODEL', 'deepseek/deepseek-v4-flash-0731'),
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Agent Decision Cadence (seconds)
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Minimum interval between two reasoning calls for the same agent,
|
|
| protecting the upstream API from excessive usage.
|
|
|
|
|
*/
|
|
|
|
'think_interval' => (int) env('AI_THINK_INTERVAL', 60),
|
|
|
|
];
|