fix(drone): resolve drone feed fog and flicker issues

- Add preserveDrawingBuffer to enable automated test screenshots
- Neutralize scene fog during drone render pass to prevent misted aerial view
- Render drone feed every frame instead of alternating with ground view
- Update visor styling with scanline effect and improved borders
- Add debug getters for WebGL renderer and scene information
This commit is contained in:
2026-11-01 01:00:56 -03:00
parent 68cb4eb9ad
commit a43176a68e
16 changed files with 54 additions and 7 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

+47 -4
View File
@@ -30,7 +30,9 @@ scene.fog = new THREE.Fog(0x87ceeb, 120, 600);
const camera = new THREE.PerspectiveCamera(72, 1, 0.1, 2000); const camera = new THREE.PerspectiveCamera(72, 1, 0.1, 2000);
camera.position.set(0, 1.7, 0); camera.position.set(0, 1.7, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true }); // preserveDrawingBuffer: lets automated tests/screenshots read real
// frame pixels (the PIP visor proof); cost is negligible here.
const renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true; renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; renderer.shadowMap.type = THREE.PCFSoftShadowMap;
@@ -4347,6 +4349,18 @@ function renderDroneView() {
drone.cam.aspect = w / h; drone.cam.aspect = w / h;
drone.cam.updateProjectionMatrix(); drone.cam.updateProjectionMatrix();
// Neutralise the city fog for the drone feed so the aerial view is
// never misted ("embassado"). Only the fog uniform values change for
// this pass — three refreshes them per render, so no material is
// recompiled and the ground view keeps its weather haze.
const fog = scene.fog;
const fogNear = fog ? fog.near : 0;
const fogFar = fog ? fog.far : 0;
if (fog) {
fog.near = 1e7;
fog.far = 2e7; // smoothstep → factor 0 at any real distance
}
renderer.setScissorTest(true); renderer.setScissorTest(true);
renderer.setScissor(x, y, w, h); renderer.setScissor(x, y, w, h);
renderer.setViewport(x, y, w, h); renderer.setViewport(x, y, w, h);
@@ -4354,6 +4368,12 @@ function renderDroneView() {
renderer.setScissorTest(false); renderer.setScissorTest(false);
renderer.setViewport(0, 0, cr.width, cr.height); renderer.setViewport(0, 0, cr.width, cr.height);
if (fog) {
fog.near = fogNear;
fog.far = fogFar;
}
drone.fogCleared = !!fog;
drone.view = { x, y, w, h }; drone.view = { x, y, w, h };
} }
@@ -7137,10 +7157,10 @@ function animate() {
renderer.render(scene, camera); renderer.render(scene, camera);
perfFrame.render = performance.now() - tRender; perfFrame.render = performance.now() - tRender;
// Drone command-panel feed: scissored second pass on alternate frames. // Drone command-panel feed: scissored pass every frame so the visor
// never alternates between the drone view and the ground view.
if (drone.active && drone.mesh) { if (drone.active && drone.mesh) {
drone.frame += 1; renderDroneView();
if (drone.frame % 2 === 0) renderDroneView();
} }
} }
animate(); animate();
@@ -7626,6 +7646,26 @@ window.__city10 = {
return out; return out;
}, },
playerCrossingAllowed, playerCrossingAllowed,
/** Debug: the WebGL renderer (frame forensics for automated tests). */
get gl() {
return renderer;
},
/** Debug: live scene background/fog hex + camera pose. */
get sceneInfo() {
return {
bg: scene.background ? scene.background.getHexString() : null,
fog: scene.fog ? scene.fog.color.getHexString() : null,
fogNear: scene.fog ? scene.fog.near : null,
fogFar: scene.fog ? scene.fog.far : null,
cam: {
x: +camera.position.x.toFixed(2),
y: +camera.position.y.toFixed(2),
z: +camera.position.z.toFixed(2),
},
toneMapping: renderer.toneMapping,
exposure: renderer.toneMappingExposure,
};
},
/** Current bump-to-talk peer (null when the chat is closed). */ /** Current bump-to-talk peer (null when the chat is closed). */
get talkPeer() { get talkPeer() {
return currentPeer; return currentPeer;
@@ -7684,6 +7724,8 @@ window.__city10 = {
trafficLights: trafficSystem ? trafficSystem.heads.length / 2 : 0, trafficLights: trafficSystem ? trafficSystem.heads.length / 2 : 0,
crosswalks: crosswalkCount, crosswalks: crosswalkCount,
sidewalkSegments: sidewalkSegmentCount, sidewalkSegments: sidewalkSegmentCount,
hour: +(sim.hour ?? 0).toFixed(1),
night: +(sim.nightFactor ?? 0).toFixed(2),
beach: beachSystem ? beachSystem.counts() : null, beach: beachSystem ? beachSystem.counts() : null,
flights: airplaneSystem ? airplaneSystem.flights() : null, flights: airplaneSystem ? airplaneSystem.flights() : null,
port: portSystem ? portSystem.counts() : null, port: portSystem ? portSystem.counts() : null,
@@ -7693,6 +7735,7 @@ window.__city10 = {
y: +drone.pos.y.toFixed(1), y: +drone.pos.y.toFixed(1),
z: +drone.pos.z.toFixed(1), z: +drone.pos.z.toFixed(1),
yaw: +drone.yaw.toFixed(2), yaw: +drone.yaw.toFixed(2),
fogCleared: !!drone.fogCleared,
view: drone.view || { x: 0, y: 0, w: 0, h: 0 }, view: drone.view || { x: 0, y: 0, w: 0, h: 0 },
} : null, } : null,
robots: pedestrianSystem robots: pedestrianSystem
+7 -3
View File
@@ -254,9 +254,13 @@
style="border:0; background:rgba(148,163,184,.18); color:#e2e8f0; border-radius:.35rem; cursor:pointer; padding:.1rem .45rem;">✕</button> style="border:0; background:rgba(148,163,184,.18); color:#e2e8f0; border-radius:.35rem; cursor:pointer; padding:.1rem .45rem;">✕</button>
</div> </div>
<div id="drone-video" <div id="drone-video"
style="height:200px; background:transparent; style="height:200px;
border-top:1px solid rgba(148,163,184,.35); background:repeating-linear-gradient(0deg,
border-bottom:1px solid rgba(148,163,184,.35);"></div> rgba(0,0,0,.14) 0px, rgba(0,0,0,.14) 2px,
rgba(0,0,0,0) 2px, rgba(0,0,0,0) 4px);
border:7px solid #050a14; border-radius:.4rem;
box-shadow:inset 0 0 16px rgba(0,0,0,.75),
0 0 0 2px rgba(148,163,184,.45);"></div>
<div id="drone-hints" <div id="drone-hints"
style="padding:.4rem .6rem; font-size:.7rem; color:#cbd5e1; background:rgba(2,8,20,.88);"> style="padding:.4rem .6rem; font-size:.7rem; color:#cbd5e1; background:rgba(2,8,20,.88);">
{{ __('city.drone_hint') }} {{ __('city.drone_hint') }}