1const asNumber = value => Number.isFinite(value) ? value : 0;23export function blockedPresentation(blocked, at, durationMs, reducedMotion, frameMs) {4 return {5 blocked: {6 ...blocked,7 start: at,8 duration: durationMs,9 outlineUntil: at + (reducedMotion ? frameMs : durationMs),10 },11 shake: reducedMotion ? null : {12 start: at,13 duration: durationMs,14 amplitude: blocked.reason === 'bounds' ? 1.5 : 2.5,15 },16 };17}1819export function automatonTransition(system, priorPhase, at, startDelay, durationMs, undo) {20 return {21 id: system.id,22 fromPhase: priorPhase,23 toPhase: system.phase,24 start: at + (undo ? 0 : startDelay),25 duration: durationMs,26 wrapped: system.phase === 0 && priorPhase === system.pattern?.length - 1,27 };28}2930export function destructionEffect(destruction, at, durationMs) {31 const x = asNumber(destruction.at?.x ?? destruction.at?.[0]);32 const y = asNumber(destruction.at?.y ?? destruction.at?.[1]);33 const type = destruction.cause === 'sink' ? 'sink' : 'defeat';34 const role = destruction.cause === 'sink' ? 'SINK' : 'DEFEAT';35 return {36 type,37 text: destruction.noun || destruction.text || '',38 x,39 y,40 cell: { x, y },41 color: role,42 role,43 start: at,44 t0: at,45 duration: durationMs,46 };47}4849export function transformEffect(transform, entity, at, durationMs) {50 return {51 type: 'transform',52 text: transform.from || '',53 cell: { x: entity.x, y: entity.y },54 color: 'fg',55 role: 'fg',56 start: at,57 t0: at,58 duration: durationMs,59 };60}