fix: surface clip/direct failures instead of swallowing them

Dropping a clip on a non-mixamo rig (lady.glb is a Character Creator export,
CC_Base_* bones) threw inside a fire-and-forget event handler — the rejected
promise vanished and the drop appeared to do nothing at all. Timeline._fail
now logs + toasts via a shared window.sgToast that tlui installs; clipdrop
and direct handlers route through it.

Repro before: two clipdrops fired, only the mixamo one landed, no error.
After: 'clip: retarget: missing hips (mixamorig skeleton?)' toast.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-25 21:03:21 +10:00
parent 128e24a7cb
commit a785de41ae
3 changed files with 57 additions and 2 deletions

38
scenegod-8020.log Normal file
View File

@ -0,0 +1,38 @@
INFO: Started server process [54956]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8020 (Press CTRL+C to quit)
INFO: 127.0.0.1:54739 - "GET / HTTP/1.1" 200 OK
INFO: 127.0.0.1:54771 - "GET / HTTP/1.1" 200 OK
INFO: 127.0.0.1:54771 - "GET /web/style.css HTTP/1.1" 200 OK
INFO: 127.0.0.1:54772 - "GET /web/stage.js HTTP/1.1" 200 OK
INFO: 127.0.0.1:54773 - "GET /web/dock.js HTTP/1.1" 200 OK
INFO: 127.0.0.1:54773 - "GET /web/room3d.js HTTP/1.1" 200 OK
INFO: 127.0.0.1:54772 - "GET /web/presets.js HTTP/1.1" 200 OK
INFO: 127.0.0.1:54771 - "GET /favicon.ico HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:54773 - "GET /assets/tree HTTP/1.1" 200 OK
INFO: 127.0.0.1:54773 - "GET /web/grammar.js HTTP/1.1" 200 OK
INFO: 127.0.0.1:54773 - "GET /director HTTP/1.1" 405 Method Not Allowed
INFO: 127.0.0.1:54771 - "GET /web/timeline.js HTTP/1.1" 200 OK
INFO: 127.0.0.1:54771 - "GET /web/tlui.js HTTP/1.1" 200 OK
INFO: 127.0.0.1:55276 - "GET /assets/file?path=characters%2Ftest%2Flady.glb HTTP/1.1" 200 OK
INFO: 127.0.0.1:55276 - "GET /assets/file?path=characters%2Ftest%2Fman.fbx HTTP/1.1" 200 OK
INFO: 127.0.0.1:55276 - "GET /assets/file?path=backdrops%2Fvideo%2Fscenegod_plates%2Fsuburb_strip_goldenhour.mp4 HTTP/1.1" 206 Partial Content
INFO: 127.0.0.1:55318 - "GET /assets/file?path=backdrops%2Fvideo%2Fscenegod_plates%2Fsuburb_strip_goldenhour.mp4 HTTP/1.1" 206 Partial Content
INFO: 127.0.0.1:55318 - "GET /assets/thumb?path=backdrops%2Fvideo%2Fscenegod_plates%2Fsuburb_strip_goldenhour.mp4 HTTP/1.1" 200 OK
INFO: 127.0.0.1:55318 - "GET /assets/file?path=animations%2Ftest%2Fhiphop.fbx HTTP/1.1" 200 OK
INFO: 127.0.0.1:55319 - "GET /assets/file?path=animations%2Ftest%2Fcarrying.fbx HTTP/1.1" 200 OK
INFO: 127.0.0.1:55319 - "GET /assets/file?path=backdrops%2Fvideo%2Fscenegod_plates%2Fsuburb_strip_goldenhour.mp4 HTTP/1.1" 206 Partial Content
INFO: 127.0.0.1:57176 - "GET / HTTP/1.1" 200 OK
INFO: 127.0.0.1:57176 - "GET /web/style.css HTTP/1.1" 200 OK
INFO: 127.0.0.1:57176 - "GET /web/dock.js HTTP/1.1" 200 OK
INFO: 127.0.0.1:57177 - "GET /web/stage.js HTTP/1.1" 200 OK
INFO: 127.0.0.1:57176 - "GET /web/room3d.js HTTP/1.1" 200 OK
INFO: 127.0.0.1:57178 - "GET /favicon.ico HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:57177 - "GET /web/presets.js HTTP/1.1" 200 OK
INFO: 127.0.0.1:57176 - "GET /assets/tree HTTP/1.1" 200 OK
INFO: 127.0.0.1:57176 - "GET /web/grammar.js HTTP/1.1" 200 OK
INFO: 127.0.0.1:57176 - "GET /director HTTP/1.1" 405 Method Not Allowed
INFO: 127.0.0.1:57177 - "GET /web/timeline.js HTTP/1.1" 200 OK
INFO: 127.0.0.1:57177 - "GET /web/tlui.js HTTP/1.1" 200 OK
INFO: 127.0.0.1:57221 - "GET /assets/file?path=characters%2Ftest%2Flady.glb HTTP/1.1" 200 OK

View File

@ -95,13 +95,18 @@ export class Timeline {
if (id) this.addKey(id, 'transform', { t: this.time, ...this.stage.entityTransform(id), ease: 'inout' });
});
// Lane A dock drops a clip on a character → add a block at the playhead.
// REVIEW-FIX: this is fire-and-forget from an event handler, so a rejected
// promise vanished — a non-mixamo rig (CC_Base_*) failed the retarget and
// the drop just silently did nothing. Surface every failure.
window.addEventListener('scenegod:clipdrop', (e) => {
if (e.detail && e.detail.id) this.addClipDrop(e.detail);
if (e.detail && e.detail.id) this.addClipDrop(e.detail).catch((err) => this._fail('clip', err));
});
// M5 DIRECTOR MODE: Lane A presets dispatch ops → normal keys/cuts at
// the playhead (contract: lanes/A §M5.3 / PLAN §5 M5).
window.addEventListener('scenegod:direct', (e) => {
if (e.detail && e.detail.op) this.applyDirect(e.detail);
if (e.detail && e.detail.op) {
try { this.applyDirect(e.detail); } catch (err) { this._fail('direct', err); }
}
});
}
// SYNC2 seam: mirror live stage adds/removes into scene.entities so
@ -109,6 +114,15 @@ export class Timeline {
if (stage && stage.onChange) stage.onChange((en) => this._mirror(en));
}
// One place for "the user asked for something and it didn't happen": console + a
// toast if the UI provides one (tlui installs window.sgToast). Never swallow.
_fail(what, err) {
console.error(`[scenegod:${what}]`, err);
const msg = (err && err.message) || String(err);
if (typeof window !== 'undefined' && typeof window.sgToast === 'function') window.sgToast(`${what}: ${msg}`);
return null;
}
_mirror(en) {
if (!en || !en.id) return;
const list = this.scene.entities;

View File

@ -48,6 +48,9 @@ export class TimelineUI {
this.tl = timeline;
this.stage = timeline.stage;
this.el = typeof mountEl === 'string' ? document.querySelector(mountEl) : mountEl;
// REVIEW-FIX: shared failure surface — timeline._fail (and any other module)
// toasts through here instead of dying silently in the console.
window.sgToast = (m) => this._toast(m);
this._hits = [];
this._drag = null;
this._selKeys = []; // box-selected keys: [{id, track, key}]