// What the screen needs to move smoothly between two steps of the rules. The rules move in steps of a game // minute (STEP in sim.ts), a fifth of a second or so at 1x, so the screen draws a little behind them and eases // what moves from where it was one step ago to where it is now. The loop (main.ts) says how far into the next step // it is; the drawing (view.ts) asks for the time and places here. Nothing here changes the game. import { STEP, type Person, type State } from './sim.ts'; import type { Trace } from './watch.ts'; /** How far the screen is into the next step: 0 just after a step, nearly 1 just before the next. */ let lead = 1; /** Where each moving thing was before the last step: a person's place, a scan's hop and how far along it it was. */ const was = new WeakMap(); const hops = new WeakMap(); /** Called by the loop just before each step, to remember where things were. */ export function beforeStep(S: State) { for (const p of S.people) was.set(p, [p.x, p.y]); for (const T of S.watch?.traces ?? []) { was.set(T, [T.p]); hops.set(T, `${T.from}>${T.to}`); } } /** Called by the loop once a frame: the game time owed to the rules, less than a step. */ export function setLead(owed: number) { lead = Math.max(0, Math.min(1, owed / STEP)); } export const leadNow = () => lead; /** The time to draw at: a step behind the rules, eased toward them. What is animated by the clock (streams, pulses) * runs on this, so it moves every frame and never jumps back. */ export const drawTime = (S: State) => S.t - STEP * (1 - lead); /** Draw with the game's clock set to drawTime, and put it back after: whatever the drawing animates by the clock * (streams, pulses, dashes, countdowns) moves every frame, not once a step. Nothing in the drawing changes the * game, so the clock is the only thing it reads that needs easing this way. */ export function drawing(S: State, draw: () => void) { const t = S.t; S.t = drawTime(S); try { draw(); } finally { S.t = t; } } /** A person's place to draw: between where they were a step ago and where they are. One who jumped (came in at the * door, a new game) is drawn where they are. */ export function placeOf(p: Person): [number, number] { const w = was.get(p); if (!w) return [p.x, p.y]; const dx = p.x - w[0], dy = p.y - w[1]; if (dx * dx + dy * dy > JUMP * JUMP) return [p.x, p.y]; return [w[0] + dx * lead, w[1] + dy * lead]; } const JUMP = 160 * STEP * 2; // farther than anyone walks in a step (Dana hurries at 150 a second) /** How far along its link a scan or trace is drawn. On a new link since the last step, it eases from the link's start. */ export function traceAt(T: Trace): number { const w = was.get(T), p = Math.min(1, T.p); if (!w) return p; const from = hops.get(T) === `${T.from}>${T.to}` ? Math.min(1, w[0]) : 0; return from + (p - from) * lead; }