mrly.net

Load-bearing comments

Code carries none. Anything non-obvious is explained here instead.

`apps/web/src/gpu.ts` (PROJECTOR)

The rAF loop tweens shader uniforms between the last two kernel keyframes, lagging one beat behind so interpolation is exact - no prediction, no correction pops. The `TWEENS` table classifies uniform indices per program: `linear` lerps, `angle` lerps by shortest path around TAU, `viewport` lerps centers linearly and spans in log space (fractal zoom compounds multiplicatively, so a linear span lerp would stutter). `leaps` snaps instead of tweening when a keyframe jumps (span ratio over 8x or the center leaving the old view) - that is a `begin()` reset, not motion. Tween duration is the observed keyframe gap clamped to 125-500ms, so a lagging kernel stretches gracefully. Reduced motion clears every tween and the kernel's honest 8/s stepping shows. Untabled programs (textured shaders) never tween.

The `mesh` program is a second draw path: vertex buffers instead of a fullscreen triangle, its own depth texture per canvas, and geometry fetched from wasm only when `shade.mesh` (the signature) changes, since the buffer itself never rides the per-frame uniforms. Alpha (uniform 19) picks opaque (one pass, depth write) or glass (two passes, back then front by `facing`, depth test only) - there is no per-triangle sort on the GPU, so glass correctness relies on the shapes being convex.

`apps/web/src/journal.ts` (RUNS)

Only beat-flagged calls merge, and only verbs in `RUNS`; the caps mirror each verb's kernel clamp (64 for the physics sims, 1024 elsewhere), so a merged row always replays whole. Merging rewrites the tail row's `{n}` and `now` in place and persists, so a crash loses at most one beat. Any other verb lands after the run row, preserving order and outcome. The tick verbs (clock, font, memory, timer) take no `n` and stay unmerged.

`pkgs/mrlyrs/mrlyos/src/kernel/os.rs` (RING)

The display ring merges a call into the tail entry when the verb matches and both args are the n-shape ({} or {n}), so a beat run reads as one "verb n" row in the log app. Display only - the replay journal has its own merge rule in apps/web/src/journal.ts, which must stay outcome-exact.

`pkgs/mrlyrs/mrlyos/src/kernel/app.rs` (CAPTURE)

In gpu render mode mandelbrot/julia skip the CPU raster and emit an empty `frame` (real width/height, no rows) - the shell reads only `shade` then. solids/three instead raster a small real 96 frame in gpu mode (the `detail` setting sizes the cpu mode), so replay stays whole. `sys.shot` goes through `App::capture()`, which these apps override to raster at `detail` on demand, so shots stay bit-identical in both modes.

`pkgs/mrlyrs/mrlyui/src/shaders/mesh.wgsl` (MESH)

The vertex stages are float ports of `Camera::matrix()`/`view()` and `beam()` from camera.rs, which stays the tested spec - its LUT-quantized i64 angles cannot serve smooth steer, so the shader takes radians and rebuilds the matrix per vertex. Geometry rides one packed Float32Array: `[tri floats, line floats, tris..., lines...]`; a tri vertex is pos3+normal3, a line vertex pos3+spin flag+rgba, flag 1 spins with the model, 0 is world furniture (axes, grid). The shade's `mesh` signature names the buffer; gpu.ts refetches geometry only when it changes.

`apps/web/src/render/nodes.ts` (CANVAS)

`Canvas` patches skip on a committed signature (the `Cells` pattern) built from render mode, darkmode, shade uniforms, palette and rows; theme's accent case clears it because the alpha fill path reads the CSS color. `strip` playback paints intermediate frames across one 125ms beat window on rAF; reduced motion paints only the final frame.

`pkgs/mrlyrs/mrlymath/src/physics/waves.rs`

The `step` leapfrog update is `n = 2c - p + c2lap - damp(c-p)`, with neighbor values zeroed across reflective walls and walls themselves held at

  1. Sources inject additively through a baked Gaussian stamp, suppressed

inside walls, at amplitude `amp envelope[age] SINE[phase]`. The whole step path is `+ - * /` only, no libm, to stay parity-clean with the GPU shader.

`pkgs/mrlyrs/mrlymath/src/physics/lasers.rs`

Ray directions are integer indices into the baked `trig::SINE` ring, not radians. `omega` (spin rate) and `spread` are carried as index quantities so the step/trace path never calls `cos`/`sin` - only `radians_to_index` (an authoring-time helper, off the step path) converts real angles in.

`pkgs/mrlyrs/mrlymath/src/physics/rng.rs`

`Rng` is a hand-rolled SplitMix64, not the `rand` crate, so the exact bit sequence is fixed in source and reproduces identically across every backend that mirrors this module (needed for `examples/physics_parity.rs`).

`pkgs/mrlyrs/mrlymath/src/physics/waves_luts.rs`

Generated by `data/physics/gen_waves.py`. Do not hand-edit; regenerate with the script if the envelope/stamp formulas change.

`apps/web/src/render/wallpaper.ts`

The pattern tile is a data-URI SVG on `body`'s `background-image`, so the glass `backdrop-filter` blurs it for free (body paints behind everything - no extra DOM, no z-index). Data-URI SVGs cannot load webfonts, so the tile is emoji-only (system font; the `emoji` and mrly-font settings cannot reach it) and geometry derives from `seed` via a Park-Miller LCG (`seed + 1` because the multiplier needs a nonzero state). The accent tint is read with `getComputedStyle`, which substitutes the `var()` chain to a real hex - a literal color is required inside the SVG.

`apps/web/styles/boxes.css` (GLASS)

Glass selectors are wrapped in `:where()` to pin specificity at one class: they must beat the base `.mrly-box`/`.border-box` rules (same specificity, later in the sheet) yet lose to the two-class hover/active inversions and `.cell-box > .mrly-box`. The reduced-transparency block restores solid values and must stay below the glass rules for the same reason. Cards paint through the `--tint` var (set inline by `patch`/`repaint`), never inline `background-color`, or glass could not translate the tint to `color-mix`.

`pkgs/mrlyrs/mrlyos/build.rs`

MRLY_VERSION hashes every .rs under `pkgs/mrlyrs/` (the `..` walk), not just mrlyos: the web app invalidates persisted journals on version change, so the stamp must move when any crate's behavior can. With MRLY_RELEASE set the hash is dropped for a stable version string.

`testkit` features (mrlycore, mrlyos)

`mrlyos::kernel::testkit` and `mrlycore::state::guard` are gated `any(test, feature = "testkit")` because cfg(test) items are invisible across crate boundaries: downstream crates enable the feature in their dev-dependencies to reach them from tests.

`pkgs/mrlyrs/mrlyos/src/kernel/goose.rs` (GOOSE)

Verb args are type hints, not legal moves, so the goose synthesizes: enums split on `|`, `int a..b` is inclusive, bare ints roll 0..15 (except `seed` keys, which roll wide), `square` builds chess notation, strings draw from a small word list. A verb is playable when a dry run against a cloned rng fills every hint - the clone keeps the real stream unmoved, so filtering never disturbs determinism. `*.reset` is excluded from the pool: games drop their play verbs when over, so an empty pool IS game over and triggers the reset directly; PATIENCE consecutive all-miss steps (chess-like odds) do the same.

`apps/web/src/shell/mount.ts` (ATTRACT)

Games and puzzles idle under a live goose (`lure`/`shoo`/`wake`): the wasm Handle caches one Goose per seed, so a 250 ms interval replaying `goose(seed)` continues one playthrough, and each visit reseeds from the clock. `wake` resets before forwarding the waking input, so the first keypress both starts and plays. Beats never wake; `calm` skips the interval but keeps the start affordance.