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