Add elapsed window anchor (run-relative distortions)#6
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 ownt = 0(the instant theClockis constructed), measured in real elapsed time. This is theengine seam the future Stopwatch/Countdown facades sit on (issue #4, step 2).
The shape of the change
Step 1 already made
Clockconsume windows throughresolveAt(anchorMs), so the offsetintegrator needs no changes — a new anchor kind is just another implementation of the same
tiny contract:
DistortionWindow(new) — theresolveAt+clonecontract;ResolvedWindowmoves here.TimeWindow— nowimplements DistortionWindow; time-of-day logic untouched.ElapsedWindow(new) —resolveAt(anchor) = { anchor + startOffset, anchor + endOffset },in reference (real) time.
RelativeTimeDistortion— first constructor arg generalized toDistortionAnchor = ClockTimeDescriptor | { elapsed: Duration }, branching on'elapsed' in anchor. Also drops the unusedstartClockTimefield, decouples thegetElapsedTimeInMillisdefault length toreferenceDurationInMillis, and extracts adurationToMillishelper.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 baredescriptor still means time-of-day. The only addition is accepting
{ elapsed }in that slot.Public runtime exports are unchanged (the
DistortionAnchortype is exported;ElapsedWindow/DistortionWindowstay internal).Tests
ElapsedWindowunit tests (additive resolution, independent clone).polling-frequency independence, and a dilation repaid by a later compression netting to zero.
{ elapsed }→ElapsedWindow, bare descriptor →TimeWindow).tsc --noEmitand eslint clean.Not in this PR
Stopwatch/Countdown facades (next step), the
absoluteanchor + multi-day spans (step 4),dailyrecurrence, and eased distortions. No version bump (0.2.0 isn't shipped yet).Refs #4
🤖 Generated with Claude Code