macro-clips slice 6: export runtime fires script frames

- 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) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.7 committed 2026-04-20 21:10:18 +02:00
1 parent 8817a73c3d
commit ec8d25a351
1 file changed
+74 -10
+74 -10
View File
@@ -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; ffi<frames.length; ffi++){
var fr=frames[ffi];
if(fr.time>prev && fr.time<=next) toFire.push(fr);
}
toFire.sort(function(a,b){return a.time-b.time;});
for(var ffj=0; ffj<toFire.length; ffj++){
var acts=toFire[ffj].actions||[];
for(var fai=0; fai<acts.length; fai++) evalAction(acts[fai]);
}
}
var currentT=rangeIn;
var paused=false;
var lastNow=0;
function frame(now){
if(!start)start=now;
var elapsed=(now-start)/1000;
var t=rangeIn;
if(rangeLen<=0){ draw(t); syncBgVideo(t); requestAnimationFrame(frame); return; }
if(loop) t=rangeIn+(elapsed%rangeLen);
else if(elapsed>=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){