@@ -678,6 +678,7 @@ var Sequence = Sequence || (() => {
678678
679679 const validateEasingExpr = ( expr ) => {
680680 if ( ! expr || ! expr . trim ( ) ) return null ;
681+ if ( expr . trim ( ) === 'continuous' ) return null ; // special keyword — not a curve
681682 const token = parseEasingToken ( expr . trim ( ) ) ;
682683 if ( ! token ) return `Could not parse easing expression: "${ expr } "` ;
683684 if ( ! EASING [ token . name ] ) return `Unknown easing curve: "${ token . name } "` ;
@@ -975,6 +976,7 @@ var Sequence = Sequence || (() => {
975976 return node . apply ( null , [ _ctx ] . concat ( args ) ) ;
976977 } ;
977978 }
979+ if ( node === null || typeof node !== 'object' ) return node ;
978980 var wrapped = { } ;
979981 Object . keys ( node ) . forEach ( function ( k ) {
980982 wrapped [ k ] = _wrapNode ( node [ k ] ) ;
@@ -1490,6 +1492,18 @@ var Sequence = Sequence || (() => {
14901492 description : 'Transparent (no tint)' ,
14911493 } ) ;
14921494
1495+ // ── Math constants ────────────────────────────────────────────────────────
1496+ registerPlaybackConstant ( SCRIPT_NAME , {
1497+ name : 'PI' , namespace : 'core' , type : 'number' ,
1498+ value : Math . PI ,
1499+ description : 'π (3.14159…)' ,
1500+ } ) ;
1501+ registerPlaybackConstant ( SCRIPT_NAME , {
1502+ name : 'TAU' , namespace : 'core' , type : 'number' ,
1503+ value : Math . PI * 2 ,
1504+ description : '2π (6.28318…) — one full rotation in radians.' ,
1505+ } ) ;
1506+
14931507 // Pre-built query strings for handout dropdown buttons
14941508 // Escape a string for use inside a nested Roll20 ?{} query that is itself
14951509 const EASING_QUERY = ( ) => {
@@ -2873,42 +2887,55 @@ var Sequence = Sequence || (() => {
28732887 // Resolve expression if needed to get the target value
28742888 let nextParsed = nextKf . deltas [ attrName ] ;
28752889 if ( nextParsed && nextParsed . expr ) {
2890+ const srcEasing = prevIdx >= 0 && kfs [ prevIdx ] . easings && kfs [ prevIdx ] . easings [ attrName ]
2891+ ? kfs [ prevIdx ] . easings [ attrName ]
2892+ : ( pb . currentEasings [ attrName ] || pb . easing || 'linear' ) ;
2893+ const isContinuous = srcEasing === 'continuous' ;
28762894 const orig = pb . initialState [ attrName ] !== undefined ? pb . initialState [ attrName ] : 0 ;
28772895 const prev = prevAbsState [ attrName ] !== undefined ? prevAbsState [ attrName ] : orig ;
2878- // curr fetched lazily inside evalExpr via reg+obj
28792896 try {
28802897 const val = evalExpr ( nextParsed . expr , orig , prev , undefined ,
28812898 { obj, reg, t : tNorm , cumulative : pb . cumulative || { } } ) ;
2882- nextParsed = nextParsed . mode === 'abs' ? { abs : val }
2899+ let resolved = nextParsed . mode === 'abs' ? { abs : val }
28832900 : nextParsed . mode === 'mul' ? { delta : val }
28842901 : { delta : ( nextParsed . sign || 1 ) * val } ;
2885- // Cache the resolved value so all ticks in this segment agree
2886- if ( ! pb . resolvedExprs ) pb . resolvedExprs = { } ;
2887- const key = `${ nextIdx } :${ attrName } ` ;
2888- if ( ! ( key in pb . resolvedExprs ) ) pb . resolvedExprs [ key ] = nextParsed ;
2889- nextParsed = pb . resolvedExprs [ key ] ;
2902+ if ( isContinuous ) {
2903+ // Continuous: use resolved value directly, no lerp, no cache
2904+ const shadow = makeShadow ( prevAbsState ) ;
2905+ if ( 'abs' in resolved ) reg . set ( shadow , resolved . abs ) ;
2906+ else if ( 'delta' in resolved ) reg . apply ( shadow , resolved . delta ) ;
2907+ interpolated = shadow . _state [ attrName ] ;
2908+ } else {
2909+ // Cache the resolved value so all ticks in this segment agree
2910+ if ( ! pb . resolvedExprs ) pb . resolvedExprs = { } ;
2911+ const key = `${ nextIdx } :${ attrName } ` ;
2912+ if ( ! ( key in pb . resolvedExprs ) ) pb . resolvedExprs [ key ] = resolved ;
2913+ nextParsed = pb . resolvedExprs [ key ] ;
2914+ }
28902915 } catch ( e ) {
28912916 log ( `${ SCRIPT_NAME } : lerp expr error for ${ attrName } : ${ e . message } ` ) ;
28922917 }
28932918 }
2894- // Apply resolved delta to prevState shadow to get absolute nextVal
2895- const shadow = makeShadow ( prevAbsState ) ;
2896- if ( nextParsed && 'abs' in nextParsed ) reg . set ( shadow , nextParsed . abs ) ;
2897- else if ( nextParsed && 'delta' in nextParsed ) reg . apply ( shadow , nextParsed . delta ) ;
2898- const nextVal = shadow . _state [ attrName ] ;
2899- const prevTime = prevIdx >= 0
2900- ? ( pb . resolvedTimes [ prevIdx ] !== undefined ? pb . resolvedTimes [ prevIdx ] : ( typeof kfs [ prevIdx ] . time === 'number' ? kfs [ prevIdx ] . time : 0 ) )
2901- : 0 ;
2902- const nextTime = pb . resolvedTimes [ nextIdx ] !== undefined
2903- ? pb . resolvedTimes [ nextIdx ]
2904- : ( typeof nextKf . time === 'number' ? nextKf . time : prevTime ) ;
2905- const segDur = nextTime - prevTime ;
2906- const segElapsed = t - prevTime ;
2907- const segT = segDur > 0 ? segElapsed / segDur : 1 ;
2908- const srcEasing = prevIdx >= 0 && kfs [ prevIdx ] . easings && kfs [ prevIdx ] . easings [ attrName ]
2909- ? kfs [ prevIdx ] . easings [ attrName ]
2910- : ( pb . currentEasings [ attrName ] || pb . easing || 'linear' ) ;
2911- interpolated = reg . lerp ( prevVal , nextVal , getEasing ( srcEasing ) ( segT ) ) ;
2919+ if ( interpolated === undefined ) {
2920+ // Normal lerp path (non-continuous expressions or non-expression deltas)
2921+ const shadow = makeShadow ( prevAbsState ) ;
2922+ if ( nextParsed && 'abs' in nextParsed ) reg . set ( shadow , nextParsed . abs ) ;
2923+ else if ( nextParsed && 'delta' in nextParsed ) reg . apply ( shadow , nextParsed . delta ) ;
2924+ const nextVal = shadow . _state [ attrName ] ;
2925+ const prevTime = prevIdx >= 0
2926+ ? ( pb . resolvedTimes [ prevIdx ] !== undefined ? pb . resolvedTimes [ prevIdx ] : ( typeof kfs [ prevIdx ] . time === 'number' ? kfs [ prevIdx ] . time : 0 ) )
2927+ : 0 ;
2928+ const nextTime = pb . resolvedTimes [ nextIdx ] !== undefined
2929+ ? pb . resolvedTimes [ nextIdx ]
2930+ : ( typeof nextKf . time === 'number' ? nextKf . time : prevTime ) ;
2931+ const segDur = nextTime - prevTime ;
2932+ const segElapsed = t - prevTime ;
2933+ const segT = segDur > 0 ? segElapsed / segDur : 1 ;
2934+ const lerpEasing = prevIdx >= 0 && kfs [ prevIdx ] . easings && kfs [ prevIdx ] . easings [ attrName ]
2935+ ? kfs [ prevIdx ] . easings [ attrName ]
2936+ : ( pb . currentEasings [ attrName ] || pb . easing || 'linear' ) ;
2937+ interpolated = reg . lerp ( prevVal , nextVal , getEasing ( lerpEasing ) ( segT ) ) ;
2938+ }
29122939 } else {
29132940 interpolated = prevVal ;
29142941 }
0 commit comments