From ec8d25a3512528dcf8715f0b56cc84dd6a7c12bd Mon Sep 17 00:00:00 2001
From: th
Date: Mon, 20 Apr 2026 21:10:18 +0200
Subject: [PATCH] macro-clips slice 6: export runtime fires script frames
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- exportPlayer engine refactored from elapsed-since-start to an explicit
currentT / lastNow / paused model so actions can jump, pause, and
resume cleanly
- every frame: advance currentT by dt, fire script frames whose time
sits in (prev, next], sorted; on loop wrap, fire frames up to rangeOut
then snap to rangeIn without firing across the wrap
- evalAction mirrors src/lib/actions.ts (play/pause/rewindToLoop/
jumpTo/jumpToFrame/jumpToMarker/setButtonState/setButtonDisabled/
script); marker map built from scene.markers; unknown markers warn
via window.console
- RUNTIME_VERSION bumped to '3' so old cached engines don't kick in
Server render.html (PNG-sequence path) stays untouched — scripted
interactivity is a runtime-only concern, the non-interactive server
export should not bake it in.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
src/lib/exportPlayer.ts | 84 ++++++++++++++++++++++++++++++++++++-----
1 file changed, 74 insertions(+), 10 deletions(-)
diff --git a/src/lib/exportPlayer.ts b/src/lib/exportPlayer.ts
index 984cb54..d9405fe 100644
--- a/src/lib/exportPlayer.ts
+++ b/src/lib/exportPlayer.ts
@@ -1,6 +1,6 @@
import type { Scene } from '@/types/scene'
-const RUNTIME_VERSION = '2'
+const RUNTIME_VERSION = '3'
const ENGINE_SOURCE = `(function(){
if (window.AnimatrPlayer && window.AnimatrPlayer.__v === '${RUNTIME_VERSION}') return;
@@ -189,16 +189,80 @@ const ENGINE_SOURCE = `(function(){
}
}
var rangeLen=Math.max(0, rangeOut-rangeIn);
+ var runtime={
+ fps: scene.fps||60,
+ rangeIn: rangeIn,
+ duration: scene.duration,
+ markers: {},
+ jumpTo: null,
+ forcePause: false,
+ forcePlay: false,
+ buttonStates: {},
+ buttonDisabled: {},
+ warn: function(m){ if(window.console)window.console.warn('[animatr]',m); }
+ };
+ (scene.markers||[]).forEach(function(mk){ runtime.markers[mk.name]=mk.time; });
+ function evalAction(act){
+ switch(act.type){
+ case 'play': runtime.forcePlay=true; runtime.forcePause=false; return;
+ case 'pause': runtime.forcePause=true; runtime.forcePlay=false; return;
+ case 'rewindToLoop': runtime.jumpTo=runtime.rangeIn; return;
+ case 'jumpTo': runtime.jumpTo=Math.max(0,Math.min(runtime.duration,act.time)); return;
+ case 'jumpToFrame': runtime.jumpTo=Math.max(0,Math.min(runtime.duration,act.frame/runtime.fps)); return;
+ case 'jumpToMarker': {
+ var mt=runtime.markers[act.name];
+ if(mt==null){ runtime.warn('Unknown marker: '+act.name); return; }
+ runtime.jumpTo=Math.max(0,Math.min(runtime.duration,mt));
+ return;
+ }
+ case 'setButtonState': runtime.buttonStates[act.instanceId]=act.state; return;
+ case 'setButtonDisabled': runtime.buttonDisabled[act.instanceId]=act.disabled; return;
+ case 'script': runtime.warn('Script DSL not implemented'); return;
+ }
+ }
+ function fireFrames(prev, next){
+ if(next<=prev) return;
+ var frames=scene.scriptFrames||[];
+ var toFire=[];
+ for(var ffi=0; ffiprev && fr.time<=next) toFire.push(fr);
+ }
+ toFire.sort(function(a,b){return a.time-b.time;});
+ for(var ffj=0; ffj=rangeLen) t=rangeOut;
- else t=rangeIn+elapsed;
- draw(t);
- syncBgVideo(t);
+ if(rangeLen<=0){
+ draw(currentT);
+ syncBgVideo(currentT);
+ requestAnimationFrame(frame);
+ return;
+ }
+ if(!lastNow) lastNow=now;
+ var dt=(now-lastNow)/1000;
+ lastNow=now;
+ if(!paused){
+ var prev=currentT;
+ var nextT=currentT+dt;
+ if(nextT>=rangeOut){
+ fireFrames(prev, rangeOut);
+ currentT = loop ? rangeIn : rangeOut;
+ } else {
+ currentT=nextT;
+ fireFrames(prev, currentT);
+ }
+ if(runtime.jumpTo!=null){ currentT=runtime.jumpTo; runtime.jumpTo=null; }
+ if(runtime.forcePause){ paused=true; runtime.forcePause=false; }
+ if(runtime.forcePlay){ paused=false; runtime.forcePlay=false; }
+ }
+ draw(currentT);
+ syncBgVideo(currentT);
requestAnimationFrame(frame);
}
function tracePolyPath(tctx,sp,ap,offX,offY,closed){
|