name: blitz3d-animation-system description: Implement and debug Blitz3D skeletal animation semantics in Blitz3D-WASM and the SCPCB web port, including frame-based SetAnimTime/AnimTime behavior, Animate/UpdateWorld-style playback, SMPK animation metadata (fps/sequences), and correct Three.js mixer/action integration across main thread and Web Worker runtimes.
Blitz3D Animation System (SCPCB-focused)
Ground truth: how SCPCB drives animation
- SCPCB advances animation in game logic using frame units and
FPSfactor, then callsSetAnimTime entity, frame.- See
../scpcb/Main.bbfunctionsAnimateNPC,SetNPCFrame,Animate2.
- See
- SCPCB reads the pose cursor back via
AnimTime(entity)(primarily for branching/comparisons). - SCPCB does not rely on Blitz3D
Animate()auto-advancing for NPCs in the common paths; a minimal correct port needsSetAnimTime+AnimTimefirst.
Required semantics (what your runtime must make true)
- Treat
timepassed toSetAnimTime(entity, time, seq)as frames, not seconds. AnimTime(entity)should return the current frame cursor (ideally exactly what was set, modulo clamping).AnimLength(entity)should return the total length in frames for the active clip/sequence.- If you implement
Animate()auto-play:- Ensure the system advances animation state during the engine tick (Blitz3D advances on
UpdateWorld).
- Ensure the system advances animation state during the engine tick (Blitz3D advances on
Mapping frames ↔ Three.js time
SMPK stores:
root.userData.fps(fromweb/src/runtime/smpk.ts)root.userData.sequences(optional, from B3DSEQS)
Implement these conversions:
SetAnimTime(entity, frame):action.time = frame / fpsmixer.update(0)to force evaluation
AnimTime(entity):return action.time * fps
AnimLength(entity):return action.getClip().duration * fps
Avoid hard-coding 30 — use entity.userData.fps || 30.
Where to wire imports (this repo)
Both the main-thread loader and the SCPCB worker currently call stubMissingImports(...), so missing animation imports will silently degrade unless implemented.
- Main thread instantiation:
web/src/main.ts(look forinstantiateWasmandbuildImports)
- Worker instantiation:
web/src/worker/scpcb_worker.ts(look forbuildImportsandinstantiate)
Add/import-bind these functions (at minimum):
SetAnimTimeAnimTimeAnimLength
Optionally (if you want to support broader Blitz3D programs):
AnimateAnimatingExtractAnimSeq/AddAnimSeq/AnimSeq
Debug checklist (fast)
- Confirm the module actually calls animation imports:
- Run and watch the
missing:env.SetAnimTimecounters in the worker (seestubMissingImportsonCallMissingFunctionhook).
- Run and watch the
- Confirm the entity you’re animating has an SMPK mixer/action:
root.userData.mixerandroot.userData.actionmust exist.
- Confirm you advance mixers somewhere:
- If relying on
Animate()auto-play, callmixer.update(deltaSeconds)per tick. - For SCPCB’s manual stepping via
SetAnimTime, you still need tomixer.update(0)after setting time.
- If relying on
- If pose looks wrong:
- Verify bone hierarchy and
SkinnedMesh.bind(...)behavior inweb/src/runtime/smpk.ts(bones must remain in original hierarchy).
- Verify bone hierarchy and