Download ceremony.js from FormaLau/microduck-reachy-simulator: direct link, hf CLI and curl.
- Browser
- Download file 6.59 kB
-
https://huggingface.co/spaces/FormaLau/microduck-reachy-simulator/resolve/fa03d3d92d22df70c49a85cbddfdfd3bcd853436/ceremony.js
- Command line
-
hf download hf://spaces/FormaLau/microduck-reachy-simulator@fa03d3d92d22df70c49a85cbddfdfd3bcd853436/ceremony.js
-
curl -L -o ceremony.js https://huggingface.co/spaces/FormaLau/microduck-reachy-simulator/resolve/fa03d3d92d22df70c49a85cbddfdfd3bcd853436/ceremony.js
6.59 kB
| // Cutscene layer: page-load entrance and every later respawn. | |
| // | |
| // Physics (resetSim) snaps the duck to the stand keyframe; this module | |
| // owns what the player sees around that snap. Two mutually exclusive | |
| // timelines share the duck's wireframe FX: | |
| // | |
| // entrance (once) grid + walls draw in, then the duck scans up | |
| // respawn (every kill / Space / loco switch) | |
| // 0.00 duck clipped away, camera glides home from the kill view | |
| // 0.80 camera almost settled -> duck scan-up cues | |
| // 0.90 camera lands | |
| // 1.70 scan done, inputs unlock | |
| // | |
| // rl.js injects the world (rig, grid, camera reset) and the lock/flash | |
| // callbacks; this file never touches MuJoCo. | |
| export const CAM_RESET_S = 0.9; | |
| export const RESPAWN_SCAN_AT = 0.8; // cue scan-up at 80% of the camera glide | |
| const ENTRANCE_GRID_S = 1.1; | |
| const ENTRANCE_WALL_DELAY_S = 0.35; | |
| const ENTRANCE_WALL_S = 0.9; | |
| const ENTRANCE_FX_START_S = 0.6; | |
| const clamp01 = (x) => Math.min(Math.max(x, 0), 1); | |
| export function createCeremony({ | |
| THREE, scene, camera, renderer, fx, | |
| getRig, grid, wallMats, | |
| syncRig, startCameraReset, | |
| setLocked, flashReset, | |
| }) { | |
| const ENTRANCE_TOTAL_S = Math.max( | |
| ENTRANCE_GRID_S, | |
| ENTRANCE_WALL_DELAY_S + ENTRANCE_WALL_S, | |
| ENTRANCE_FX_START_S + fx.TOTAL_S, | |
| ); | |
| let fxRig = null; | |
| let fxPrev = null; | |
| // Decorative props (addPropFx): each owns a wireframe FX bound to its | |
| // own root. They cue whenever the duck's scan cues (entrance and every | |
| // respawn), offset by their stagger, and drive on their OWN clock so a | |
| // late-finishing prop never extends the input lock or the ceremony's | |
| // completion checks. | |
| const props = []; // { fx, delayS, at, started, prev } | |
| let entranceT0 = null; | |
| let entranceFxCued = false; | |
| let entranceDone = false; | |
| let respawn = null; // { t0, scanCued } | null | |
| let entranceFinishedResolve; | |
| const entranceFinished = new Promise((r) => { entranceFinishedResolve = r; }); | |
| function bind() { | |
| const rig = getRig(); | |
| if (fxRig === rig) return; | |
| fx.dispose(); | |
| fx.init({ THREE, scene, rig, camera, renderer }); | |
| rig.placer.traverse((o) => { | |
| if (o.isMesh && !o.userData.meshName) o.userData.fxOverlay = true; | |
| }); | |
| fxRig = rig; | |
| } | |
| function fxStart() { | |
| bind(); | |
| fx.start(); | |
| fxPrev = performance.now(); | |
| propsCue(); | |
| } | |
| function propsCue() { | |
| const now = performance.now(); | |
| for (const p of props) { | |
| p.at = now + p.delayS * 1000; | |
| p.started = false; | |
| p.prev = null; | |
| } | |
| } | |
| function propsHide() { | |
| for (const p of props) { | |
| p.fx.setProgress(0); | |
| p.at = null; | |
| p.started = false; | |
| } | |
| } | |
| function propsFinish() { | |
| for (const p of props) { | |
| if (!p.started) p.fx.start(); | |
| p.fx.update(1e3); | |
| p.at = null; | |
| } | |
| } | |
| // Deterministic screenshot/test hook (setFx): park every prop at the | |
| // same scan progress as the duck, detached from the timed driver. | |
| function propsSetProgress(v) { | |
| for (const p of props) { | |
| p.fx.setProgress(v); | |
| p.at = null; | |
| p.started = false; | |
| } | |
| } | |
| function driveProps() { | |
| const now = performance.now(); | |
| for (const p of props) { | |
| if (p.at === null || now < p.at) continue; | |
| if (!p.started) { | |
| p.fx.start(); | |
| p.started = true; | |
| p.prev = now; | |
| continue; | |
| } | |
| if (p.fx.isDone()) continue; | |
| const dt = Math.min((now - p.prev) / 1000, 0.25); | |
| p.prev = now; | |
| p.fx.update(dt); | |
| } | |
| } | |
| function fxDrive() { | |
| if (fxPrev === null || fx.isDone()) return; | |
| const now = performance.now(); | |
| const dt = Math.min((now - fxPrev) / 1000, 0.25); | |
| fxPrev = now; | |
| fx.update(dt); | |
| } | |
| function fxForceFinish() { | |
| if (!fx.isDone()) fx.update(1e3); | |
| } | |
| function startEntrance() { | |
| if (entranceT0 !== null || entranceDone) return; | |
| entranceT0 = performance.now(); | |
| entranceFxCued = false; | |
| grid.material.uniforms.uReveal.value = 0; | |
| for (const m of wallMats) m.uniforms.uReveal.value = 0; | |
| } | |
| function driveEntrance() { | |
| if (entranceT0 === null) return; | |
| const t = (performance.now() - entranceT0) / 1000; | |
| grid.material.uniforms.uReveal.value = clamp01(t / ENTRANCE_GRID_S); | |
| const wallR = clamp01((t - ENTRANCE_WALL_DELAY_S) / ENTRANCE_WALL_S); | |
| for (const m of wallMats) m.uniforms.uReveal.value = wallR; | |
| if (t >= ENTRANCE_FX_START_S && !entranceFxCued) { | |
| entranceFxCued = true; | |
| fxStart(); | |
| } | |
| fxDrive(); | |
| if (t >= ENTRANCE_TOTAL_S) { | |
| if (!fx.isDone() && t < ENTRANCE_TOTAL_S + 0.5) return; | |
| fxForceFinish(); | |
| entranceT0 = null; | |
| entranceDone = true; | |
| grid.material.uniforms.uReveal.value = 1; | |
| for (const m of wallMats) m.uniforms.uReveal.value = 1; | |
| setLocked(false); | |
| entranceFinishedResolve(); | |
| } | |
| } | |
| function playRespawn() { | |
| if (!entranceDone) return; | |
| setLocked(true); | |
| flashReset(); | |
| bind(); | |
| startCameraReset(); | |
| syncRig(); | |
| fx.setProgress(0); | |
| propsHide(); | |
| respawn = { t0: performance.now(), scanCued: false }; | |
| } | |
| function driveRespawn() { | |
| if (!respawn) return; | |
| const u = (performance.now() - respawn.t0) / 1000 / CAM_RESET_S; | |
| if (!respawn.scanCued && u >= RESPAWN_SCAN_AT) { | |
| respawn.scanCued = true; | |
| fxStart(); | |
| } | |
| if (respawn.scanCued) fxDrive(); | |
| if (respawn.scanCued && fx.isDone()) { | |
| respawn = null; | |
| setLocked(false); | |
| } | |
| } | |
| function drive() { | |
| driveEntrance(); | |
| driveRespawn(); | |
| driveProps(); | |
| } | |
| function setReveal(floor, wall = floor) { | |
| entranceT0 = null; | |
| grid.material.uniforms.uReveal.value = floor; | |
| for (const m of wallMats) m.uniforms.uReveal.value = wall; | |
| } | |
| function setFx(p) { | |
| entranceT0 = null; | |
| respawn = null; | |
| bind(); | |
| if (p >= 1) { | |
| fx.start(); | |
| fxForceFinish(); | |
| propsFinish(); | |
| setLocked(false); | |
| entranceFinishedResolve(); | |
| } else { | |
| fx.setProgress(p); | |
| propsSetProgress(p); | |
| } | |
| } | |
| // Register a decorative prop's wireframe FX (already init'd on its own | |
| // root, hidden). It materializes delayS after each duck scan cue. | |
| function addPropFx(propFx, delayS = 0) { | |
| props.push({ fx: propFx, delayS, at: null, started: false, prev: null }); | |
| } | |
| // Boot hidden: clip parked below the feet from the first frame. | |
| bind(); | |
| return { | |
| CAM_RESET_S, | |
| startEntrance, | |
| playRespawn, | |
| drive, | |
| bind, | |
| setReveal, | |
| setFx, | |
| addPropFx, | |
| entranceFinished, | |
| get entranceDone() { return entranceDone; }, | |
| get respawnActive() { return respawn !== null; }, | |
| }; | |
| } | |