Files
City10/app/Support/Airports.php
T
marcocosta10000 e7498e1566 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.
2026-10-31 22:06:18 -03:00

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],
];
}
}