feat(city): add city landmarks, stores, luminous signs, and ATC echo guard

Add university and fire station civic buildings plus 7 new store types,
each with facade and luminous blade signs. Extend landmark material
colors and sign accents. Fix ATC dispatch to reject prompt-echoing AI
responses via heuristic guard. Add CityPlacesTest for collision and
roadway avoidance, and extend AirportTest and SignLabelsTest.
This commit is contained in:
2026-10-31 22:22:44 -03:00
parent e7498e1566
commit 46b15ec7be
14 changed files with 297 additions and 13 deletions
@@ -365,13 +365,49 @@ class CityViewerController extends Controller
}
if (is_string($text) && trim($text) !== '') {
return mb_substr(trim($text), 0, 240);
$text = mb_substr(trim($text), 0, 240);
if (self::looksLikeClearance($text, $flight)) {
return $text;
}
// Model echoed the instruction instead of the clearance:
// reject and continue down the chain (next model or the
// deterministic fallback in the caller).
Log::warning('ATC clearance rejected (not a clearance)', ['model' => $model]);
}
}
return null;
}
/**
* Heuristic guard: a clearance is short, speech-like and about the
* requested flight — never a restatement of the prompt itself.
*
* @param array{number: string, airline: string, runway: string} $flight
*/
private static function looksLikeClearance(string $text, array $flight): bool
{
$lower = mb_strtolower($text);
$promptMeta = [
'the user wants',
'respond only',
'no quotes',
'atc style',
'short sentence',
'for example',
'instructions',
];
foreach ($promptMeta as $needle) {
if (str_contains($lower, $needle)) {
return false;
}
}
return str_contains($text, $flight['number'])
|| preg_match('/autoriz|pista|runway|cleared|descolagem|decolag/iu', $text) === 1;
}
/**
* Resolve the city from the query string or the active default.
*/