Load-bearing comments
Code carries none. Anything non-obvious is explained here instead.
apps/web/src/gpu.ts (PROJECTOR)
Uniforms never ride the frame JSON: shade carries only the program (plus route/mesh for the mesh path) and the vector comes from wasm as a Float32Array, the same route geometry() takes.
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, the pulled uniform vector, 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 + c2*lap - damp*(c-p), with neighbor values zeroed across reflective walls and walls themselves held at
- 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. Everything that survives a save is integer: WavesConfig is milli, source x/y are milli grid units, phase is step-milli on the 256-step wheel (WHEEL = 256000 per turn) and age is a tick count. Only the field itself, recomputed every step and never saved, stays f32.
pkgs/mrlyrs/mrlymath/src/physics/lasers.rs
Ray directions are step-milli positions on the baked trig::SINE ring (WHEEL = 256000 per turn), not radians. dir, omega_idx and spread_idx all carry that unit, so the step/trace path never calls cos/sin; the app's spread and spin tables are baked step-milli constants rather than radians converted at runtime. Emitter x/y are milli grid units and only ray marching, which ends in pixels, drops to f32.
pkgs/mrlyrs/mrlymusic/src/theory.rs
MILLIHERTZ bakes all 128 midi notes as integer millihertz, equal temperament from A4 = 440000, so freq is a table lookup and cue payloads carry exact integers with no float in the construction path. Cue gain is centi percent. The ear boundary is the only place that divides: synthesis in render.rs and WebAudio in apps/web/src/shell/effects.ts.
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.
pkgs/mrlyrs/mrlycore/src/codec.rs (EGRESS)
The png encoder is egress only - the world stores Grids (Image facts), never png bytes. The zlib stream uses deflate's fixed Huffman table (BTYPE 01) over a greedy hash-chain LZ77 matcher: window 32768, chain capped at 128, first full-length match wins. Fixed beats stored by ~100x on our flat-color faces and even beats the old png crate, because runs of identical pixels collapse into short match symbols; dynamic Huffman (BTYPE 10, per-image code tables) would shave maybe another third off at 3x the code and is not worth owning until a real consumer feels the bytes. The tests carry a tiny fixed-only inflater so round-trips stay proven without any dependency; Python's zlib and macOS sips validated the streams independently when the encoder landed. The huffman writer reverses bit order because deflate packs Huffman codes MSB-first into an LSB-first stream - the one genuinely confusing rule in RFC 1951.
pkgs/mrlyrs/mrlycore/src/chacha.rs (STREAM)
A from-scratch port of the exact stream rand_chacha 0.3 + rand 0.8 produced, so every seeded replay recorded before the port stays bit-identical forever. That contract explains everything odd in the file: from_u64 is rand_core's PCG32 seed expansion; the 64-word buffer with counters advancing four blocks per refill mirrors BlockRng, including next_u64's three-way branch at the buffer edge; word_pos/set_word_pos reproduce ChaCha8Rng's 68-bit word addressing for Rng::pos/seek. The samplers are rand 0.8 verbatim: widening-multiply rejection with the shifted-zone approximation for single draws, the exact-modulus zone for sample_rejection, the f32 cost model picking floyd/inplace/rejection in sample_indices, and the u32 fast path in shuffle. None of it may be "improved" - any deviation changes streams and breaks replay. The pinned tests in the file were generated from the real crates before they were removed; golden app fixtures pin the same contract end to end. state.rs seeds its unseeded default from RandomState (the one entropy source std offers everywhere, wasm included); every product path seeds explicitly, so that default is only a fallback.
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.
apps/git/public/style.css (HERO)
The home hero replays the MRLYPROD merge from apps/jsx/src/lib/frames.ts in pure CSS: letters snap in left to right (opacity keyframes 0.2% apart make the fade binary), hold, then collapse pairwise inward over 72%-92% in four 5% sub-phases, one 6-unit jump per phase, mirroring the frames.ts phase walk. Each letter's final offset is 22-(1+i*6), so the eight letters stack on one 5x5 cell whose union is the X glyph - cargo test -p mrlyssg mrlyprod_union_is_x proves it. The final translate sits on 92%, 100%: drop the 100% and the browser synthesizes it from the base value 0, sliding every letter home. The 87% keyframe switches to steps(3, end) so the last 3-unit phase still jumps exactly one unit per step.
utils/brand.py (MANIFEST)
MANIFEST is the only declaration of what lands in each app's public/. Shared bytes (the text faces, the emoji shards, MrlyFont, the licenses) go to data/cdn/ under content-hashed names and are served from cdn.mrly.net; site-specific bytes (favicon, mark, pngs, the per-site icon subset) stay local, because each site subsets a different set of Material Symbols. fnv must stay bit-identical to pkgs/mrlyssg/src/dist.rs, so a name hashed here and a name hashed by the git projection agree. The icon subset url differs by site on purpose: git's sheet is served raw so it takes /fonts/site.woff2, while web's is bundled by bun, which cannot resolve a root-absolute url and needs ./fonts/icons.woff2.