Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 34 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Sim-specific context for AI assistants. General SceneryStack guidance: [OpenPhys

## Project

A four-screen simulation of flat-spacetime special relativity: **Light Clock**, **Spacetime
Diagram**, **Twin Paradox**, **Relativistic Doppler**. Original work, not a PhET or NAAP port.
Forked from `SceneryStackTemplate` on 31 Jul 2026.
A five-screen simulation of flat-spacetime special relativity: **Light Clock**, **Spacetime
Diagram**, **Length Contraction**, **Twin Paradox**, **Relativistic Doppler**. Original work, not a
PhET or NAAP port. Forked from `SceneryStackTemplate` on 31 Jul 2026.

Read [`doc/model.md`](doc/model.md) before changing anything physical, and
[`doc/implementation-notes.md`](doc/implementation-notes.md) before changing anything structural.
Expand All @@ -33,8 +33,9 @@ Read [`doc/model.md`](doc/model.md) before changing anything physical, and
| `src/common/view/controlHelpers.ts` | `createNumberControl` / `createCheckbox` / `createReadoutRow` — the controls every screen shares |
| `src/common/TimeModel.ts` | Composable clock: play/pause, speed, `scaledDt`, step forward/back |
| `src/SpecialRelativityColors.ts` | `ProfileColorProperty` table **and the sim's colour language** — read its header before adding a colour |
| `src/SpecialRelativityConstants.ts` | Grouped `as const` blocks (`DIAGRAM`, `EVENT`, `LIGHT_CLOCK`, `TWIN`, `DOPPLER`, `FONTS`) |
| `src/SpecialRelativityConstants.ts` | Grouped `as const` blocks (`DIAGRAM`, `EVENT`, `LIGHT_CLOCK`, `LADDER_BARN`, `TWIN`, `DOPPLER`, `FONTS`) |
| `src/light-clock/model/lightClockGeometry.ts` | Photon height, tick counts, the zigzag trail, the light-travel triangle |
| `src/length-contraction/model/ladderBarnGeometry.ts` | Contracted lengths, both frames' snapshots, the two door-slam events, the fitting verdicts, world sheets, and the simultaneity slices in lab coordinates |
| `src/twin-paradox/model/twinJourney.ts` | Both worldlines, proper times, the simultaneity jump, the pulses the twins exchange |
| `src/relativistic-doppler/model/dopplerGeometry.ts` | Retarded emission solve (for an arbitrary observer position), received signal, wavefronts, beaming lobe |

Expand Down Expand Up @@ -72,6 +73,20 @@ Read [`doc/model.md`](doc/model.md) before changing anything physical, and
exactly that instant, and `β·t_wrap` rounds onto either side of the modulo — the answer can flip to
the far end of the rail. Use `traverseStartPosition()`, which returns the rail end exactly. This was
a real bug in `photonTrail`, caught by the light-clock triangle's structural test.
- **The Length Contraction screen's clock is one number read by two frames.** `sceneTimeProperty` is
barn time `ct` or ladder time `ct′` depending on the toggle. This is legitimate only because both
frames' clocks are zeroed on the same event — the ladder's centre passing the barn's centre — which
is the one instant they can agree to label. Do not add a second clock.
- **The Length Contraction diagram is always in barn-frame coordinates**, and the frame toggle changes
exactly one thing on it: the tilt of the simultaneity slice. Its `betaProperty` is therefore a
derived 0-or-β, not the model's β. Switching the diagram's coordinates with the toggle would destroy
the screen's point, which is that both frames are describing the same picture.
- **That screen has no scrubber, on purpose.** At high β in the ladder frame the window is set by the
slams (γβB apart), not by the fly-past, so a fixed-range slider would have had a few percent of
useful travel. The two "go to slam" buttons replace it and teach better: in the barn frame they land
on the same instant.
- **Its β is capped at 0.95, not the sim-wide 0.99**, and floored at 0.1. Documented in
`LADDER_BARN`; both bounds are about the animation window, not the arithmetic.
- **The Doppler screen uses the retarded emission event**, not the source's current position. That is
what makes the transverse redshift come out at exactly γ — from wherever the observer is standing.
- **Beaming is D⁴** (bolometric flux). D³ and D² are also correct, for other measured quantities; the
Expand All @@ -96,12 +111,13 @@ Full convention: [Baton/ACCESSIBILITY.md](https://github.com/OpenPhysics/Baton/b

## Testing

`npm test` — Vitest, `happy-dom`, `--expose-gc`. 129 tests across six files.
`npm test` — Vitest, `happy-dom`, `--expose-gc`. 151 tests across seven files.

| Path | Purpose |
|---|---|
| `tests/lorentz.test.ts` | Kinematics: γ, boosts, invariance, causal structure, velocity addition, Doppler, aberration, beaming |
| `tests/lightClockGeometry.test.ts` | Photon path, tick counts, and the independent "the photon travels at c" check |
| `tests/ladderBarnGeometry.test.ts` | Contraction by γ, both frames' fitting verdicts re-derived by sweeping the drawn snapshots, the slams' spacelike separation and invariant interval, and the check that no door shuts through the ladder |
| `tests/twinJourney.test.ts` | Proper times and the Earth-time accounting identity at the turn |
| `tests/dopplerGeometry.test.ts` | The retarded solve, the three Doppler limits, wavefronts, the beaming lobe |
| `tests/TimeModel.test.ts` | The shared clock |
Expand All @@ -125,8 +141,17 @@ Query parameters: `?initialBeta=0.8`, `?showRapidity=true`, `?shadeLightCone=tru

After `npm run build`, the sim is installable offline via Workbox (`dist/manifest.webmanifest`).

## Ideas for a fifth screen
## Ideas for a sixth screen

Length contraction is the one standard topic this sim deliberately avoids — every screen is arranged
so that it does not enter. A "ladder and barn" screen would reuse `MinkowskiDiagramNode` and
`lorentz.ts` almost unchanged.
The four topics still untouched, roughly in order of how much they would reuse:

- **Velocity addition.** `velocityAddition()` already exists in `lorentz.ts` and is unit-tested but is
not on screen anywhere. A rocket firing a probe forward, with the two boosts composing on a rapidity
scale that *does* add, would need little more than the existing diagram.
- **Relativity of simultaneity as a train-and-lightning screen.** Cheaper than it sounds — it is the
Length Contraction screen's machinery with the two events on a moving object rather than a fixed one.
- **Momentum and energy.** The one genuinely new module: E = γm, p = γmβ, and E² − p² = m² as a fourth
invariant hyperbola to sit beside the one the Spacetime Diagram screen already draws.
- **Reciprocity of time dilation.** The classic misconception the sim does not yet address head-on: a
frame toggle on the Light Clock screen, so that each clock in turn is the one at rest and the *other*
is the one running slow. The Length Contraction screen's frame selector is the pattern to copy.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Special Relativity

An interactive simulation of flat-spacetime relativity — a moving light clock, a live Minkowski
diagram, the twin paradox, and the relativistic Doppler effect — built with
diagram, the ladder-and-barn paradox, the twin paradox, and the relativistic Doppler effect — built
with
[SceneryStack](https://scenerystack.org/), Vite 8, TypeScript 6, and Biome 2.

## Features
Expand All @@ -11,6 +12,10 @@ diagram, the twin paradox, and the relativistic Doppler effect — built with
- **Spacetime Diagram** — drag two events on a Minkowski diagram while a velocity slider shears the
primed axes live; light cone, lines of simultaneity, invariant hyperbolas, and a readout that
contrasts the invariant interval with the frame-dependent order of events
- **Length Contraction** — a ladder flying through a barn whose two doors slam together, watched
from the barn's frame and then from the ladder's; the same two slams, one pair of events, and two
frames that answer "did it fit?" differently because they slice spacetime into "nows" at
different angles
- **Twin Paradox** — place the turnaround event and watch both worldlines, both clocks, and the
jump in the traveller's "now" that resolves the paradox
- **Relativistic Doppler** — a source flying past an observer, with wavefronts, the received colour
Expand All @@ -19,7 +24,7 @@ diagram, the twin paradox, and the relativistic Doppler effect — built with
- English, Spanish, and French localization via `StringManager`
- Full keyboard access, screen-reader summaries, and default/projector colour profiles
- Progressive Web App (installable, offline-capable)
- 100 unit tests over the pure physics modules, plus a memory-leak suite
- 151 unit tests over the pure physics modules, plus a memory-leak suite

## Quick Start

Expand Down
40 changes: 37 additions & 3 deletions doc/implementation-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ src/
controlHelpers.ts, chartUtils.ts
light-clock/ model/{LightClockModel,lightClockGeometry} view/…
spacetime/ model/SpacetimeDiagramModel view/…
length-contraction/ model/{LengthContractionModel,ladderBarnGeometry} view/…
twin-paradox/ model/{TwinParadoxModel,twinJourney} view/…
relativistic-doppler/ model/{RelativisticDopplerModel,dopplerGeometry} view/…
tests/ one file per pure module, plus memory-leak
Expand All @@ -36,11 +37,12 @@ tests/ one file per pure module, plus memory-leak

### Pure functional physics, Property layers on top

`lorentz.ts`, `lightClockGeometry.ts`, `twinJourney.ts` and `dopplerGeometry.ts` are plain functions
`lorentz.ts`, `lightClockGeometry.ts`, `ladderBarnGeometry.ts`, `twinJourney.ts` and
`dopplerGeometry.ts` are plain functions
of plain numbers and `Vector2`s. They import from `scenerystack/dot` and nothing else — no axon, no
scenery. Everything reactive lives in the model classes that wrap them.

This is what makes the physics testable without SceneryStack, and it is where all 100 unit tests
This is what makes the physics testable without SceneryStack, and it is where all 151 unit tests
point. It follows `CarnotHeatEngine/src/common/model/carnotCycleGeometry.ts`.

### Everything animated is a closed form of elapsed time
Expand Down Expand Up @@ -138,6 +140,38 @@ mouse would never see them:
Both default to **zero** in the pure functions, so the tests check exact behaviour; only the model
layer passes a positive value. Do not push the tolerance into `lorentz.ts`.

### The Length Contraction screen has one clock, read by two frames

`LengthContractionModel.sceneTimeProperty` is a single number, interpreted as barn time `ct` or as
ladder time `ct′` depending on which frame is selected. That is not a shortcut. Both frames' clocks
are zeroed on one event — the ladder's centre passing the barn's centre — so there is exactly one
instant they can label the same, and it is the one the clock is zeroed on. Flipping the frame toggle
without touching the clock is then a meaningful operation, and it is the operation the screen exists
to offer: the number stays put and the scene rearranges itself around it.

The clock wraps rather than scrubs. Its window, `sceneHalfWindow()`, is derived from the setup and
the frame, and at high β in the ladder frame it is set by the *slams* (γβB apart) rather than by the
fly-past — so a fixed-range slider would have had a useful travel of a few percent of its track. Two
push buttons take the clock straight to each slam instead, which is also the better teaching control:
in the barn frame both buttons land on the same instant, and that is the whole content of "the doors
are on one switch".

`advance()` uses a modulo rather than a comparison against the ends, so a large `dt` after a
background tab regains focus lands in the right place instead of skipping the window.

### The Length Contraction diagram never changes frames

The stage is drawn in the selected frame; the spacetime diagram is always in barn-frame coordinates,
and the toggle changes exactly one thing on it — the tilt of the simultaneity slice. `betaProperty`
for the shear is therefore *not* the model's β but a DerivedProperty that is β in the ladder frame and
0 in the barn frame, because in the barn frame the primed mesh would be the unprimed mesh already
drawn.

That split is the argument the screen makes, and it is why `ladderSliceInLab()` returns **barn-frame**
events for a measurement taken in either frame: a length is two ends at one instant, and the ladder
frame's pair of ends lands on the diagram as a tilted segment. Its being shorter against the barn's
upright strip is the disagreement drawn rather than asserted.

### The Twin Paradox screen has no `SpecialRelativityModel`

Its β is *derived* from the turn's position, not chosen. A trip is specified by where and when you
Expand Down Expand Up @@ -202,7 +236,7 @@ is more than one flat list keeps legible. This is a documented variation on the
## Testing

`npm test` runs Vitest over `tests/`, environment `happy-dom`, with `--expose-gc` for the memory-leak
suite. 129 tests across six files.
suite. 151 tests across seven files.

The house style is three layers per physics module:

Expand Down
Loading
Loading