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
+13 -9
View File
@@ -4932,8 +4932,10 @@ function buildCity(data) {
const [bx, , bz] = b.position;
const [bw, bh, bd] = b.size;
const isHospital = b.type === 'hospital';
const landmarkBodyColors = { university: 0x7b87c9, fire_station: 0xc0442e };
const mat = new THREE.MeshStandardMaterial({
color: isHospital ? 0xe8eef5 : (b.is_complete ? 0x8b6f47 : 0x6b7280),
color: isHospital ? 0xe8eef5
: (landmarkBodyColors[b.type] ?? (b.is_complete ? 0x8b6f47 : 0x6b7280)),
roughness: 0.9,
transparent: !b.is_complete,
opacity: b.is_complete ? 1 : 0.75,
@@ -4956,19 +4958,21 @@ function buildCity(data) {
}
// Luminous roofline letreiro for the landmark buildings.
const landmarkLabels = {
hospital: i18n.signHospital || 'Hospital',
school: i18n.signSchool || 'Escola',
city_hall: i18n.signCityhall || 'Prefeitura',
const landmarkStyle = {
hospital: { label: i18n.signHospital || 'Hospital', accent: '#f87171' },
school: { label: i18n.signSchool || 'Escola', accent: '#4ade80' },
city_hall: { label: i18n.signCityhall || 'Prefeitura', accent: '#7dd3fc' },
university: { label: i18n.signUniversity || 'Universidade', accent: '#a5b4fc' },
fire_station: { label: i18n.signFirestation || 'Corpo de Bombeiros', accent: '#fb923c' },
};
if (b.is_complete && landmarkLabels[b.type]) {
const landmark = landmarkStyle[b.type];
if (b.is_complete && landmark) {
const lFace = faceNearestRoad(bx, bz, grid);
const [lOffX, lOffZ] = facingOffset(lFace, bw / 2 + 0.35, bd / 2 + 0.35);
const landmarkSign = buildLitSign(landmarkLabels[b.type], {
const landmarkSign = buildLitSign(landmark.label, {
w: Math.min(11, Math.max(bw, bd) * 0.8),
h: 1.6,
accent: b.type === 'hospital' ? '#f87171'
: (b.type === 'school' ? '#4ade80' : '#7dd3fc'),
accent: landmark.accent,
});
landmarkSign.position.set(bx + lOffX, bh + 1.35, bz + lOffZ);
landmarkSign.rotation.y = lFace;