Skip to content

Add elapsed window anchor (run-relative distortions)#6

Merged
boatmeme merged 1 commit into
mainfrom
elapsed-anchor
Jun 28, 2026
Merged

Add elapsed window anchor (run-relative distortions)#6
boatmeme merged 1 commit into
mainfrom
elapsed-anchor

Conversation

@boatmeme

Copy link
Copy Markdown
Owner

What & why

Generalizes where a distortion is anchored. Until now a window was always a wall-clock
time-of-day; this adds a second kind — elapsed — that pins a window to the run's own
t = 0 (the instant the Clock is constructed), measured in real elapsed time. This is the
engine seam the future Stopwatch/Countdown facades sit on (issue #4, step 2).

The shape of the change

Step 1 already made Clock consume windows through resolveAt(anchorMs), so the offset
integrator needs no changes
— a new anchor kind is just another implementation of the same
tiny contract:

  • DistortionWindow (new) — the resolveAt + clone contract; ResolvedWindow moves here.
  • TimeWindow — now implements DistortionWindow; time-of-day logic untouched.
  • ElapsedWindow (new) — resolveAt(anchor) = { anchor + startOffset, anchor + endOffset },
    in reference (real) time.
  • RelativeTimeDistortion — first constructor arg generalized to
    DistortionAnchor = ClockTimeDescriptor | { elapsed: Duration }, branching on
    'elapsed' in anchor. Also drops the unused startClockTime field, decouples the
    getElapsedTimeInMillis default length to referenceDurationInMillis, and extracts a
    durationToMillis helper.

Design note

Elapsed windows are positioned on real elapsed time (only the display warps). This keeps
the integrator unchanged and makes "a countdown still hits zero on the real deadline" emerge for
free — placing windows on the warped timeline would make a window's bounds depend on the offset
it itself produces.

Backward compatibility

new ConstantTimeDilation({ hour: 1 }, { hours: 3 }, { hours: 6 }) is unchanged — a bare
descriptor still means time-of-day. The only addition is accepting { elapsed } in that slot.
Public runtime exports are unchanged (the DistortionAnchor type is exported;
ElapsedWindow/DistortionWindow stay internal).

Tests

  • ElapsedWindow unit tests (additive resolution, independent clone).
  • Clock-level elapsed-anchor tests: run-relative regardless of calendar date,
    polling-frequency independence, and a dilation repaid by a later compression netting to zero.
  • Distortion-construction coverage ({ elapsed }ElapsedWindow, bare descriptor →
    TimeWindow).
  • Full suite green — 56 tests, tsc --noEmit and eslint clean.

Not in this PR

Stopwatch/Countdown facades (next step), the absolute anchor + multi-day spans (step 4),
daily recurrence, and eased distortions. No version bump (0.2.0 isn't shipped yet).

Refs #4

🤖 Generated with Claude Code

Generalize where a distortion is anchored. Until now a window was always
a wall-clock time-of-day; this adds a second kind, `elapsed`, that pins a
window to the run's own t=0 (the instant the Clock is constructed),
measured in real elapsed time. It is the engine seam the future
Stopwatch/Countdown facades will sit on.

Because step 1 already made the Clock consume windows through
resolveAt(anchorMs), the integrator needs no changes -- a new anchor kind
is just another implementation of the same contract:

- Extract that contract into DistortionWindow (resolveAt + clone);
  ResolvedWindow moves here from TimeWindow.
- TimeWindow now implements DistortionWindow (time-of-day logic unchanged).
- Add ElapsedWindow: resolveAt(anchor) = { anchor + startOffset,
  anchor + endOffset }, in reference (real) time.
- Generalize RelativeTimeDistortion's first constructor argument to
  DistortionAnchor = ClockTimeDescriptor | { elapsed: Duration },
  branching on `'elapsed' in anchor`. A bare descriptor still means
  time-of-day, so all existing call sites are unchanged. Drops the unused
  startClockTime field, decouples the getElapsedTimeInMillis default
  length to referenceDurationInMillis, and extracts a durationToMillis
  helper.
- Export the DistortionAnchor type; ElapsedWindow/DistortionWindow stay
  internal.

Elapsed windows are positioned on real elapsed time (only the display is
warped), which keeps the integrator unchanged and makes "a countdown
still hits zero on the real deadline" fall out naturally.

Adds ElapsedWindow and Clock-level elapsed-anchor tests (run-relative
regardless of calendar date, polling-frequency independence, dilation
repaid by a later compression nets to zero) plus distortion-construction
coverage. Full suite green (56 tests), tsc and eslint clean. README API
note on the { elapsed } form. No facades yet; no version bump.

Refs #4

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@boatmeme boatmeme merged commit f2ecf8f into main Jun 28, 2026
4 checks passed
@boatmeme boatmeme deleted the elapsed-anchor branch June 28, 2026 04:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generalize the distortion window anchor (unlocks multi-day, stopwatch, and countdown modes)

1 participant