fix(street-level): camera ownership, mesh refresh, panel CSP (Sameh's review)

- Photo framing and FOLLOW claim the camera through the application's
  navigation, injected with attachNavigation({ run, subscribeHandoff }):
  aircraft and satellite tracking is released first, the cockpit refuses,
  and FOLLOW stops when another feature takes the camera. No UI imports
  in the layer.
- The mesh sampler follows the mesh floor's rules: it waits for the
  visible tileset to finish streaming (visibleTilesetLoaded, now exposed
  by the mesh floor service), needs a real bare-earth prior and keeps to
  the shared meshFloorSampleWithinPrior window, which cameraFollow now
  uses too. A cell is probed again once the camera is half as far from
  it (down to 40 m), so a coarse early sample is replaced and redrawn.
- The MCP globe panel's CSP allows the Mapillary Graph API and its image
  CDN (https://*.xx.fbcdn.net).
- The tile proxy reads bodies with the shared readResponseBytesCapped.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-10-06 23:04:33 +01:00
1 parent 630a1abdae
commit 1fa129ea8e
13 files changed
+459 -79

No files matched your search

@@ -156,3 +156,80 @@ test('turning FOLLOW on stops the framing flight so it cannot fight the follow v
assert.equal(camera.cancelled, 1, 'the framing tween stops');
assert.equal(views.length, 1, 'and the camera stands at the photo');
});
/**
* The application's camera authority: `run` releases tracking (here, a tracked
* entity) and stamps a handoff before the move, or refuses like the cockpit.
*/
function navigationFor(state, { refuse = false } = {}) {
const listeners = new Set();
const nav = {
runs: [],
run(noun, move) {
nav.runs.push(noun);
if (refuse) return false;
for (const listener of listeners)
listener({ generation: nav.runs.length });
state.viewer.trackedEntity = undefined;
return move(nav.runs.length);
},
subscribeHandoff(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
},
handOff() {
for (const listener of listeners) listener({ generation: -1 });
},
listeners,
};
return nav;
}
test('framing a photo claims the camera through the application, releasing tracking', () => {
const { follow, state, flights } = setup({ sampled: 160 });
state.viewer.trackedEntity = { id: 'aircraft' };
const nav = navigationFor(state);
follow.attachNavigation(nav);
follow.lookAtPosition();
assert.deepEqual(nav.runs, ['photo']);
assert.equal(state.viewer.trackedEntity, undefined);
assert.equal(flights.length, 1);
});
test('a refused claim (cockpit) neither frames the photo nor turns FOLLOW on', () => {
const { follow, state, flights, views } = setup({ sampled: 160 });
follow.attachNavigation(navigationFor(state, { refuse: true }));
follow.lookAtPosition();
follow.setFollow(true);
assert.equal(flights.length, 0);
assert.equal(views.length, 0);
assert.equal(state.street.follow, false);
});
test('FOLLOW claims the camera once and ends when another feature takes it', () => {
const { follow, state, views } = setup({ sampled: 160 });
let notified = 0;
state.notify = () => notified++;
const nav = navigationFor(state);
follow.attachNavigation(nav);
follow.setFollow(true);
assert.equal(state.street.follow, true, 'our own handoff keeps FOLLOW on');
assert.deepEqual(nav.runs, ['photo']);
follow.followCamera();
assert.deepEqual(nav.runs, ['photo'], 'pose updates do not claim again');
assert.equal(views.length, 2);
nav.handOff();
assert.equal(state.street.follow, false);
assert.ok(notified >= 2);
follow.followCamera();
assert.equal(views.length, 2, 'the camera is no longer driven');
});
test('destroying the follow part stops listening for handoffs', () => {
const { follow, state } = setup({ sampled: 160 });
const nav = navigationFor(state);
follow.attachNavigation(nav);
assert.equal(nav.listeners.size, 1);
follow.destroy();
assert.equal(nav.listeners.size, 0);
});