- 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.
148 lines
5.0 KiB
PHP
148 lines
5.0 KiB
PHP
<?php
|
|
|
|
namespace App\Support;
|
|
|
|
use App\Models\City;
|
|
|
|
/**
|
|
* Airport grounds north and south of the city grid.
|
|
*
|
|
* Geometry derives from the layout edges so both airports always sit on
|
|
* open land beyond the roads. The same descriptor drives the client-side
|
|
* build (runways, taxiways, aprons, terminals, hangars, stands, helipads)
|
|
* and the walkable-visit zone used by the position clamp.
|
|
*/
|
|
class Airports
|
|
{
|
|
/** Half width (X) of the walkable airport strips. */
|
|
public const VISIT_HALF_WIDTH = 110.0;
|
|
|
|
/** Walkable Z range inside the airport strips (both airports). */
|
|
public const VISIT_MIN_Z = -400.0;
|
|
|
|
public const VISIT_MAX_Z = 400.0;
|
|
|
|
/**
|
|
* Layout edge north of the city (same derivation the road grid uses).
|
|
*/
|
|
public static function layoutMaxZ(City $city): float
|
|
{
|
|
return (float) (ceil(((float) $city->bounds_max_z) / 40) * 40 + 40);
|
|
}
|
|
|
|
/**
|
|
* Layout edge south of the city.
|
|
*/
|
|
public static function layoutMinZ(City $city): float
|
|
{
|
|
return (float) (floor(((float) $city->bounds_min_z) / 40) * 40 - 40);
|
|
}
|
|
|
|
/**
|
|
* Full airports descriptor for the world payload.
|
|
*
|
|
* @return array{visit: array<string, float>, sites: list<array<string, mixed>>}
|
|
*/
|
|
public static function geometry(City $city): array
|
|
{
|
|
return [
|
|
'visit' => [
|
|
'half_width' => self::VISIT_HALF_WIDTH,
|
|
'min_z' => self::VISIT_MIN_Z,
|
|
'max_z' => self::VISIT_MAX_Z,
|
|
],
|
|
'sites' => [
|
|
self::northSite(self::layoutMaxZ($city)),
|
|
self::southSite(self::layoutMinZ($city)),
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Large airport south of the city (+Z): east-west runway.
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
private static function northSite(float $lz): array
|
|
{
|
|
// lz = 160 for the shipped bounds: runway at 350, apron at 285.
|
|
$standZ = $lz + 135;
|
|
|
|
return [
|
|
'id' => 'sul',
|
|
'name' => 'Aeroporto City10 Sul',
|
|
'code' => 'CTS',
|
|
'runway' => [
|
|
'center' => [0.0, $lz + 190],
|
|
'length' => 180.0,
|
|
'width' => 18.0,
|
|
'axis' => 'x',
|
|
'designators' => ['09', '27'],
|
|
],
|
|
'taxiways' => [
|
|
['center' => [0.0, $lz + 160], 'length' => 170.0, 'width' => 10.0, 'axis' => 'x'],
|
|
['center' => [-80.0, $lz + 175], 'length' => 30.0, 'width' => 8.0, 'axis' => 'z'],
|
|
['center' => [80.0, $lz + 175], 'length' => 30.0, 'width' => 8.0, 'axis' => 'z'],
|
|
],
|
|
'apron' => ['center' => [0.0, $lz + 125], 'size' => [170.0, 60.0]],
|
|
'terminal' => ['center' => [-45.0, $lz + 105], 'size' => [80.0, 18.0]],
|
|
'hangars' => [
|
|
['center' => [35.0, $lz + 106], 'size' => [26.0, 20.0]],
|
|
['center' => [65.0, $lz + 106], 'size' => [26.0, 20.0]],
|
|
],
|
|
'tower' => ['center' => [10.0, $lz + 118]],
|
|
'stands' => [
|
|
[-70.0, $standZ],
|
|
[-35.0, $standZ],
|
|
[0.0, $standZ],
|
|
[35.0, $standZ],
|
|
],
|
|
'helipad' => ['center' => [-100.0, $lz + 165], 'radius' => 8.0],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Regional airport north of the city (-Z): north-south runway.
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
private static function southSite(float $lz): array
|
|
{
|
|
// lz = -160 for the shipped bounds: runway centre at -300.
|
|
$cZ = $lz - 140;
|
|
|
|
return [
|
|
'id' => 'norte',
|
|
'name' => 'Aeroporto Regional do Norte',
|
|
'code' => 'CRN',
|
|
'runway' => [
|
|
'center' => [-50.0, $cZ],
|
|
'length' => 160.0,
|
|
'width' => 14.0,
|
|
'axis' => 'z',
|
|
'designators' => ['18', '36'],
|
|
],
|
|
'taxiways' => [
|
|
['center' => [-30.0, $cZ], 'length' => 150.0, 'width' => 8.0, 'axis' => 'z'],
|
|
// Connectors aligned with the takeoff lineup (-240) and the
|
|
// landing rollout exit (-370) so taxi paths stay on pavement.
|
|
['center' => [-40.0, $lz - 80], 'length' => 20.0, 'width' => 7.0, 'axis' => 'x'],
|
|
['center' => [-40.0, $lz - 210], 'length' => 20.0, 'width' => 7.0, 'axis' => 'x'],
|
|
],
|
|
'apron' => ['center' => [15.0, $cZ], 'size' => [90.0, 140.0]],
|
|
'terminal' => ['center' => [55.0, $lz - 95], 'size' => [34.0, 14.0]],
|
|
'hangars' => [
|
|
['center' => [55.0, $lz - 205], 'size' => [24.0, 18.0]],
|
|
],
|
|
'tower' => ['center' => [40.0, $lz - 80]],
|
|
'stands' => [
|
|
[0.0, $lz - 95],
|
|
[0.0, $lz - 155],
|
|
[30.0, $lz - 125],
|
|
[30.0, $lz - 185],
|
|
],
|
|
'helipad' => ['center' => [75.0, $lz - 85], 'radius' => 7.0],
|
|
];
|
|
}
|
|
}
|