fix: load scene over HTTP so tracking no longer depends on the WebSocket; HUD shows scene state and detected-vs-usable crests

This commit is contained in:
2026-08-19 16:31:37 +10:00
parent c0a998ad19
commit 7cfadf2aca
+36 -9
View File
@@ -52,8 +52,10 @@ let detector, poseEst, fuser, cvSolver;
let gradients = {}, modelManifest = null, characters = { defaults: {}, byId: {} };
let ghostHeight = 4; // cm, from scene.ghostHeightCm
const activeGhosts = new Map(); // uid -> { rec, group }
let tracking = { markers: 0, lastSeen: 0 };
let tracking = { markers: 0, raw: 0, rawIds: [], lastSeen: 0 };
let netRef = null;
let sceneLoaded = false;
let anchorCount = 0;
const rxLog = []; // last few WS message types, newest last
function rxTrace(t) { rxLog.push(t); if (rxLog.length > 6) rxLog.shift(); }
const clockOffsetSamples = [];
@@ -71,7 +73,7 @@ let dbgPose = '';
function dbgStatus() {
const ws = netRef && netRef.ws ? ['conn','open','closing','closed'][netRef.ws.readyState] : 'none';
return `ws ${ws} rx ${rxLog.join(',') || '-'}\n` +
`ghost recs ${activeGhosts.size}\n`;
`scene ${sceneLoaded ? anchorCount + ' anchors' : 'NOT LOADED'} ghost recs ${activeGhosts.size}\n`;
}
function paintDbg() { if (dbgEl) dbgEl.textContent = dbgStatus() + dbgPose + dbgAim; }
@@ -123,7 +125,7 @@ function updateDbg(fusedQuat, markerCount, extra) {
dbgPose =
`eng ${engine} ${freezeGhosts ? 'FROZEN' : 'moving'}\n` +
`${modeLine}\n` +
`markers ${markerCount}${extra}\n${rawLine}\nview ${v3(view)}\nup ${v3(up)}\n` +
`markers ${markerCount}/${tracking.raw} seen [${tracking.rawIds.join(',')}]${extra}\n${rawLine}\nview ${v3(view)}\nup ${v3(up)}\n` +
`pos ${v3(camera3.position)}`;
paintDbg();
}
@@ -222,6 +224,13 @@ async function start() {
cvSolver = new CvBoardSolver();
ensureOpenCV();
// scene over HTTP so tracking works even if the WebSocket is down
try {
applyScene(await (await fetch('/api/scene')).json());
} catch (e) {
reportError({ kind: 'scene', msg: `scene fetch failed: ${(e && e.message) || e}` });
}
// dev-mode pose readout + tap-to-cycle modes
if (info.mode === 'dev') {
freezeGhosts = true; // start static so tracking jitter is isolated from ghost float
@@ -266,11 +275,7 @@ async function start() {
const net = new ExhibitNet();
netRef = net;
const on = (t, fn) => net.on(t, (m) => { rxTrace(t); return fn(m); });
on('scene', (m) => {
fuser.setScene(m.scene); cvSolver.setScene(m.scene); buildOcclusion(m.scene);
ghostHeight = m.scene.ghostHeightCm || 4;
for (const e of activeGhosts.values()) e.group.userData.setHeight(ghostHeight);
});
on('scene', (m) => applyScene(m.scene));
on('characters', async (m) => {
characters = m.characters || characters;
const recs = [...activeGhosts.values()].map(e => e.rec);
@@ -301,6 +306,22 @@ function onResize() {
camera3.updateProjectionMatrix();
}
/* Scene application. Tracking must NOT depend on the WebSocket: detection filters
* against the scene's marker IDs, so if the socket is slow or flapping the crest
* is detected and then silently discarded ("Point at a Newbury Crest" forever).
* We therefore load the scene over plain HTTP at start; the WS message just
* refreshes it when an operator edits the layout. */
function applyScene(scene) {
if (!scene) return;
sceneLoaded = true;
anchorCount = (scene.anchors || []).filter(a => a.enabled !== false).length;
fuser.setScene(scene);
cvSolver.setScene(scene);
buildOcclusion(scene);
ghostHeight = scene.ghostHeightCm || 4;
for (const e of activeGhosts.values()) e.group.userData.setHeight(ghostHeight);
}
// ---------- occlusion: invisible depth-only building volumes ----------
const occlusionGroup = new THREE.Group();
function buildOcclusion(scene) {
@@ -374,6 +395,8 @@ function loop(t) {
const markers = detectMarkers(detector, img, fuser.knownIds());
tracking.markers = markers.length;
tracking.raw = markers.rawCount || 0;
tracking.rawIds = markers.rawIds || [];
if (dbgEl) updateAim(markers, W, H);
if (markers.length) {
tracking.lastSeen = performance.now();
@@ -421,7 +444,11 @@ function loop(t) {
if ((frameCount++ & 15) === 0) {
paintDbg();
const stale = performance.now() - tracking.lastSeen > 1500;
$('#trk').textContent = stale ? 'Point at a Newbury Crest' : `Tracking ${tracking.markers} crest${tracking.markers === 1 ? '' : 's'}`;
$('#trk').textContent = !stale
? `Tracking ${tracking.markers} crest${tracking.markers === 1 ? '' : 's'}`
: (tracking.raw > 0
? `Crest ${tracking.rawIds.join(',')} not in this layout` // detected, but no anchor for it
: 'Point at a Newbury Crest');
$('#trk').classList.toggle('warn', stale || tracking.markers < 2);
$('#cnt').textContent = `${activeGhosts.size} ghost${activeGhosts.size === 1 ? '' : 's'} nearby`;
}