diff --git a/.playwright-mcp/semaforo-corrigido-ew-green.png b/.playwright-mcp/semaforo-corrigido-ew-green.png
new file mode 100644
index 0000000..493aa07
Binary files /dev/null and b/.playwright-mcp/semaforo-corrigido-ew-green.png differ
diff --git a/.playwright-mcp/semaforo-corrigido-ns-green.png b/.playwright-mcp/semaforo-corrigido-ns-green.png
new file mode 100644
index 0000000..5aabc43
Binary files /dev/null and b/.playwright-mcp/semaforo-corrigido-ns-green.png differ
diff --git a/.playwright-mcp/semaforo-ns-green-antes.png b/.playwright-mcp/semaforo-ns-green-antes.png
new file mode 100644
index 0000000..a0757a7
Binary files /dev/null and b/.playwright-mcp/semaforo-ns-green-antes.png differ
diff --git a/.playwright-mcp/semaforo-pedestres-corrigido.png b/.playwright-mcp/semaforo-pedestres-corrigido.png
new file mode 100644
index 0000000..aeb1db5
Binary files /dev/null and b/.playwright-mcp/semaforo-pedestres-corrigido.png differ
diff --git a/public/js/city/app.js b/public/js/city/app.js
index b4dbdbd..82912dc 100755
--- a/public/js/city/app.js
+++ b/public/js/city/app.js
@@ -556,13 +556,15 @@ function initTrafficLights(g) {
group.add(pole);
// Mast arm over the vertical road (northbound lane), facing
- // south so approaching traffic sees the signal.
+ // south so approaching traffic sees the signal. Northbound
+ // drives on the east half (right-hand traffic); the SE-corner
+ // pole sits on that same (right) side.
const armNS = new THREE.Mesh(new THREE.BoxGeometry(0.12, 0.1, 2.2), poleMat);
armNS.position.set(x + sw, 4.0, z + sw - 1.1);
group.add(armNS);
const headNS = makeSignalHead();
headNS.position.set(x + sw, 3.7, z + sw - 2.2);
- headNS.rotation.y = Math.PI; // face -Z (south)
+ headNS.rotation.y = 0; // face +Z (south — toward northbound traffic)
group.add(headNS);
heads.push({ head: headNS, axis: 'z' });
@@ -578,23 +580,30 @@ function initTrafficLights(g) {
// Pedestrian signals: short posts on the same corner, well
// inside the sidewalk, facing the crosswalks they control.
- const pedZ = makePedSignal();
- const pedZPole = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.06, 2.4, 6), pedPoleMat);
- pedZPole.position.set(x + sw + 0.6, 1.2, z + sw - 0.6);
- group.add(pedZPole);
- pedZ.position.set(x + sw + 0.6, 2.5, z + sw - 0.6);
- pedZ.rotation.y = Math.PI; // faces south crosswalk
- group.add(pedZ);
- pedHeads.push({ head: pedZ, axis: 'z' });
+ //
+ // The east crosswalk crosses the horizontal (x) road, so its
+ // signal follows the x-axis vehicle state. It sits on the east
+ // sidewalk facing north, for walkers arriving from the north.
+ const pedEast = makePedSignal();
+ const pedEastPole = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.06, 2.4, 6), pedPoleMat);
+ pedEastPole.position.set(x + sw + 0.6, 1.2, z + sw - 0.6);
+ group.add(pedEastPole);
+ pedEast.position.set(x + sw + 0.6, 2.5, z + sw - 0.6);
+ pedEast.rotation.y = Math.PI; // faces north (approach from the north)
+ group.add(pedEast);
+ pedHeads.push({ head: pedEast, axis: 'x' });
- const pedX = makePedSignal();
- const pedXPole = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.06, 2.4, 6), pedPoleMat);
- pedXPole.position.set(x + sw - 0.6, 1.2, z + sw + 0.6);
- group.add(pedXPole);
- pedX.position.set(x + sw - 0.6, 2.5, z + sw + 0.6);
- pedX.rotation.y = -Math.PI / 2; // faces west crosswalk
- group.add(pedX);
- pedHeads.push({ head: pedX, axis: 'x' });
+ // The south crosswalk crosses the vertical (z) road, so its
+ // signal follows the z-axis vehicle state. It sits on the south
+ // sidewalk facing west, for walkers arriving from the east.
+ const pedSouth = makePedSignal();
+ const pedSouthPole = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.06, 2.4, 6), pedPoleMat);
+ pedSouthPole.position.set(x + sw - 0.6, 1.2, z + sw + 0.6);
+ group.add(pedSouthPole);
+ pedSouth.position.set(x + sw - 0.6, 2.5, z + sw + 0.6);
+ pedSouth.rotation.y = -Math.PI / 2; // faces west (approach from the east)
+ group.add(pedSouth);
+ pedHeads.push({ head: pedSouth, axis: 'z' });
}
}
@@ -4329,6 +4338,64 @@ window.__city10 = {
if (trafficSystem) trafficSystem.setPhase(name);
return this.trafficPhase;
},
+ /**
+ * Snapshot of every signal head (position, facing, axis and the lamp
+ * currently lit) so automated tests can assert placement and states.
+ */
+ trafficLightHeads() {
+ if (!trafficSystem) return { vehicle: [], pedestrian: [] };
+ const vehicle = trafficSystem.heads.map(({ head, axis }) => {
+ const intensities = head.userData.lamps.map(
+ l => +l.material.emissiveIntensity.toFixed(2)
+ );
+ const active = ['red', 'yellow', 'green'][intensities.indexOf(Math.max(...intensities))];
+ return {
+ axis,
+ x: +head.position.x.toFixed(2),
+ y: +head.position.y.toFixed(2),
+ z: +head.position.z.toFixed(2),
+ rotationY: +head.rotation.y.toFixed(3),
+ active,
+ intensities,
+ };
+ });
+ const pedestrian = trafficSystem.pedHeads.map(({ head, axis }) => {
+ const red = +head.userData.lamps.red.material.emissiveIntensity.toFixed(2);
+ const green = +head.userData.lamps.green.material.emissiveIntensity.toFixed(2);
+ return {
+ axis,
+ x: +head.position.x.toFixed(2),
+ y: +head.position.y.toFixed(2),
+ z: +head.position.z.toFixed(2),
+ rotationY: +head.rotation.y.toFixed(3),
+ active: red > green ? 'red' : 'green',
+ };
+ });
+ return { vehicle, pedestrian };
+ },
+ /**
+ * Per-vehicle stop-line probe: travel axis, distance to the next stop
+ * line and current speed, for red-light compliance checks.
+ */
+ trafficProbe() {
+ if (!vehicleSystem) return [];
+ const out = [];
+ for (const v of vehicleSystem.vehicles) {
+ if (!v.pts) continue;
+ const probe = probeAhead(v.pts, v.s, v.L);
+ const cur = new THREE.Vector3();
+ posOnPath(v.pts, v.s, v.L, cur);
+ out.push({
+ x: +cur.x.toFixed(2),
+ z: +cur.z.toFixed(2),
+ speed: +(v.speed || 0).toFixed(2),
+ axis: probe.axis,
+ distToStop: +distanceToStopLine(probe, cur).toFixed(2),
+ state: trafficSystem ? trafficSystem.axisState(probe.axis) : null,
+ });
+ }
+ return out;
+ },
playerCrossingAllowed,
get barriersDown() { return trainSystem ? trainSystem.blocks : null; },
setHour(h) {
diff --git a/resources/views/city/viewer.blade.php b/resources/views/city/viewer.blade.php
index 2c83798..746d75a 100755
--- a/resources/views/city/viewer.blade.php
+++ b/resources/views/city/viewer.blade.php
@@ -372,5 +372,5 @@
citySlug: @json($city->slug),
};
-
+
@endpush