A passive ledger of what shipped on reabal.ai. Newest at the top. Short. Honest. No marketing voice.
This doc is the source for /changelog. Append a new entry when a meaningful change ships. Old versioned rebuild history was removed from the working tree during the Studio OS reset; use git history for archaeology.
2026-06-12 — R1, the lighting round: the bag out, the light layers in, the room gets a voice
the first roadmap round. every authored light now lives in a runtime channel; the lamp finally tells the truth.
- the boxing bag removed (5 code sites — live-props row + pendulum, the nav action, the lmFactor clamp, the live-probe row, the manifest entry) + the room rebaked without it. the kettlebell stays as floor dressing.
- the cabinet blockout proxy bakes (▸panel occlusion-gap law): a massing-accurate stand-in for the R5 cabinet (0.62×0.78×1.78m, the freed bag slot on the west wall, probed against the shipped blend) joins every bake as
live-cabinet-proxy+ a screen quad, so AO/shadow/bounce are right NOW and the R5 mesh swap never invalidates a bake. - the light layers (the my-room-in-3d / at010303 architecture, ▸panel-amended to 8 channels): every authored light LEFT the base bake (the lamp-honesty law generalized) and lives in a per-light influence mask — baked isolated at 16-bit (white sources, the hue belongs to the runtime), gain-normalized per channel at pack (
layer_gainsin the compress receipt →calibinapartment-light-layers.ts), channel-packed RGBA×2 as LOSSLESS webp (lossy webp is YUV 4:2:0 — chroma subsampling would cross-bleed independent channels). runtime compositesuColor*uStrength*maskwith glsl-blendlighten(max, never add) over the dark base BEFORE the 2.45 recovery scale, patched into every lightmapped material viaTHREE.ShaderChunksubstitution (onBeforeCompile sees the TEMPLATE — the include directive is the patch point, not the expanded line; learned the silent-miss way). zero added runtime lights. screen-spill pre-baked for R3, cabinet-glow pre-baked for R5. - the lamp honesty fix shipped: the pool is baked dark; the live channel carries it, damped on the toggle's own curve. lamp off = pool gone (the magenta wash beneath shows, honestly). the pool-persists-when-off bug is dead at the root.
- brighter neon + ambience tuned + FROZEN (leva dev panel + the
__aptLayersknob on :3055): per-channel strengths recover the lighten-vs-sum level and land the neon ask (magenta 2.6, cyan 2.85; the §4 hierarchy held — floor blacks intact). ibl 0.03→0.04. the panel is dev-only by construction; no panel ships. - the audio bus v1: module-singleton graph over the shared AudioContext (rides the / ↔ /window route push) — ambience bed (lowpassed room tone + a near-subliminal machinery trace, both already in the locked library; zero new sourcing), focus-dock stems (desk static / door scanner / the room thins at the glass / everything ducks at the bed), synthesized quiet UI ticks (hover/press/lamp pull-cord — no assets), and the one-time diegetic consent fold ("let the room breathe?") on the existing
audio_prefcontract. - the iamerfan finishing pass, eye-gated A/B: 2 procedural grime overlays (seeded canvas, zero payload — no sourced packs) at multiply 5% + screen 15% + a corner-discipline vignette, DOM-side so the GL post chain stays bloom→AgX.
?grime=0/__aptGrimeflips it live; it dies this session if rey's eye says slop. - render hygiene: one background truth (
WORLD_BOOT_BG— scene bg = host div = GL clear),failIfMajorPerformanceCaveatpreflight before the 19MB GLB streams (software GL gets the DOM landing via[data-apt-nogl]), forced-filter canvas guard. - perf receipts (real chrome, DPR 2): 102 pipeline draw calls (≈88 scene + composer chain; the invisible hit volumes were silently costing 11 draws —
material.visible=falseraycasts free), 97fps, textures 66→68 (+2 masks, +0.84MB payload — net wash vs the bag's textures). payload: 18.8MB GLB + 0.1MB base + 0.84MB masks. - rey's eye verdict applied same session ("still feels a bit dark, barely any ambient or soft lighting; light should come FROM the window and lamps and LEDs; lean dota-painterly; rug + pillow out"): the floor clutter dropped at the bake (the mat+pillows assembly, the stray floor pillow, the rug prop — the bed's own pillows stay); the env became a real cool wash through the porthole (the one ambient path into a sealed room — color [0.35,0.42,0.6] × 0.6, was a 0.0008 whisper); the window rim grew into a true soft key (85W @ 3.2m, was 28W "shape only") riding its own channel; the pools grew softer and warmer (lamp 42W@0.55 / kitchen 34@0.6 / bed 26@0.7); fixture emissives 4.5→5.5 so the LEDs throw; runtime ibl 0.08 + bloom threshold 0.92 / intensity 0.42 so emitters carry the soft halo that reads as actual light. final: 101 calls, 60fps vsynced, 18.2MB GLB + 0.2MB base + 0.89MB masks.
2026-06-12 — the finalization: THE MASTER ROADMAP + the cull + the renames
one roadmap, no more pivots. the dead universes got farewell films, then the axe.
- THE MASTER ROADMAP locked (
docs/plans/2026-06-12-master-roadmap.md, the grill session — fable 5 + ultracode, every fork rey's call). it OWNS sequencing + scope over every other plan. the spine: R1 lighting (bag out, channel-packed light layers, audio bus v1) → R2 sharpness (uv2 recovery, paint dials) → R3 the computer (/os, noindex, standalone-usable; v1 apps: explorer + terminal + Spotify embed + readme — browsable + linkable, never playable in-frame) → R4 screens + the mood-soda vending machine (drinks = light-layer presets, never marketing) → R5 the hand-built arcade cabinet + rapier authored moments → P3/arcadeend-to-end → P4 (gated, unscheduled). P2.5 "finalize the spaceport" ABSORBED into R1+R2. the tier-object slate locked: cabinet=/arcade, specimen jar=/lab, reading slate=/essays, holo-orrery=/sims, corkboard=/about.CONTEXT.mdborn at the repo root — the canonical glossary; the domain language wins on terminology. one new dependency sanctioned:@react-three/rapier2.2.0 (installs at R5, authored moments only). - the cull executed (commit
1ac6056c, −37,937 lines): the cosmos fly-through DELETED (/dev/cosmos,src/features/home/cosmos/, the cosmos-review workflow + everycosmos:*script, the 2026-05-30 cosmos plan doc, the public cosmos assets) and the hades ship DELETED (/dev/hades,src/features/home/ship/, the parked(ship)room routes,lib/ship-mode.ts,scripts/ship-asset/, the hades brain-review scripts, ~12MB of models freed). farewell captures recorded FIRST:docs/reviews/captures/farewell/{farewell-cosmos,farewell-hades}.webm— the R4 monitor-loop placeholder sources. THE SKY SALVAGE: the five tableau components (tau-ceti-star, adrian-world, petrova-arc, star-field, cosmos-comet) +r3f-types.d.tsmoved tosrc/features/world-engine/apartment/sky/as live window deps;public/models/cosmos/adrian.glb+public/audio/cosmos/comet-pass.webmsurvive.models.mjsmoved toscripts/brain-review/(shared by all brain-review scripts; the 2026-08-25 quarterly model audit retargets there). the/loginUI deleted (crypto +/api/auth+canViewExperimentkept; the revert kit now also needsapp/login/*from git history).proxy.tsis a documented pass-through on an unmatchable matcher. orphans graveyard-registered (rewrite-day, friendship-forecast, twelve-year-old) — routes live until the post-P3 content-cull session. - the renames executed (commit
4733a635):/essaysand/simsare the live canonical routes ("the essays", "the sims");/library,/worlds, and/archiveall 301 to them innext.config.ts. internal tier ids (world,library), biome ids, component names, and feature dirs unchanged on purpose — only public paths + user-facing labels moved.scripts/library-language-audit.mjsenforces the new reality in code.
2026-06-12 — the stylized apartment: kill test PASS + THE PAINTED BAKE
photoreal lost on its own pre-registered terms. the room got painted.
- the direction lock (2026-06-11, `a5c5c956`) — the stylized-apartment design doc (
docs/plans/2026-06-11-stylized-apartment-design.md) locked the recipe and pre-registered the kill test, with the desk direction as the only fallback (fires only on a total no at the verdict frame). - the phase-1 kill test PASSED (receipt:
docs/reviews/2026-06-12-stylized-apartment-kill-test.md) — the stylized recipe won at the verdict frame; the desk fallback never fired and is re-frozen. photoreal kit RENDERING retired permanently on signature surfaces; kits survive as geometry sources. - THE PAINTED BAKE (design doc §3b; the
c02e1107..fb56a23aarc): quantize-before-bake synthesized albedo + geometry-derived paint passes (convex-edge highlight, deep-AO grime) + the full moody zhou-dark bake. normals stripped from the shipped GLB; payload collapsed 89.4MB → 19.2MB GLB + 0.45MB webp lightmap. - THE STYLIZED RUNTIME (
85f48d82, + the doctrine ripple): the light strip — no hemisphere fill, no practical pools, ≤1 runtime light (the lamp); the post chain is bloom-in-HDR-then-AgX (bloom runs in linear HDR, AgX tonemaps after — docs that said "AgX-then-bloom" had the order backwards); DPR [1,2]. new pipeline stage:scripts/world-pipeline/stylize-textures.mjs(the quantize/repaint pass, before the bake). - the glitter killed at the root (
287255ec): lightmap padding 10 > margin 4. - the 2026-06-12 reference sweep (
420bb8c5): rey's 32-link reference dump verdict-ed (adopt/prototype/park) into the learning doc; diegetic-room peers folded into reabal.
2026-06-11 — P2.5 round 1: the S+ dressing + the alive layer + the window depth work
- the S+ KB3D dressing: ~20 placements in one bake cycle. every fab placeholder with a kit equivalent swapped (camera, vent, mug, both posters); the desk gained the investigation board + computer stack + laptop; the room gained the ceiling fan, the vending machine (the P3 arcade seed), the punching bag + kettlebell, safe, extinguisher, junction box, magazines, books, bottles. payload went DOWN 90.0 → 89.4MB (kit props reuse shipped trim sheets).
- the alive layer: 8 live props on one shared affordance — hover lifts the prop softly (no HUD), press does a small in-world thing (the laptop wakes, the lamp toggles, the fan spins up, the vending machine surges, the poster glitches, the bag sways, the aquarium pulses, the terminal types a line). reduced-motion + keyboard mirror included. the props are keep_separate bake nodes (new pipeline capability) with unique materials.
- the window depth pass: the near comet now crosses 5.5-7.5m behind the glass, the star shell sits 57-90m deep, the sky is fog-free, and a dielectric glass pane carries a one-shot cube-captured reflection of the lit room over true-black space. yaw-tied eye sway adds real parallax to every pan.
- two pre-existing production bugs fixed: the USD extra-prop lane placed everything in parent-rotated space (the shipped rug was tilted into the floor; the desk lamp was invisible on the kitchen floor), and the window walk-up had strobed since the flip (the intent steering's feedback loop — reverted to the proven M4 walk).
2026-06-11 — P1 apartment built + M4 + THE FLIP: / is the apartment
the homepage stopped flying past planets and became a room. the cosmos didn't die — it moved into the window.
- USD-instancing root-cause fix — KB3D USDs author reusable props (bed/doors/monitors) as USD scene-graph instances; the blender importer default joined the prototypes AT THE ORIGIN and silently lost every authored placement.
support_scene_instancing=Falseon import recovered the real dressed apartment. every future kit bake inherits the fix. - P1 the apartment, built + polished — anchored gaze camera at the kitchen doorway (window LEFT / living wall ACROSS / bed + portal door RIGHT, one ±40° leash sweep); bright lived-in light direction (lightmap demoted to subtle GI at 0.25, IBL fill + warm practical pools as key); the desk converted to a real workstation (terminal/screen/keyboard); portal door closed on the north wall with status lights. rey directed the layout + polish rounds in-session. live tuning knobs, no rebuild:
__aptPools__aptEmis__aptIbl__aptLm+__aptEye/__aptTarget/__aptFov. - M4: the window is a live cosmos viewport —
src/features/world-engine/apartment/apartment-sky.tsximports five cosmos tableau components as SHARED LIVE DEPS: tau-ceti-star, adrian-world, petrova-arc, star-field (fixed-view mode), cosmos-comet. the P5 "sky-stack salvage" step is already banked; these five never get deleted with the cosmos. - window walk-up choreography — magnetic gaze + stance, intent steering, dolly ray in the GazeRig (
apartment-scene.tsx). the dolly-to-glass camera work/windowwill formalize is banked here; click formalization is M5. - THE FLIP (P5 pulled EARLY, rey's explicit order — before P2/P3/P4) —
/is the apartment viaApartmentMountinapp/(ship)/page.tsx(data-apartment-root, sr-only DOM copy, reduced-motion DOM landing with tier links); the apartment also mounts at/dev/apartment. the cosmos fly-through is PARKED at/dev/cosmos(noindex, runnable, NOT deleted — the registry/cleanup session decides its end).foundation-routes.test.tscontract rewritten, 15/15 green. cosmos-review auto-triggers disabled (manual dispatch kept for the parked scene); no review lane gates pushes now — apartment proof runs on the playwright capture lane + the panel scripts + perf receipts. - the MB budget raised (owner order) — rey, 2026-06-11, verbatim: "increase the MB limit — I'm willing to sacrifice loading speed for quality." the multiverse plan's ~30MB world-payload law raises to ~60-80MB per world; the constraint that survives is first-paint-fast (the GLB streams after paint). unlocks the quality rebake: UASTC over ETC1S, 2048 texture resize, material atlasing evaluation (33 mats → ~10).
- M5: click-to-focus shipped on the shared interactable primitive — four foci (the window, the portal door, the desk, the bed) as config rows in
apartment-interactables.tsx: invisible hit volumes + hover labels + authored focus poses; a damped focus FSM layered over the GazeRig (press to dolly in, ESC or a deliberate scroll-back steps out, a micro-leash + the idle drift keep the focused frame breathing); the window press formalizes the M4 dolly-to-glass on the same stance constants. keyboard/SR mirror nav (hidden until focused, WCAG 2.4.7). the enabling host fix: the canvas event system binds todocument.documentElement(eventSource) because#main-content's PageTransition wrapper eats pointer events above the z-0 canvas — events now reach the raycaster by bubbling. activation is pointerdown, not click (r3f click synthesis is unreliable under the drifting gaze). the five cosmos-era homepage contract tests repointed at/dev/cosmos(they'd been red since the flip). - the dressing round (rey's asset drop) — ten props placed and frame-verified: the KB3D apartmentinteriors rug (under the floor pillow), the worn sci-fi folding map folded on the mattress (the hail-mary bunk callback), a pothos money plant + a leaning eye painting + the taza cup + books on the counter, a megascans book on the bed, a snake-skeleton painting on the drawer tower, a vent module + security camera over the portal door. the bake lane grew a real FBX prop pipeline (
extra_props_fbx: import, recenter-to-base, uv-rename-to-room-layer, explicit sidecar material rebuild, textureless grading) plus the anti-guessing placement tools (elevations.pyorthos,probe-mounts.pyraycasts). seven silent traps found + fixed in code (receipt:docs/reviews/2026-06-11-apartment-dressing-round.md). payload 83.2 → 90.0MB (ceiling 95). the retro terminal DROPPED — its textures were never downloaded. KB3D-first doctrine recommended, awaiting rey. - grounding + interaction completeness (the round after P2) — the polish panel's twice-converged "one thing" shipped: a one-frame baked
ContactShadowsplane at deck height grounds the floor pillow, bed, chair, and box (frames=1 — the scene is static; warm-dark tint per the tinted-shadow law). en route it surfaced a real kit bug: five sheets ship alpha-BLEND without depth write (BedCloth, DetailAtlas, PlasticTrimA, TrashAtlas, DecalGraphics — the pillow is BedCloth), so the shadow showed through them as a stain; the material pass now restores depth write on non-glass blend sheets. M5 completeness: the press is acknowledged (the label holds + brightens for a beat as the dolly starts), pressing empty space or the focused object steps back out (lightbox rule), ESC + scroll-back unchanged. - P2: `/window` shipped (v1) — the space view is a real place. the same apartment scene stays resident in the persistent canvas (shared registration object — the route swap is DOM-only, no GLB remount, no black frame);
apartment-window-bridge.tsxbinds the focus state to the URL: the window focus settling on/carries you to/window(the dolly IS the transition), a cold/windowload plays the dolly-to-glass as the arrival, scroll-back or ESC steps out to/. the page borders become a quiet hairline frame; zero site navigation (one whisper-quiet "back to the room"); reduced-motion DOM landing./window+ the apartment label landed inlib/foundation-routes.ts(the/entry still said "the cosmos" — stale since the flip). PHM eggs (flare, centrifuge spin) are the later egg session per the plan. - the quality rebake LANDED (same day) —
p1-apartment.glb19.3MB → 83.2MB. two root causes of the old texture mush: every sheet was resized to 1024, and the UASTC-normals pass was a silent no-op (--slots "{normalTexture}"— single-element braces match nothing in micromatch — shipped every normal as ETC1S; P0 had the same bug). the new manifest-driven recipe: native 2048, UASTC normals (λ3) + the texture-critical basecolors + the screens' emissive, ETC1S q255 elsewhere, prop/glass sheets + most MR at 1024. proven with before/after capture beats + native-res crops (floor seams, chair speculars, screen graphics all visibly sharper; the M4 window tableau intact). receipt:docs/reviews/2026-06-11-apartment-quality-rebake.md. atlasing (33→~10) evaluated + DEFERRED (draws already inside the gate; payload fits without it). the polish panel's converged backlog item: grounding/contact shadows under the floor pillow + chair.
2026-06-10 — the multiverse P0: the pipeline + the loop proof (PASSES)
Cargo USD in, a lightmapped kit slice at 120fps out, disposed clean ten times over. the bake pipeline is real.
- the pipeline, proven end-to-end on a free kit ($0 risk,
kb3d_secretlab):scripts/world-pipeline/(inventory.py, bake.py, compress.mjs, budget.mjs) — USD import → curate by sightline → decimate → join-by-material → xatlas uv2 → rebuild materials from the kit texture convention (Cargo ships MaterialX-only shaders that Blender imports black; rebuilt as Principled BSDF, metalness clamped ≤0.85) → cycles GPU lightmap bake → GLB → meshopt + KTX2. 100MB raw → 10.99MB shipped (8.4MB GLB + 2.6MB KTX2), 194k tris. - the runtime:
lib/world-engine/deep-dispose.ts(full enumerated dispose — geometry, materials, every texture slot + uniforms, render targets, skeletons),kit/kit-loaders.ts(shared KTX2 transcoder singleton at/basis/+ meshopt),kit/session-environment.ts(session-resident PMREM IBL, never disposed per route). - measured on the prod build at retina (`/dev/p0`, M4 Pro, DPR 2): 120fps p50; 10 mount/dispose cycles with
renderer.infoflat at baseline (geo 1 / tex 2 / prog 3) and heap reclaiming to 21.5MB; streamed load mid-dolly with 1 upload frame of 1073 (41.6ms), otherwise locked to refresh. Receipt: `docs/reviews/2026-06-10-p0-pipeline-loop-proof.md`. - a leak found + fixed: the first run leaked +1 GPU texture/cycle from drei
<Environment>'s un-evicted IBL map; the session-resident PMREM env fixed it (textures flat at 2 across all 10 cycles after). - license matrix written against the actual KitBash3D EULA: `docs/systems/kit-license-matrix.md` — baked derivatives ship, raw/editable kit files never do, the subscription stays active while worlds are live.
- toolchain installed: toktx/ktx2check (KTX-Software 4.4.2), xatlas (into Blender's Python), three-stdlib.
- auth gate removed (rey's call): reabal.ai is a personal site with no visitors to gate. The proxy no longer redirects to
/login; the session resolver reports every request as the owner. The HMAC crypto +canViewExperimentstay intact for a one-line revert./admin,/vault,/journal,/dev,/scrapbookare now open. (Dead/loginUI can be deleted in a cleanup pass.)
2026-06-06 — Spaceship foundation buildout (rey's review pass)
dashboard you can see, metal that's been used, a sky with ghosts in it. the cockpit stopped being a still life.
- cockpit POV — pulled the eye back + tilted down + widened the cone (
back -0.7→0.42, pitch 13→18, fov 42→52) so the console/dash is the lower-frame foreground (where the dials/nav-screen go); the worlds stay framed above the cowl. live-tunable via?back/?pitch/?fov. - worn metal — data grade (dust albedo + low envMap + deeper normal + off-mirror) then the proper fix: offline AO baked into the served hull (
scripts/ship-asset/bake-hull-ao.mjs) + consumed viavertexColors, so crevices/seams darken = a used ship, no grime texture. - windshield deep field + lore eggs —
ship-deep-field.tsx: Tau Ceti (light-free gold sun), Adrian (emerald), the Petrova Line (astrophage-red arc), and the Hail Mary (drifter) placed far + high above the worlds at varied depth, so the cockpit-vs-field parallax reads as real depth. worlds stay the near nav. - live console —
ship-screens.tsxanimates the dash screen surface (a powered-console glow: faint scanlines, sparse data rows, a slow sweep). - controls action-system stub —
ship-controls.tsxregistry + hit volumes over the joystick/throttle/console; click = a dry in-voice line. extends to the sourced control GLBs with no new wiring. - deferred: decal-atlas application (needs DecalGeometry projection + placement direction — the Phase-2 dressing pass). rooms stay skeletal placeholders (real interiors are their own sessions).
2026-06-06 — Spaceship platform foundation (steps 1-3)
the ship learned to have rooms behind you. walk in, the screen dims, you arrive. no clipping through walls.
- offline prep pipeline —
scripts/ship-asset/prep.mjs: every sourced ship GLB runs to disk (prune → flatten → pivots with manifest hinge overrides → merge static by material family → WebP → offline vertex-AO bake into COLOR_0 → material-policy validate → meshopt → budget receipt + reject checklist + node manifest). No runtime geometry/material surgery.budget.mjsshares category ceilings withaudit:ship-glb --receipt. AO via three + three-mesh-bvh (no native gltf-transform AO; N8AO stays desktop-only). - ship-scene split — the 1911-line monolith became a lean orchestrator + focused modules (camera-rig, materials, lighting, anchors, planet, window, debug, view-params).
- aft route spine — the 4 personal rooms (bunk/desk/locker/engine) are URL-addressable SUB-STATES of the ONE persistent ship scene (
app/dev/hades/layout.tsxowns the mount; rooms never unmount the canvas)./dev/hades/[room](unknown segment 404s), cold-load snaps, back resolves to the cockpit,/dev/hades/*subtree public. - dissolve walk-in — click a room → a DOM fade-through-dark masks the camera cut to a placeholder lit room (the deep dolly into raw cabin read as wall-clip — proven, rejected). Reduced-motion snaps. Scroll-lock + 2.5s fail-safe.
- discipline — 4 frame-proof hero poses as data + deterministic Playwright capture (
capture-poses.mjs), composition law + lived-in test + kill rule (hero-poses.mjs), dev?diag=onoverlay, and the operator indexdocs/systems/ship-foundation.md. - incubation — still
/dev/hades, public for review. Re-gate before any/promotion.
2026-06-01 — Homepage regenerated GLB intake
the sprite loophole is closed. hail mary gets geometry. space gets objects, not excuses.
- planet bodies — promoted the regenerated low-poly diorama GLBs from
3d assets/Cosmos-GLB-assets/intopublic/models/cosmos/{arcade,worlds,lab,archive,home}.glb, optimized with WebP textures + Draco. - hail mary — replaced the homepage WebP sprite with
public/models/cosmos/hail-mary.glbin both the full scroll scene and static poster. - ambient models — added the regenerated debris panel, moon, satellite, spacecraft, space probe, and meteorites as sparse authored pass-by GLBs instead of spraying model confetti everywhere.
- ledger — updated the asset ledger, allowlist, active homepage plan, and root doctrine so the docs stop preserving the retired sprite law.
- impostor runway —
pnpm cosmos:render-impostorsrenders fresh transparent planet WebPs from the promoted GLBs for the static/mobile and far-LOD tier.
2026-06-01 — Homepage website spine
the cosmos stopped pretending the absence of interface was sophistication. still weird, now more clearly a website.
- diegetic ui —
HomeDiegeticUireplaces the minimal chrome with a DOM-owned system strip, route rail, biome readout, local trace line, audio control, and anchored planet inspector. - state contract —
useCosmosUiStatesubscribes tohtmldataset state viauseSyncExternalStore; scroll, hover, and warp publishers now firereabal:cosmos-ui-change. - planet spec —
BiomePlanetSpecnow ownsimpostorSrc,uiLabel,routePath,routeRole,readout,inspectorVerdict, andinspectorAction. - object anchors —
BiomePlanetSpecnow ownslabelAnchor,eggAnchor,cameraDwell, andmemoryLines; the inspector follows authored object anchors instead of the planet center. - fallback tier — static/reduced-motion planets render impostor WebPs instead of generic glowing spheres. Desktop GLBs visually crossfade from impostor to body on approach.
- asset runway —
pnpm cosmos:asset-intakeverifies planet GLBs, impostors, Draco, external refs, and ledger rows before final generated bodies get to audition. - review stack —
pnpm cosmos:reviewnow adds GPT-5.5 Pro as an OpenAI implementation critic beside Gemini video and Opus still-frame taste; Opus synthesis reconciles all three instead of pretending one model saw everything.
2026-06-01 — Homepage A5 signatures + taste pass
the remaining per-biome signatures + the arrival/bloom/pacing taste pass on the handcrafted-GLB scene. verified per beat via the retina `capture-closeup` (the probe is a scroll-through blind to these wins); rey real-Chrome verdict is the gate. 3 commits.
- signatures (
f74b8080) — worlds: replaced the sprite-era billboard halo with a body-locked weather shell (drifting cloud bands lit by a real day/night terminator + lit-limb scatter + lightning at the closest pass). lab: newLabTells— orbiting hazard-drone strobes + body-synced reactor patches, ramped to peak through the eclipse dwell so the dark disc whispers "active containment." bloom retune (.40 / .86 / .27) so the emissive tells catch the post pass now that IBL raised the midtones. - the dive (
b52805d9) —planet-handoffaims the warp at each planet'swarpEntrydoor (marquee gate / front door / containment iris), not dead center, + pushes the FOV cone + stardust smear; new CSS lightspeed overlay (radial streaks + central bloom, tinted per destination biome) driven by the publisheddata-cosmos-warp, masking the soft ~1024 texture at peak warp. - taste (
ab001a0c) — home terminus settle (slow orbital sway + bob at p≈1, gated to engage only at rest, so the final frame breathes instead of freezing); pacing glide (PROGRESS_DAMP 5.0→4.5, LOOK_DAMP 5.5→5.0 for less "abrupt").
2026-05-31 — Homepage GLB scene: first video audit + doc truth pass
first-ever video review of the handcrafted-GLB scene (the prior reviews were all the deleted marble planets). Pipeline scored 63/100 (Gemini video 68 / Opus stills 57); rey's retina verdict: 30. The gap is sprite-era plumbing left around the new GLB bodies, not the bodies themselves.
Audit findings (committed to docs/reviews/2026-05-31T07-57-44/):
- composition + hierarchy is the #1 driver: the 5 planets read as a horizontal conga-line of near-identical dark spheres, arcade is a "hat on a marble" shoved bottom-left, arrival has no focal hero.
- lab is a featureless black disc (no lab-ness; the eclipse doesn't actually align at its dwell).
- texture softness up close is real but secondary: the GLB bodies read crisp at distance, soft on approach (Meshy image-to-3D, ~1024px textures over 82-94k-tri bodies).
- genuinely premium already: archive (golden filigree), home (house/tree/car), worlds (living green world); motion + voice are the strongest dimensions.
Doc truth pass (the docs had drifted from the code): fixed the two file headers that still called the GLB planets "painterly WebP billboards" (cosmic-scene.tsx, biome-planets.ts), rewrote the stale reabal skill (it pointed at a deleted plan + preached the retired billboard-sprite / "zero new assets" doctrine), brought this changelog current, collapsed the self-contradictory handoff.md.
Tooling: added scripts/cosmos-review/capture-closeup.mjs — retina (DSF2) close-up + dive capture, because the standard capture.mjs only scrolls past at DSF1 and structurally never renders the close-ups where the texture softness shows.
2026-05-30 — Homepage handcrafted-GLB rebuild (P0–P3b)
the R3F spatial scene's planets were 4 tinted twins of one procedural marble shader (only arcade was bespoke) — the "templated beats" failure one layer down. rey art-directed 5 premium GLB bodies; this rebuild wired them in and paid down the debt. Consolidated four overlapping homepage plans into the single canonical [`2026-05-30-homepage-120-handcrafted-planets.md`](../plans/2026-05-30-homepage-120-handcrafted-planets.md).
- P0 (
e2ddc62f) — doc + doctrine reconciliation: 4 homepage plans → 1; stripped the cosmos.md implementation zombie to lore-only; fixed the framer-motion + R3F doctrine contradictions + dead drift-audit paths. - P1 (
1bae29f1) — five handcrafted GLB planet bodies via one sharedGlbPlanet+ drei<Environment>/<Lightformer>IBL; ~2,980 lines of dead procedural-planet code deleted. - P2 (
4f11889d) — un-dimmed the scene (Tau Ceti rest intensity 480→620 as a shared constant) + disposed GL leaks. - P3a (
0e32db0e) — scroll pacing eased (LOOK_DAMP 7.5→5.5) + returning-visitor stateful tagline. - P3b.1 (
8f624fca) — arcade thematic separation (baked-emissive floor + per-planet key light). - P3b.2 (
410f5369) — the continuous click-dive (radius-relative arrival, 820ms lock, FOV bell to 66, ViewTransition at 0.82).
Known gaps carried forward (per the 05-30 plan's remaining phases): adaptive LOD never built (full GLB at all distances), no perf floor, and the per-biome signatures + static fallback still target the deleted billboard sprites. These are the active homepage work.
2026-05-26 (late) — Cosmos 3D rebuild kickoff: paradigm shift to spatial scene
rey verdicted the 6-beat 2D-shader cosmos a 20/100 after multiple sessions of probe-tuning landed between 76-87 ("repetitive layout template, abrupt planet pop-ins, yellow/orange dominance, constant annoying hum, hard section breaks"). The architecture itself was the problem — no parameter retune could fix it. The 5-week 3D rebuild plan is locked at [`docs/plans/2026-05-30-homepage-120-handcrafted-planets.md`](../plans/2026-05-30-homepage-120-handcrafted-planets.md).
Paradigm shift: the homepage is now a true 3D spatial scene rendered with R3F + drei + three. One continuous scroll-driven camera path through Tau Ceti's system; each biome planet is a clickable 3D mesh that ViewTransition-hands-off to its landing. Reference tier: Henry Heffernan, Bruno Simon, Lusion, Active Theory.
Hard cut, not gradual deprecation:
- DELETED
src/features/cosmos/(entire directory: shader, beats, audio, eclipse, biome-tints, state-store, motion, all 20+ standalone components) - DELETED
src/features/home/cosmos/beats/,chrome/,cosmos.tsx,home-cosmos.stage.ts - DELETED 7 obsolete cosmos test files
- DELETED
app/dev/cosmos-{audio,shader}-preview/
New scaffold at `src/features/home/cosmos/` (week 1 deliverable):
cosmic-scene.tsx— R3F<Canvas>root +<CosmicWorld>compositioncosmic-scene-mount.tsx— client wrapper,dynamic({ ssr: false })loads the scene (Next 16 requires ssr:false inside Client Components)use-scroll-progress.ts— Lenis-driven scroll → 0..1 ref (Lenis programmatically updateswindow.scrollY)r3f-types.d.ts— triple-slash JSX type augmentation for R3F intrinsicsscene/camera-rig.tsx— scroll-drivenCubicBezierCurve3camera pathscene/tau-ceti-star.tsx— emissive sphere + procedural canvas corona sprite + PointLight, subtle pulse via useFramescene/star-field.tsx— drei<Stars>wrapper, 3000 instances
Stack lockdown update (retired 2026-06-02): this entry recorded the old narrow R3F carve-out for the homepage and the old landing-stage direction. That law has since been replaced by `docs/plans/2026-06-02-r3f-platform-wide.md`. Three rules at the time: 1. R3F + drei for the 3D homepage, no three.js direct unless R3F blocks a specific perf/render need 2. Procedural geometry for planets, rings, arcs, particles, starfields 3. External GLB only for hero objects (currently scoped: Hail Mary ship)
The unblock: Next 16 + R3F init required transpilePackages: ["three", "@react-three/fiber", "@react-three/drei", "its-fine"] + experimental.optimizePackageImports for the same set. Without these, R3F's chain of ESM-only sibling packages failed named-export resolution and produced "Element type is invalid" at render. Both directives locked in next.config.mjs.
Infrastructure changes:
.claude/launch.jsonswapped topnpm dev(Turbopack) — webpack can't resolve three.js named exports cleanly in Next 16cosmos-drift-audit.mjsgainedpathPrefixExceptionsmechanism; R3F + hex/rgba carved out undersrc/features/home/cosmos/cosmos-audio-budget-audit.mjsgracefully skips when the legacy scope no longer exists (preserved as scaffolding for phase 2 spatial audio)tests/unit/foundation-routes.test.tsupdated to assert<CosmicSceneMount />mountcomponents/fancy/text/{breathing-text,typewriter}.tsxgot minimalReact.ComponentType<Record<string, unknown>>casts on their polymorphicasprops — R3F's global JSX augmentation widenedIntrinsicElementswith ~100 THREE elements, breaking children-type inference on dynamic tags
Audio + tint retunes (last gasp of the 2D cosmos, kept as inputs to the 3D rebuild):
- Drone gain 0.62 → 0.16, cutoff 540Hz peak → 360Hz (the "annoying loud hum" rey called out)
- Cue gains halved (cold-open 0.7 → 0.32, eclipse-shimmer 0.8 → 0.38, home-arrival 0.75 → 0.34, shooting-star 0.45 → 0.20)
- Cottage tint
[0.92, 0.62, 0.28] × 0.32 → [0.55, 0.40, 0.22] × 0.18(the "yellow/orange everywhere") - Archive tint
[0.66, 0.50, 0.32] × 0.36 → [0.42, 0.34, 0.24] × 0.22 NEUTRAL_TINTwarm-brown[0.18, 0.12, 0.10]→ cool-deep[0.06, 0.07, 0.12]- Planet chrome fade window
0.30..0.70 → 0.10..0.55
These ship in the deleted 2D cosmos's last commit (visible in git history at 42679775). Not in the live codebase post-rebuild, but the audio/visual gain values transfer forward as 3D scene defaults.
Asset budget answered: Net new assets needed for the 5-week rebuild = ONE GLB (Hail Mary ship, week 2). Everything else is existing (planet WebPs as albedo textures on procedural sphere geometry) or procedural (starfield, asteroids, rings, arc, corona). Total NEW download for homepage chunk: +150KB gz R3F stack + ~150KB GLB = ~300KB gz. See docs/bookkeeping/handoff.md for the full asset table.
Week 1 acceptance gate cleared: Canvas mounted, Tau Ceti rendering as emissive sphere + corona, starfield via drei <Stars>, camera follows scroll-driven Bezier path. Verified: scroll moves the camera (Tau Ceti drifts out of frame at scroll 0.5 as the path arcs past).
Commits pushed to main (in order): 72d77291 plan + tech-stack R3F lift + CLAUDE.md pointer → 6e45e942 install R3F + drei + three → bee9a51e hard cut old cosmos + new R3F scaffold → 0a9d7670 drift audit prefix-exception extended to color rules → a8220fb7 unblock: transpilePackages + drei <Stars> — week 1 acceptance gate cleared
Next: week 2 — planets in space.
2026-05-26 (earlier) — Sprint B kickoff, 30+ commits (Beat 0-5 engine-native, foundation locked, audio fully unified, ALL 7 cue triggers live, engine-native click handoff)
The homepage cosmos now composes against the stage engine end-to-end. Probe trajectory 78 → 90 peak → 88 stable, sprint B acceptance gate (≥89) hit.
Engine surface added (5 new primitives + 1 API extension): <ParticleField format="canvas-2d"> (lib/stage/primitives/) — 20-150 particles, drift + twinkle, ResizeObserver-aware. <ParticleField format="webgl-instanced"> (lib/stage/primitives/, full pipeline in particle-field-webgl.ts) — 500-3000 particles, GPU-instanced quads + per-instance twinkle phase + additive blending, ~1.2ms/frame at 1500 instances on M-series. dom format stubbed; warns dev-time + renders null. <LightCurveEvaluator> (lib/stage/light/) — per-frame curve evaluator, auto-mounted by ComposeStage so spec-declared LightSource curves actually publish positions. useOcclusionFraction(occluderRole, lightPos) (lib/stage/light/) — wires the Beat 3 Tau Ceti eclipse signature gesture; covered by 6 unit tests (251 stage tests total). ComposeStage backdrop?: ReactNode prop — clean way to pass the real backdrop component instead of a placeholder slot. LightWrap now respects occlusionMaskRef on its first source — dims gradient via the occlusion fraction.
Homepage integration: src/features/home/cosmos/cosmos.tsx mounts via <ComposeStage spec={homeCosmos}> with <CosmosShader> passed through the backdrop prop. All 5 biome beats engine-native via generic <BiomeBeatHero> + per-beat config table (BEAT_CONFIGS): each beat reads as distinct via its own LightSource(s), particle color, count, drift direction. Beat 3 stacks ["tau-ceti", "biome-lab-glow"] so the eclipse signature wires end-to-end. Beat 0 partially decomposed — 5 of 12 bands now have engine perspective depth (bandFar 0.5, bandConstellations 0.42, bandLore 0.30, bandShip -0.05, bandParticulates -0.12). <CosmosBeatBridge> mirrors engine beat → cosmos store; legacy useBeatObserver retired. Cursor bridge via inline-style calc on <main data-cosmos-root> — derives legacy --cosmos-cursor-x/y from engine --cursor-x/y, finally feeding beat consumers cursor data (pre-existing scope bug fixed).
Foundation packages installed (the 120/100 baseline): lenis 1.3 (canonical scroll driver, supersedes legacy ScrollSmoother opt-in), @rive-app/react-canvas 4 (state-machine vector animation, whitelisted with discipline rules), @floating-ui/react 0.27 (positioning primitive for phase 2 radix replacements), shiki 4 (build-time syntax highlighting), embla-carousel-react 8 (headless carousel), vaul 1 (iOS bottom-sheet), sonner 1 (Emil Kowalski's toast lib), cmdk 1 (Paco Coursey's command palette primitive). app/_providers/lenis-provider.tsx mounted at root with GSAP ScrollTrigger sync; auto-disabled on touch + prefers-reduced-motion.
Motion P1s fixed via smoothstep ease: Planet dolly scale (CSS var * var * (3 − 2 * var) on --hero-fade-approach) — cinematic accelerate/decelerate replaces the old linear ramp. Chrome fade-in (title / caption / artifact opacity) — same math on the normalized progress window. Shader biome tint lerp (per-biome approach weight before the cross-blend in resolveCosmosBiomeResponse) — single commit that lifted probe 81 → 90. Three classes of "abrupt" complaints cleared from the verdict text.
Audio unification (two passes): Pass 1 — drone moved out of <CosmosAudio> into the engine's StageAudio (driven by spec.audio.drone + lib/stage/audio/drone-bed.ts). Eliminates the dual-oscillator 80Hz phasing problem. Pass 2 — CosmosAudio routes its per-biome pad voices + per-beat textures through the engine bus's master gain via ensureAudioContext() + getMasterGain(). One WebAudio context for the entire homepage (was two). Single autoplay-unlock path, single visibility-pause path, no second graph competing for the ~4-context browser tolerance ceiling. CosmosAudio keeps its biome pad voice library + per-beat texture crossfade engine. 4 cosmos cues generated via ffmpeg + ledgered: cold-open-rumble (4s sub-bass swell), eclipse-shimmer (2.2s tremolo at 3.8kHz, fires at scroll 0.55), home-arrival-chord (3.5s A-major triad, fires on Beat 5 entry), shooting-star-whoosh (1.4s pink-noise bandpass, fires after 20s idle on Beat 5). Total ~205 KB CC0.
Doctrine alignment: Naming rule locked: no version suffixes anywhere in the tree (no cosmos-v2/, no BeatNext, etc.). Rive un-banned and whitelisted in docs/doctrine/tech-stack.md + reabal SKILL.md with discipline rules (state-machine vector animation only; GSAP stays default). Lenis canonical scroll documented as the rule, not opt-in. Framer-motion footprint claim corrected: ~238 deprecated consumers across src/, app/, lib/, components/ slated for a phase 3 lane post-sprint-B (CLAUDE.md previously claimed "button.tsx is the last surviving consumer" which was fiction). cmdk search palette refactor drops 4 framer-motion imports from components/search-palette.tsx via Tailwind data-open: variants + native matchMedia + visibility-lag state instead of AnimatePresence.
Typographic fixes: Title↔caption clearance (clamp-scaled gap so the title's 88px descenders clear the caption baseline at every breakpoint). Lore-artifact↔title overlap fix (arcade / lab / archive bumped to topPct 92 so the artifact sits well below the title bottom in lower-left zones).
Other: 11 macOS Dropbox " 2." dupes cleaned from working tree (lib/stage 2/, etc.). Fraunces (Google Fonts, free, variable opsz/wght/SOFT/WONK axes) shipped as --font-display-serif via next/font/google — replaces the rejected Reckless plan (€352 not earning the price). audit:stages static gate added to the prebuild chain. Cosmos-review pipeline gained --ensemble=N multi-run option + 3-tier JSON parse defense (parseGeminiJson with direct → outermost-brace slice → brace-balanced first-object fallback). Quarterly model audit scheduled (2026-08-25) via mcp__scheduled-tasks. 14 dead UI primitives + 13 unused radix packages purged from the tree. .claude/skills/reabal/SKILL.md ships as canonical session doctrine (auto-loaded). context7 MCP wired with API key; figma + magic MCPs removed.
First webgl-instanced consumer: Beat 0's src/features/home/cosmos/beats/beat-0-arrival.tsx now mounts a <ParticleField format="webgl-instanced" count={220}> deep-stars layer at depth 0.6 (slightly deeper than the legacy 3-canvas StarField at depth 0.5). First retune caught by the cosmos:probe drift alarm (probe 74, -12.5 vs median) — additive blending over the existing field over-painted. Dialed down to 220 stars / 1-2px / 0.10-0.30α — subtle augmentation, not replacement. Proves the primitive on production paths; legacy StarField stays until a careful side-by-side retirement pass.
Per-beat planet-tracking light positions: Each biome's glow LightSource (biome-arcade-glow, biome-worlds-glow, etc.) used to sit at staticPos (0.5, 0.5) — the viewport center. Now updated per HERO_COMPOSITION's planet positions: arcade/archive at (0.70, 0.50), worlds at (0.30, 0.50), lab at (0.50, 0.52), home at (0.50, 0.50). LightWrap radial gradients anchor ON the planet for each beat instead of off-axis. If HERO_COMPOSITION changes, these need to change in lockstep.
Cue dispatcher: scroll-position + idle triggers implemented: Real bug found and fixed — useCueDispatcher had stubbed-out scroll-position + idle, but home-cosmos.stage.ts USED both (eclipse-shimmer at scroll 0.55; shooting-star-whoosh at idle 20s). Those 2 of 4 cues were dispatcher-orphan since the audio files shipped. Rising-edge detection for scroll-position (fires once per crossing; re-fires on next ascent after falling below). Drop-detection for idle (new idle period starts when idleMs drops from previous tick → clears per-cue fired flags). 6 new tests; total 257 stage tests.
StageShaderPost grain MVP: The primitive was skeleton-only; spec field grain: 0.025 was decorative. Now ships a CSS-only film grain overlay via inline SVG <feTurbulence> (fractalNoise, 200×200 stitched tile, opacity ramp scaled to the doctrine [0, 0.1] band). Static texture (not per-frame animated yet — the real GLSL pass adds that). Zero RAF cost. mixBlendMode: overlay — cinematic film grain standard. The other 3 effects (velocityBlur / chromaticAberration / cursorLensing) stay as accepted-but-no-op props until the full GLSL post-pass commit lands.
Dev verification route: New /dev/stage-cue-test (admin-gated per proxy.ts ADMIN_PREFIXES) mounts a minimal cueTestStage exercising the 4 trigger types live at the time it shipped (stage-enter + beat-enter + scroll-position + idle) in compressed form (400vh scroll + 3s idle window vs the home cosmos's 800vh + 20s). Status overlay shows live scroll progress + active beat + idleMs.
All 7 CueTrigger variants now live: The remaining 3 trigger types shipped: beat-exit (mirror of beat-enter, fires when leaving the matching beat; pairs symmetric beat-boundary cues), cursor-near (RAF poll against the bounding rect of the DOM element carrying data-stage-role={layerRole}; rising-edge fire on entering radiusPx circle; re-fires after exiting + re-entering), click (querySelector + addEventListener attached to the named layer; 500ms re-resolve poll so beats mounting/unmounting between scrolls keep cues wired). Bug fix bundled: prior beat-enter implementation re-fired on every snapshot notify, including non-beat changes (tier device/motion). prevBeat guard added so beat-enter + beat-exit fire ONLY on actual beat transitions.
Engine-native click handoff: <DepthLayer handoffTag handoffHref> now becomes a clickable + keyboard-focusable surface that triggers navigateWithHandoff() — the new transport helper at lib/stage/handoff/navigate-with-handoff.ts. Helper resolves the tag against the outbound registry, stashes {vtTag, receiveLayer} in sessionStorage for the destination stage's incoming, calls document.startViewTransition(() => router.push(href)). Falls back to plain navigate when VT API absent. Survives sessionStorage throwing (private mode / quota). View-transition-name CSS applied to the layer element when the tag resolves, so the outgoing element is part of the VT pair. Modifier-key handling (cmd/ctrl/shift) lets the browser default take over (new tab / window). Engine-native handoff is the TRANSPORT layer; useEntryTimeline keeps owning visual choreography (halo wash + scale ramp) for the homepage's biome planet clicks (incremental migration, preserves character).
What's deferred: Legacy 3-canvas <StarField> retirement (doctrine §11 named anti-pattern — webgl-instanced primitive is live; replacement needs browser smoke first). Beat 0 deeper decomposition (7 of 12 bands still at focal plane). Per-beat planet-tracking light positions (currently the wash centers in each beat). BiomeAmbientParticles → per-beat webgl ParticleField. Browser-smoke validation pass.
2026-05-25 — Cosmos stage engine, sprint A complete (25/25 tasks)
The engine that every cinematic stage (homepage cosmos + 5 landings + /about) will compose against shipped — primitives, hooks, store, audio, light, handoff, all wired through one `<ComposeStage spec={...}>` API and acceptance-tested at `/dev/stage-smoke`.
Module: `lib/stage/`. Seven sub-modules — motion/ (5 hooks + store + beat resolver + easing), light/ (registry + LightSource + useLightAnchor), audio/ (bus + curated-cue dispatch + drone bed + useCueDispatcher), primitives/ (DepthStage + DepthLayer + LightWrap + StageBackdrop + StageShaderPost skeleton + StageAudio + StageController class + resolveTier), handoff/ (handoff-tags registry + useStageHandoff), composition/ (StageSpec contract + Zod schema + logSignature + ComposeStage). lib/stage/README.md documents the public surface.
Architecture. Per-stage external store (stage-store.ts, two tiers: mutable raw for per-frame numerics + immutable snapshot for transitions) so React only re-renders on semantic events, not 60Hz ticks. StageController class owns the RAF loop + event listeners + IntersectionObserver + matchMedia subscriptions, kept separate from <DepthStage>'s React wrapper for node-pure testability via the setupRafDriver helper. Cursor CSS vars publish on the stage root so DepthLayer cursor tilt resolves at browser paint time. Light registry is app-global per doctrine §6.
StageSpec contract — Zod-validated runtime. Required: id, scrollHeight, lightSources, audio, mobileTier, reducedMotion, visualFx, handoff, signature.description. Audio cue budget capped at 5; signature description must be ≥ 8 chars; grain in [0, 0.1].
Test infrastructure. 251 tests across 32 files. Most stage tests stay node-pure via renderToStaticMarkup + setupRafDriver (no happy-dom). Three test files use the @vitest-environment happy-dom directive for real useEffect lifecycle: useCueDispatcher, StageAudio, useStageHandoff, ComposeStage. Audio modules mocked via vi.hoisted so factory + test-body share fn instances.
Acceptance smoke. /dev/stage-smoke/page.tsx mounts every primitive together against the minimal smoke.stage.ts. Browser-verified in headless Chrome: hero focal sphere with LightWrap halo, mid layer behind, far-bg radial gradient, chrome chip pinned, zero console errors. pnpm prebuild clean on every push.
What's deferred. Per-trigger sprint-B work: scroll-position + idle + cursor-near + click cue triggers (sprint A covers stage-enter/exit + beat-enter). DepthLayer's handoffTag outbound-click flow. StageShaderPost's real GLSL post-pass. Cosmos retrofit onto the engine (sprint B). Landings + /about as their own stages (sprints C-G).
2026-05-23 — Cosmos rebuild Phase A → H, doctrine promoted
The homepage cosmos became the journey the doctrine promised, end-to-end.
Phase C — tokens + typography lock. The locked §4 OKLCH palette landed under [data-cosmos-root]: three --cosmos-* backdrop tones, five --biome-* tints (with --biome-cottage aliased to --biome-home until Phase F's trace-schema rename), --tau-ceti-core / --tau-ceti-rim / --adrian-glow, and three cosmos-scoped type tokens. §5 typography locked across the per-beat hero stack: 88px Switzer weight 800 with zero tracking on biome title cards, 14px JetBrains Mono 0.18em on lore captions, 16px on the masthead narration, 12px on the upper-right beat counter. A cinematic fade kernel ramps title / caption / artifact opacity 0→1 across --hero-fade-approach 0.30→0.70 (the biome's approach kernel routed through one indirection on the BeatSection wrapper).
Phase C hardening + Phase C+ second pass. Per-beat grade strength flipped from binary per-active-biome rules to a continuous max(approach-arcade × 0.65, approach-worlds × 0.55, …) on the cosmos root so the band-grade fades up around section midlines instead of crossfading via the 700ms IO ease. lib/cosmos-narration.ts became the single source for BEATS[i].masthead (no more duplicated literal voice strings in beats-config.ts). Per-biome --approach-* kernels SSR-seed to 0 so first paint is deterministic. A typed approachVar(biome: BiomeId | null) helper replaced inline template-string CSS-var indirection, giving Phase F's cottage → home rename a single compile-time failure site.
Doctrine promoted. docs/plans/2026-05-22-cosmos-rebuild.md → docs/doctrine/cosmos.md. The plan was operating as locked doctrine across three commit chains; leaving it under docs/plans/ was the same accretion liability the doc cleanup just fixed. git ls-files | xargs sed swept absolute + relative references across the canonical + code surface (185 files). §14 of the doctrine rewrote as a completed audit-trail record. The orphaned chapter-placeholder.{tsx,module.css} from the retired chapter-based scroll direction was deleted along the way — untracked WIP that was breaking pnpm build against the current HomeSceneRenderState union and that two tests already forbade.
Phase D + E — macro parallax + 3.0s cold open. Hail Mary drift moved from the 110s CSS animation to a scroll-coupled curve anchored at the §6 storyboard points (scroll 0 → x=33vw y=36vh, 0.25 → 50/42, 0.5 → 68/36 mid-fade, 0.6 → opacity 0 + data-hail-mary-visible=false + display:none — "ship NEVER reappears after Beat 3" enforced in the compositor). Tau Ceti recession curve binds --tau-ceti-width: 56vw → 42 → 28 → 18 (behind occluder) → 14 → 10vw across the six beats. --beat-grade-color crossfade tightened 700ms → 540ms (§6). New useBeatCameraCoupling hook publishes --beat-camera-zoom (1.0 → 1.18 across Beat 5 local 30-50%) and --beat-atmosphere-progress (50-70%) only when the home beat is active, scaffolding Phase F's surface descent. Phase E extended useFirstVisitColdOpen to the §6 3.0s timeline with five per-element windows (stars / tau-ceti / petrova / ship / masthead), each ramped via easeOutCubic. Every consumer (StarField / TauCeti / PetrovaLine / HailMaryShip / BeatCounter) now reads its corresponding window so the assembly choreography is actually visible. Tau Ceti's clamp() floor dropped 280px → 80px so the Beat 5 "very distant warm point" anchor lands at the §6 spec value on desktop instead of being inflated to almost 2× by an arbitrary floor.
Phase G — generative audio textures. Third audio layer on top of the existing drone + biome pad chord. Pink (Voss-McCartney) / brown (leaky-integrator) / white noise factories under src/features/cosmos/audio/; per-beat texture engine implementing the §7 spec table (rest pink-highpass, arcade sawtooth+CRT-pulse, worlds brown-highpass+4Hz AM, lab squares+Poisson Geiger, archive brown-highpass-bright, cottage pink-mod). 540ms scroll-pass swoosh (reverse-cymbal envelope, 2kHz→8kHz highpass sweep) fires on every IO beat transition. 80ms planet-hover whisper (bandpass at the 1.2kHz breath formant) fires on onPointerEnter / onFocus of every planet anchor via a cosmos:planet-hover custom event with a per-instance 500ms throttle. Both motion-coupled event sounds respect prefers-reduced-motion. Asset-dependent click sounds (Mustang engine, house door, Hail Mary centrifuge) defer with Phase F.
Phase H — View Transitions API hand-off. lib/cosmos/vt-tags.ts became the single source of truth for the cosmos planet → biome landing view-transition tag map; both the cosmos source side (planet anchor inline style) and every landing's destination wrapper import from it. /arcade, /worlds, /lab, /archive, /about each wrap their hero with the matching tag so the browser scales the planet into the landing on navigation. useBiomePrefetch warms the route chunks via requestIdleCallback so the hand-off lands on already-loaded destinations instead of a render-pending flash.
Phase I scaffolding. scripts/cosmos-weight-audit.mjs asserts each public/cosmos-renders/ WebP ≤ 800 KB and the total ≤ 3 MB (subset of §10's 4 MB page-weight budget; JS + global assets carry the remaining 1 MB). Current cosmos total: 1874 KB / 3072 KB (61% of budget). Warns at 80%, fails the prebuild chain at 100%. Documented in docs/systems/audits.md row 17. The interactive frame-time runner (scripts/cosmos-perf-check.mjs) and the manual desktop screencast stay on the deferred-work list for Phase F arrival.
Dev preview. /dev/cosmos-audio-preview mounts the audio layer cake (drone + pad + texture + swoosh + whisper) with explicit toggles + a biome cycler + an active-spec readout, isolating each layer from the live cosmos store. Mirrors the existing /dev/cosmos-shader-preview affordance.
Test surface. Five new contract test files — cosmos-tokens.test.ts (§4 token mirror parity, 25 cases), cosmos-phase-d-motion.test.ts (Hail Mary + Tau Ceti curve anchors + height-aware beat centerline sanity, 43 cases), cosmos-phase-g-audio.test.ts (§7 spec values + CosmosAudio integration, 25 cases), cosmos-phase-h-view-transitions.test.ts (single-source VT map + every landing's destination tag, 21 cases), plus extensions to cosmos-phase-b-heroes.test.ts pinning the single-source voice + SSR seeds + typed approachVar() plumbing. End state: 78 test files / 1179 passing cases across the cosmos surface.
Doctrine: docs/doctrine/cosmos.md. What's left: Phase F (home surface + house + Mustang assets), Phase G remainder (the three click sounds Phase F unblocks), and the final perf-pass receipts (desktop screencast, lighthouse pass, dynamic frame-time check).
2026-05-22 — Phase 1.62 photoreal eclipse + brighter cottage
The eclipse becomes a real solar eclipse. The cottage becomes a real world.
Eclipse occluder, photoreal. The Beat 3 gas-giant occluder was the last procedural composite in the cosmos foreground. Replaced with a 1280px Higgsfield render of a real solar eclipse — dark gas giant silhouette, brilliant corona ring streaming around the limb, god-rays radiating past the disc, photosphere "diamond ring" baked in at the upper-right. Occluder width 32vw → 44vw so the corona radiating ~30% past the disc has room to read.
Cottage v2, lit. The original cottage render was almost entirely in shadow — beautiful but too dim for Beat 5 to land as the destination. v2 has roughly half the disc lit by warm star light, continents visible in golden hour, atmospheric haze at the limb, AND the night-side cottage-window pinpoint preserved in shadow. The destination now reads as both "habitable world" AND "lit window calling you home."
Hail Mary alpha tightened. Bumped the luminance threshold from lum<16 to lum<28 with a steeper ramp; killed the dark-red halo around the astrophage engines that the previous threshold preserved.
2026-05-22 — Phase 1.61 photoreal asset overhaul
The cosmos stops looking like marbles.
Procedural SVG planets + ship replaced with photoreal renders. Per rey's 2026-05-22 critique ("this is still a 40/100, the planets look horrible, the hail mary looks worse, space doesnt look like space"), every cosmos foreground asset is now a Higgsfield GPT Image 2 render rather than a procedural SVG composite.
What shipped:
- Hail Mary — full industrial NASA spacecraft profile with
glowing astrophage engine block, three fuel tanks, centrifuge wheel, crew module with viewports, sensor mast + dish antenna, weathered hull paneling, NASA decal. Replaces 6 SVG rects.
- Arcade — vivid magenta gas giant with horizontal cloud
banding, polar aurora, atmospheric halo.
- Worlds — oceanic teal terrestrial with white cloud swirls,
visible continents in deeper green-grey.
- Lab — Jupiter-style steel-slate gas giant with cool cloud
bands and a dark storm hurricane.
- Archive — Saturn-cream ringed planet with full Cassini-
division ring system and ring shadow on the body.
- Cottage — warm gold terrestrial mostly in shadow with a
single brilliant lit-window point on the night side (the destination, seen from orbit).
- Tau Ceti — solar surface with prominence loops + radiant
corona god-rays streaming off-frame.
All assets sharp-encoded WebP, quality 80-84, total budget ~1MB across 7 files. Alpha-keyed at source (circular SVG masks for round planets, elliptical for Saturn, luminance-based for ship and star) so the black render backgrounds drop cleanly over the cosmos shader.
The shader's procedural Tau Ceti contribution dropped to 0.32x, god-rays to 0.55x, nebula brightness to 0.55x — the raster now carries the dominant warm light source so the shader doesn't double-paint the corona.
The "no AI-generated raster" rule from the cosmos doctrine was overridden by rey: "if u cant create proper svg assets/designs then i can use recraft to fucking vectorize photos to svg." Asset-ledger-audit allowlist updated accordingly.
2026-05-22 — Phase 1.52-1.59 cosmos audio + tactile + ambient meteors
The cosmos comes alive on first paint, and stays restless.
Audio defaults to ON. Previously the cosmos shipped silent unless you clicked the toggle — most visitors never knew about the per-biome pad chords, scroll-velocity drone filter, or the cottage arrival sting. Default flipped. An explicit "off" via the toggle still wins, so returning visitors who muted stay muted. The AudioContext is created on mount in suspended state; a one-shot pointerdown / keydown / wheel / touchstart listener resumes it on the visitor's first user gesture (browser autoplay policy compliance, not a hack — it's literally what the spec asks for).
Cosmos meteor. A bright streak fires across the cosmos every 28-52s on a varying trajectory. Each meteor picks a random upper- 60% origin and a random diagonal direction. ~78% are warm Tau- Ceti-tinted (hue 80°), ~22% are cool higher-energy (hue 200°) so the cosmos doesn't read as recurring on a single arc. The "wait, did I just see that?" moment that makes a static frame feel alive between the points of explicit interest.
Cursor magnetism widened + eased. Pull radius 120 → 180px, max offset 6 → 9px, ease-out cubic on the strength curve. Planets now lean toward the cursor before it's near them; close range pulls harder, far range still registers. Reads as "the cosmos is paying attention to you" instead of a discrete hit-test snap.
Masthead lands with weight. Added a fourth layered animation on the narration line: scale 0.94 → 1.02 → 1.00 + translateY -2px → 0 over 640ms cubic-bezier. The line still fades + clip-path reveals + warm-glows like before; the new landing curve makes it feel like a focus pull rather than a flat appearance.
Cosmos breath. Vignette intensity sine-modulates ±2.5% over an 18-second period via uTime. At rest the visitor barely notices, but the cosmos no longer feels frozen — it exhales like something alive between the points of explicit interest. At peak beat values the modulation is absorbed by the clamp; only the quiet moments breathe.
2026-05-22 — Phase 1.31-1.51 cosmos polish push (live audit pass)
Iterated against live screenshots, not Playwright thumbnails.
React #418 hydration error killed. The useCabinetTraces hook returned EMPTY_STORE via getServerSnapshot while returning a populated store on the client's first render — the {visited && <g>} visit-marker SVG differed between server and client. Gated visitedBiomes behind a mounted effect; both sides now render an empty set on first paint. Also added suppressHydrationWarning to AudioToggle's motion.span where framer-motion's MotionValue-driven transform serialized differently between SSR (transform:none) and client (transform: translateX(0px) translateY(0px)). Side effect: the Next.js streaming $RS script no longer hits null parents during hydration, and the entry timeline survives long enough for navigation to fire.
Click-to-biome navigation no longer stutters. Moved router.push from GSAP phase 4 (1.2s in) to the click handler itself so navigation fires immediately. The visual choreography (scale 1.4 → 2.8 → 8.4 across phases 1-3) now runs in parallel with the navigation request instead of gating it.
Planet scatter that survives parallax. Beat 0 rest layout rebalanced — arcade lower-left, worlds center-mid, lab center-bottom, archive upper-right, cottage middle-right. bandSpecks parallax magnitude dialed back -650 → -380 so planets land mid-frame at their own beat peaks instead of being pulled off the top of the frame.
Cottage destination, properly. Cottage anchor at (74%, 66%), peak scale bumped to 6.0× so the cottage planet dominates Beat 5 the way the eclipse dominates Beat 3. Hail Mary ship opacity now fades 0.78 → 0.06 as cottage approach climbs 0.55 → 0.90; the ship's 110s drift loop used to park over the cottage at peak, fully occluding the destination. Cottage arrival audio chord (perfect-5th open voicing, 2.4s envelope) fires once per session when cottage approach > 0.92.
Eclipse climax got its diamond ring. GasGiantOccluder publishes --diamond-intensity per frame; CSS scales the photosphere peek from 0.6× → 2.0× with a warm oklch drop-shadow that grows to 24px at peak. Sells the "Tau Ceti's edge peeks through" moment without adding a JS animation per frame.
Lab darkens during eclipse. Lab planet picks up a brightness(1 - approach*0.58) filter past planet-approach 0.55 so the silver disc actually reads as "lit only by the corona" when the gas giant is in front of Tau Ceti.
Tau Ceti corona tightened. midFalloff exponent 6.2/6.8 → 7.2/7.6, halo contribution 0.06 → 0.035 at arrival. The previous values bled warmth across 35-40% of the left half of the frame; the deep void now wins the negative-space contest, restoring "the corona bleeds OFF the upper-left corner" from the §3 storyboard.
Archive amber dial-back. archive tintStrengthAtPeak 0.42 → 0.36 — at 0.42 the amber still painted across the cosmos like a sepia colorize; the Saturn-ring read as part of the wash. At 0.36 the warmth is a tint (you sense it) instead of a filter (you see only amber).
Star twinkle visibility boost. Near layer twinkle amplitude 0.32-0.62 → 0.50-0.84; period range 2200-7600ms → 1800-5200ms. Stars now visibly twinkle instead of sitting motionless. Mid + deep layers boosted in proportion.
Arcade magenta wash dial-back. arcade tintStrengthAtPeak 0.85 → 0.52 — the previous saturation drowned the planet in neon. At 0.52 the magenta defines the beat without consuming it.
Deployed live to https://reabal.ai across this session's iterations. The cosmos at this point is the journey: arrival → arcade → worlds → eclipse → archive → home. The destination earned its weight; the ship earned its rest.
2026-05-22 — Phase 1.20 cosmos cinematic homepage
The homepage became a personal cosmos.
Six-beat scroll-driven journey shipped. / is now the cosmos doctrine made real (locked 2026-05-21 in docs/plans/2026-05-21-cosmos-doctrine-design.md): a scroll-driven journey past five biome-planets (arcade, worlds, lab, archive, cottage) with Tau Ceti + Adrian + Petrova Line + the Project Hail Mary ship as background lore.
Hand-rolled WebGL2 fragment shader as the Z0 cosmos backdrop — 4-octave fBm nebula, Tau Ceti as a real point-light source with volumetric god-rays raked through the dust bands, HDR bloom, three lens-flare ghosts on the optical axis, vignette, 1-octave film grain modulated by luminance, atmospheric perspective as the §4 depth cue. Per-channel floor clamp at oklch(0.04 0.005 248) — never pure black. 15 active uniforms across cosmos scroll/cursor wiring, per-beat tint vectors, occlusion (Beat 3 eclipse), cottage void deepening (Beat 5). Low/medium/high quality tiers with DPR cap + octave count + ray sample count scaling.
Five planet-approach beats with per-biome surface character revealed past approach 0.4 — arcade neon-lit night side rotating 30°, worlds dome glints, lab off-the-books service-light pinpricks + antennae, archive library spires + banker-lamp pool, cottage single warm window. Sibling-dim curves (opacity = 1 - 0.35 × max-other- approach), per-beat scale curves (arcade/worlds/archive 5.5×, lab 5×, cottage 4×), triangular caption pulse peaking at approach 0.7.
Beat 3 lab eclipse — <GasGiantOccluder> slides over Tau Ceti asymmetrically per Emil ("slow where the user is deciding, fast where the system is responding"): 30% ramp-in (0.55..0.85), peak hold (0.85..0.95), 5% ramp-out (0.95..1.0) = 6× wider in than out. Shader's bloom + god-rays dim under occlusion; crescent corona pass restores a thin warm rim along the occluder edge during transit.
Beat 5 cottage stillness — <CottageWindowPulse> fires once per peak crossing of approach 0.85, brightens the cottage window halo 25% for 600ms, eases back. Hysteresis at 0.7 prevents strobe. Shader's cottage void deepening (subtractive NEBULA_COOL contribution) + brightness scale 0.65 + Petrova arc fade-out + sibling planets fade off-frame so the cottage stands alone.
1.6s click-entry timeline — 4-phase GSAP (300ms glow + dolly → 400ms halo expand → 500ms viewport fill → 400ms cross-fade + view- transition). cubic-bezier(0.23, 1, 0.32, 1) ease-out per Emil. Reduced-motion: 200ms linear fade, skip phases 1–3.
Quiet DOM chrome (three pieces only). Burned the masthead glassmorphism pill, audio-toggle pill, right-edge beat-dot column. Replaced with: <MastheadNarration> (typed-const beat-keyed voice line, Beat 0 silent), <AudioToggle> (14px SVG speaker glyph + label fade on hover/focus + Framer Motion magnetic micro-pull within 24px, touch-disabled), <ScrollChevron> (lower-center, Beat 0 only, dies on first scroll past 0.04, never returns).
Single-source-of-truth scroll state. New src/features/cosmos/cosmos-state-store.ts — useSyncExternalStore for React semantic snapshots (re-render only on beat / biome / reduced-motion transitions), raw mutable state for the shader's per-frame rAF read, CSS-var publishing for the parallax bands. Zero getComputedStyle reads in the shader's draw loop.
Accessibility. Every planet anchor is ≥44×44px (WCAG 2.5.5), with a 1.5px focus ring at --tone-quest 40% opacity. Tab order: audio toggle → 5 planet anchors → (chevron + narration skipped, not focusable). Reduced-motion gracefully degrades every animated component.
Contract coverage. 129 cosmos contract tests across 5 files (cosmos-shader, cosmos-chrome, cosmos-chrome-contract, cosmos-narration, cosmos-entry-timeline). Playwright runtime tests for hit-targets, no-pure-black sampler, console clean, 4px blur silhouette. Voice-doctrine compliance test pins the six narration lines exact + bans 24 SaaS phrase patterns.
Plan: docs/plans/2026-05-21-phase-1.20-homepage-cinematic.md. Six sub-phases (1.20a doctrine lock → 1.20b WebGL backdrop → 1.20c recomposition + scroll store → 1.20d quiet chrome → 1.20e full journey → 1.20f verification gate). 69 plan tasks total, all green.
2026-05-13 — Homepage scroll scaffold cleanup
Missing assets stopped showing their scaffolding.
Production placeholders retired. Deleted the chapter placeholder component and CSS. Asset-gap chapters now keep invisible render-layer contracts and documented asset slots, but no labeled route scaffolds render on the public homepage.
Portal prompts gated. Route affordances now stay inert for asset-gap chapters until the focal object is promoted. The DOM still carries canonical route links for contract coverage, but visitors cannot click prompts that do not have a real threshold object.
Scroll continuity added. Added a shared continuity layer for horizon veiling, lower-depth shade, and chapter-boundary shading using existing light and scroll signals. It improves the foundation without inventing fake route scenery.
2026-05-13 — Overlook lookdev and source-scene foundation
The lookout got a source table.
Lookdev gate. Added pnpm validate:overlook-lookdev for the ignored Poly Haven source pack: two CC0 sunset HDRIs and six CC0 PBR material sets, each with the expected source URLs, local asset-cache/ paths, and texture maps.
Overlook source contract. Added docs/asset-briefs/overlook-scene-foundation.md and wired the active homepage scene data to the expected HDRIs, textures, Fab roles, and canvas overlay passes.
Fab list tightened. The Overlook Fab intake now requires seven object families: telescope, bench, signpost, map kiosk, trash bin, safety fence, and small-junk scatter. The fence was missing; without it, the cliff edge has no believable depth wedge.
Ambient overlay foundation. The shared homepage canvas now gives the Overlook a source-aligned overlay family: dust motes, low fireflies, a rare meteor scratch, and basin heat-haze lines. These are ambient proof layers, not a replacement for Blender render passes.
2026-05-13 — Sky proof gates: forced events and Fab intake
The rare weather can now be inspected on purpose.
Session-only event preview. The skyline has a QA override at reabal_skyline_event_preview_v1, with a matching reabal-skyline-event-preview event. Storm, eclipse, aurora, comet, and meteor-shower modes can be forced for screenshots without making a public control. The event overlays now expose stable test hooks, and comet, eclipse, and meteor-shower visibility follows the same shift-hour override as the shader.
Desktop sky-spine matrix. Added a production-homepage Playwright matrix for the first viewport across all eight time-of-day modes plus all five forced rare events. This is separate from the older /dev/skyline baselines; it guards the active HomeExperience path.
Fab intake gate. Added an Overlook Fab intake template plus pnpm validate:overlook-fab-pack. Fab props now have a source-side manifest gate before Blender staging: license file, source URL file, preview, model, notes, no baked text, no complete scene, no public paths, no versioned final slugs.
2026-05-13 — Sky spine proof: event air and eight-mode visual gate
The sky stopped treating rare weather as an overlay sticker.
Atmospheric event uniforms are active. SkylineShell now passes the picked atmospheric event into SkyCanvas; the single WebGL shader consumes uStormFlash, uEclipseProgress, and uAuroraPhase. Storms darken and flash inside the air mass, eclipses chill daylight around the sun, and auroras fold into the upper sky. DOM overlays still provide readable event objects, but the sky itself now changes.
Eight-mode desktop visual proof. The homepage visual smoke suite now pins the skyline through deep-night, predawn, morning, midday, afternoon, golden-hour, evening, and night using the existing shift-hour override. Each mode verifies the WebGL canvas is mounted and nonblank.
Rot cleanup. Deleted untracked retired signal/handoff docs, old skipped tests aimed at the removed public inbox lane, and unledgered public staging asset folders. Tracked homepage-asset-gaps.md remains the active asset-gap source because it matches the Fab/Blender 2.5d pipeline.
Audit chain: skyline-foundation / home-experience / source-asset hygiene / typecheck / lint / assets / canonical / desktop Playwright visual suite / production build all green.
2026-05-10 — Skyline weather coupling: real Canberra sky in the scene
The visitor's actual sky shows up on screen.
Live weather feed. lib/skyline/weather.ts + lib/skyline/use-weather.ts pull Open-Meteo current conditions for Canberra Parliament House (-35.28, 149.13) on a 10-minute cache (matches BoM update cadence). No per-visitor lat/lon collected — fixed coords, no API key, no analytics; localStorage cache survives reloads. WMO weather code → categorical condition (clear / partly-cloudy / cloudy / overcast / rain / thunderstorm / snow / fog).
Cumulus density buckets bound to weather. clear → 9 clouds (3 per band × 3 bands), partly-cloudy → 15, overcast → 24, rain / thunderstorm → 36 (new heavy bucket — 12 per band, properly saturated storm front). Tone hour-keyed on top of bucket; visibility multiplier pushes silhouettes darker on overcast / rain / fog.
Wind-driven cloud drift. Open-Meteo wind_direction_10m flips drift sign (clouds blow eastward when wind is from the west, vice versa). wind_speed_10m (km/h) scales drift duration via --cloud-wind-factor: calm = ~320s per band, gale ≈ ~80s. The visitor's actual sky's drift pattern shows up.
Sun rays weather-dimmed. New uSunObscured shader uniform distinct from uCloudCover — thunderstorm 0.95, rain 0.85, fog 0.55, overcast 0.35, others 0. Multiplied with cloud-cover dim so storm conditions push rays to ~5% (sun trying to break through), overcast keeps a soft glow, clear day rays stay sharp. Always-on baseline kept (rays never zero — a hint of light always reaches through atmosphere).
Rain overlay. `atmospheric/rain-overlay.tsx` — canvas-2D streaks. Density scales with precipitation (mm/hr): light rain 120 streaks, downpour 360. Wind direction tilts streak angle. Self-gates on condition === "rain" || "thunderstorm". Reduced motion → unmounted.
Fog overlay. `atmospheric/fog-overlay.tsx` — two cool-gray gradient bands drifting laterally over the lower 60vh. Pure CSS, no canvas, no rAF. Self-gates on condition === "fog". Reduced motion freezes the drift but keeps the gradient.
Audit chain: typecheck / audit:design / audit:skyline-drift / audit:asset-treatment / audit:assets / audit:canonical all green.
2026-05-10 — Skyline immersion pass: dramatic scroll dolly, atmospheric haze, lit windows, curated tree, firefly cluster
The camera pulls forward; the night fills in.
Dramatic scroll-depth camera dolly — bumped multipliers ~3× across the parallax stack so the scene reads as cinematic, not nudged.
- Mountains: near -22vw + 1.12× scale (was -8vw + 1.0×), mid -14vw + 1.08× (was -5vw), far -8vw + 1.04× (was -3vw), distant -4vw + 1.02× (was -1.4vw). Both procedural + curated bands track in lockstep.
- Cityscape: mid band -10vw + 1.06× (was -4vw + 1.0×), far -6vw + 1.04× (was -2.4vw), distant -3vw + 1.02× (was -1.2vw).
- Cliff outcrop: 1 → 1.14× scale (was 1.06×) + -5vw push (was -2vw).
- Billboard: 1 → 1.10× (was 1.04×) + +4vw push.
- Lone tree: +12vw drift + -3vh lift + 0.78× scale (the across-the-gap tree pulls visibly into the distance with scroll).
Atmospheric haze on far/distant bands — filter: blur(0 → 1.4–2.4 px) + saturate(1 → 0.68) keyed to --scroll-depth. Far + distant mountain ranges + far + distant cityscape bands all participate. Reads as the camera dollying forward through deepening haze, not a flat 2D pan.
Procedural mountain fade-on-scroll — .range opacity scales 1 → 0.58 with scroll so curated hand-drawn peaks dominate as the camera pulls forward. Procedural ridgeline supplies horizon texture + load-state fallback; curated supplies the hero silhouette character.
Curated tree silhouette over the lone tree — `foreground-vantage.tsx` overlays a deterministic curated tree from CURATED_TREES (picked via pickCuratedAsset(pool, 'foreground-vantage-tree')) on top of the procedural <LoneTree> fallback. Procedural paints during the brief curated-fetch window; curated overlays once resolved. Same silhouette family, hand-drawn character.
Lit windows ambient layer — new `lit-windows.tsx` renders 32 deterministic warm-yellow pinpricks at 12–28vh from bottom across 0–100vw. Each twinkles on a 4–11s independent period with a per-window phase offset and per-window warmth (cool LEDs → incandescent amber via OKLCH). Self-gates on hour: full intensity at deep-night + night, fades up through evening, off during the day. Reduced motion freezes the twinkle but keeps the lit mask visible. Mounts inside <CityscapeLayer> between the curated city silhouette behind and the procedural downtown in front so the windows read as embedded in the silhouette, not floating over it.
Firefly cluster near the billboard hill — `firefly-band.tsx` distribution biased 70% toward the left side (x ∈ [4, 28]vw, y ∈ [10, 24]vh — exactly where the billboard plants on the BL cliff) with 30% scattered across the remaining viewport. Reads as "fireflies on the hill near the billboard, plus a few drifting across the meadow at the edges". Spawn rule unchanged — still summer evening / night / deep-night, high-quality tier only.
uSeason warning fix superseded — uSeason now earns its place. The runtime binds it again and the shader consumes it through seasonal sky, haze, and cloud tint helpers, so the documented uniform contract matches the shipped WebGL program instead of leaving a declared-but-unused uniform.
Audit chain: typecheck / audit:design / audit:skyline-drift / audit:motion / audit:assets / audit:asset-treatment / audit:canonical / audit:audit-discoverability / audit:hydration → all green. Spawn-rules 51 + skyline-foundation 137 + light-discipline 20 = 208 tests passing.
2026-05-10 — Skyline composition pass: GTA-Vinewood layout, cloud lighting, curated silhouettes, canvas stars
The horizon learned to be lit. The billboard moved where it always belonged.
Round 1 + 2 of rey's curation drop graduated 211 illustrations through scripts/trace-png-potrace.mjs (alpha-mask + sharp + potrace fallback when vtracer isn't available), scripts/split-svg-pack.mjs (bbox-proximity union-find clustering — viewBox-cropped per cluster), scripts/treat-svg.mjs (the four-layer pipeline per docs/asset-pipeline.md: oklch / filter / overlay / asymmetry-rotation), and scripts/.svgo-aggressive.config.mjs (post-treatment compression to the 50 KB per-file budget). Surface map: skyline (horizon, foreground, clouds, cityscape, ambient-life), arcade (cabinets, controllers, neon-signs), lab (glassware, signage, instruments), cottage (misc).
Family lockdown. /arcade/cabinets locked to isometric only; the 16 face-on slot-machine cabinets that initially graduated were dropped post-curation per docs/asset-pipeline.md "one style family per surface" rule (sources sit in gitignored asset-cache/inbox/illustrations-rejected/). Six corporate-observation lab packs rejected at curation per the lab register being "skunkworks, not corporate observation theater." Total kept: 124 round-2 graduations across 7 surfaces.
Wired into the homepage: mountain-backdrop adds curated <CuratedMountainRange> per depth band beside the procedural <MountainRange> (5 hero peaks: distant-left, far-center, mid-right Hollywood-sign bearer, near-left + near-right). Cityscape adds <CuratedCityscapeBand> repeated at three atmospheric depths behind the procedural downtown. Live in `mountain-backdrop.tsx` + `cityscape-layer.tsx`.
Image-2 GTA-Vinewood layout. Billboard mirrored from right to left (billboard.module.css left-anchored). Cliff outcrop + wooden fence mirrored to BL. Lone tree stays right (the "across the gap" tree). Wooden fence extended to 10 posts with depth taper — post height drops 60% across the line, thickness drops 32%, opacity drops 28% — reads as a parallax-foreshortened leading line out toward the city sprawl. Hollywood sign repositioned from left: 12vw to right: 18vw so it rides the right-anchored curated mid-mountain silhouette.
Cloud lighting story in the shader. `sky.frag.ts` cloud band extended with three-tone vertical lighting: warm-rim where the cloud cap faces the sun (top-edge probe), neutral body fill on dense regions, cool desaturated-purple underbelly where the lower edge sits in shadow. Warmth gaussian peaks at hours 6.5 + 18.5 (sunrise / sunset) — midday clouds stay neutral cream, deep-night clouds stay flat dim. Adds the "painted clouds" feel of the GTA reference image 3 without introducing a second shader pass; samples the same fbm field at vertical offsets for the lighting probe.
Billboard rebuild per rey's plank-and-post structure. `weathered-billboard-frame.tsx` — 4 planks with per-plank rotation in [-3°, +3°] from asymmetryRotation(seed), per-plank width variation (510–516 px), per-plank xOffset jitter (-2..+1), unique <clipPath> per plank with chipped/dented bottom edge, per-plank <g transform="scale(...) skewX(...)"> so the shared filter-grain-coarse samples a different region of its noise field per plank (distinct grain without inline filter defs). Inter-plank gap shadow as hairline at the bottom of each plank. Two crossbeams (top + bottom) where there was one. Knot-hole imperfection per plank.
Canvas `<StarField>` particle. `star-field.tsx` — 180 procedural stars in the upper 65% of the viewport with twinkle phase + period + warmth + base radius per star. Brighter stars (radius >1.4) get a subtle 4-point glint cross. OKLCH warm-cool color continuum across the field. Wired into the spawn-rules system as a new starField habitat — gates on quality tier + time-of-day (deep-night = 1.0, night = 0.85, predawn = 0.55, evening = 0.45, golden-hour and day = 0). Reduced-motion freezes at first paint. Additive to the shader's procedural starfield, so the two layers complement rather than duplicate.
Cleanup. Deleted components/rehaul-flight-ribbon.tsx (Wave R artifact, no imports anywhere) and the empty app/dev/home-signal/ directory (the SUPERSEDED stub lives in docs/home-signal-system.md per the salvage ledger, but the empty Next route directory was dead). Asset audit allowlist updated to allow public/illustrations/ as a graduation directory.
Audit chain. typecheck / audit:design / audit:skyline-drift / audit:motion / audit:assets / audit:canonical / spawn-rules tests (51 passed) all green.
2026-05-10 — Skyline light-pass discipline
The whole scene reads under one global light now.
Lane: light-and-atmosphere v2 — established the platform's first global per-hour light discipline as a five-channel CSS-variable contract on :root, plus two signature overlays mounted in BAND-HAZE.
Light discipline. lib/skyline/light-discipline.ts exports lightDisciplineForHour(hour) returning OKLCH strings for --light-ambient / --light-rim / --light-shadow plus scalars for --light-fog / --light-key. Smoothstep boundaries across 13 anchor hours including the golden-hour 18:24 peak and blue-hour 5:45 + 19:25 beats; shortest-arc hue interpolation so hue crossings (e.g., 350° → 10°) don't sweep the long way. lib/skyline/use-light-discipline.ts publishes the variables on :root every 90s and honors the dial shift-hour override.
Golden-hour overlay. src/features/home/skyline/light-pass/golden-hour-overlay.tsx — single full-screen linear-gradient sweep with a sun-direction-aware rake angle (100° → 112° → 100° across 17:00..19:30), peaks at 18:24 (opacity 0.92), mix-blend-mode: soft-light. Two-layer composition using --light-rim for the orange ember and --light-ambient for the lower wash.
Blue-hour overlay. src/features/home/skyline/light-pass/blue-hour-overlay.tsx — compressed cool wash anchored to the bottom 42% of the viewport, mix-blend-mode: hue so the upper sky stays as the shader paints it and only the hue shifts cool. Same component handles both beats (predawn 5:45 + dusk 19:25); data-blue-hour distinguishes them for future beat-specific tuning.
Shader retunes. sky.frag.ts predawn / dusk horizon stops nudged toward blue for clean hand-off into the overlays; sun bloom pushed to a hotter raking-orange matching the rim signature; deep-night moon picks up a silver-rim halo (silverFactor-gated, h<5 ∪ h≥22) with a 4% disc-edge softening so the rim has something to bloom against; midday gains the doctrinal cool-white haze; sunrise/sunset glow emphasis lifted to hold hero strength under the overlay sweep; new dawn cool-blue under-line at the lowest 6vh of the horizon glow band, lives 4.6..7.0.
Act vignette tinting. acts/act-vignette.module.css consumes the discipline additively — readout color absorbs ~18% of --light-shadow (cools at golden, deepens at night), artifact wrap accepts a brightness trim from --light-fog so it embeds in the haze, and a static drop-shadow(0 0 1px var(--light-rim) at 30%) rim hairline picks up the global rake.
Tests. tests/unit/light-discipline.test.ts — 20 tests pinning anchor-hour values, smoothness invariant across the day, wrap-at-24 continuity, golden-hour + blue-hour intensity envelopes, monotone non-decreasing 5..12 + non-increasing 12..22 key intensity. tests/unit/skyline-foundation.test.ts adds 13 pins for the new shader features and shell mount.
Docs. docs/the-skyline-system.md adds a "light-pass discipline" section; docs/the-skyline-tech-stack.md registers the new files in the canonical naming convention; docs/storage-keys.md updates the reabal-dial-v1.shift-hour consumer list; CLAUDE.md Composition section points agents at the canonical doc; README.md and docs/current.md add the light-pass to the homepage surface description.
2026-05-10 — Skyline handcrafted SVG detail
The horizon got its hand-drawn body.
Lane: handcrafted-svg-detail — added a procedural SVG library at src/features/home/skyline/svg-library/ composing into the existing BAND-zone markers without touching other lanes' zones.
Clouds. Three parallax cumulus bands at hi/mid/lo depths via cumulus-shapes.tsx (10 hand-crafted silhouettes with separate body/rim/shadow paths), cumulus-band.tsx (5 clouds per band, per-hour rim-light from rimLightForHour()), cloud-layer.tsx (composes the three at distinct atmospheric perspective levels, polls lib/atmosphere/atmosphere.ts on 90s cadence). (min-width: 1024px) gates dense extras on desktop.
Mountains. mountain-range.tsx generates an asymmetric ridgeline via seeded RNG — sharp peaks for ridges, quadratic saddles between, scree-streak slope marks, notched-summit imperfection. mountain-backdrop.tsx composes four ranges at distant / far / mid / near depths with atmospheric perspective (0.32 → 0.95 saturation). Moonlit ridgeline accent uses stoneFace instead of silhouetteDeep so the silhouette reads against the shader sky at deep-night.
Cityscape. downtown-silhouette.tsx (56 procedural buildings dense in the core, hero radio-tower with pulsing antenna tip, window flicker only at hour <7 || >=18), suburb-sprawl.tsx (220 scattered houses with optional pitched roofs), winding-roads.tsx (3 quadratic-bezier paths weaving toward a central vanishing point with sampled headlight pricks at night). Seeds canonicalized in lib/skyline/cityscape-seed.ts.
Foreground. cliff-outcrop.tsx (hero rocky silhouette at bottom-right with stratification + chip-corner imperfection), wooden-fence.tsx (8 weathered posts with broken-rail imperfection between posts 3 and 4), lone-tree.tsx (importable primitive, 3 variants — windswept / spruce / deciduous, configurable scale + lean), foreground-vantage.tsx composes the three.
Billboard upscale. weathered-billboard-frame.tsx extracts the inline 320×240 SVG into a hero-scale 640×480 primitive with diagonal back-bracing struts (X-pattern behind the planks) and three discrete per-bulb halos rather than one merged glow. billboard.tsx refactored to consume it; the useBillboardToday hook contract stays intact.
Hollywood-sign re-anchor. Moved from the now-deleted BAND-HORIZON-SIGN zone into BAND-MOUNTAINS, positioned on the mid-range silhouette at smaller scale (9.5rem desktop, was 14rem). Parallax-tracks the cursor field at the same rate as the mid mountain range.
New shared utilities. src/features/home/skyline/svg-library/treatment.ts exports SKYLINE_FILTERS (filter id namespace), SKYLINE_TONE (OKLCH palette tokens), rimLightForHour(), shadowToneForHour(), asymmetryRotation() (deterministic ±3°), pickImperfection(), makeSeededRng() (xorshift32). lib/skyline/use-desktop-density.ts exports a hook for the (min-width: 1024px) matchMedia pattern.
New audit gate. pnpm audit:bandzones (script in scripts/bandzones-audit.mjs) verifies all 14 BAND-zone markers exist in skyline-shell.tsx. Wired into prebuild.
Tests. tests/unit/handcrafted-svg-detail.test.ts adds 63 file-shape contracts covering the four-layer treatment compliance: OKLCH-only enforcement, global filter id usage, asymmetry rotation, imperfection presence, reduced-motion bails, atmospheric perspective tiers.
2026-05-10 — Skyline ambient life rebuild
Three solo birds got tired of carrying the whole sky.
Eleven-habitat particle system replaces the legacy <AmbientLife /> (three solo birds + three solo bats + two mist bands) with a habitat-driven layer at BAND-AMBIENT-LIFE. Pure spawn rules in `lib/skyline/spawn-rules.ts` (48 unit tests) map (timeOfDay, season, quality, reduced-motion, activeAct) to a per-habitat enable matrix; the composition root at `src/features/home/skyline/particles/ambient-life-layer.tsx` mounts only what fires.
Habitats — bird-flock (3 V-formations, 5+7+9 birds, day modes), bat-swarm (14 erratic bats, evening), firefly-band (28 warm-amber points, summer evening/night), mist (predawn, any season), dust-motes (14 around foundation lamps), moths-around-light (6 per active scroll-act lamp), falling-leaves (6 autumn leaves with fall+sway+twirl), pollen-haze (36 chartreuse points, spring predawn/morning), owl-swoop (one-per-session deep-night silhouette pass), distant-train-smoke (40% per-visit far-horizon plume), cursor-breath (12px idle ring at cursor, Henry-Heffernan-tier diegetic).
Discipline — pure CSS-keyframe animations only (no rAF unless gated to high tier), OKLCH colors only, pointer-events: none on every layer root, reduced-motion still composition for every habitat that has one. Quality-tier gating: low → only cursorBreath; medium drops fireflyBand + pollenHaze + owlSwoop + distantTrainSmoke; high enables everything its time/season window allows.
New storage — reabal-owl-swoop-v1 and reabal-train-smoke-v1 (sessionStorage) registered in `docs/storage-keys.md`.
Legacy retired — src/features/home/skyline/ambient-life.tsx and its module CSS deleted. Single occupant of BAND-AMBIENT-LIFE is now <AmbientLifeLayer />.
2026-05-10 — Skyline promoted to /
The doctrine becomes the door.
Phase 4 promotion shipped. app/page.tsx now mounts <SkylineShell /> directly. The homepage is the skyline: WebGL2 fragment shader earthward sky across eight lib/atmosphere/atmosphere.ts modes, six rooms as horizon landmarks, scroll-pinned per-act vignettes (worlds glasshouse, arcade marquee, lab structure, archive outbuilding, cottage), Phase 7 cinematic moments (storm, eclipse, aurora, comet, meteor-shower) when the picker fires, billboard with daily AI-rotated poster + previous-poster continuity flash, hilltop sign, and visit-pattern-aware caption.
Legacy migration. <HomeScene /> and the legacy home-shell scene tree it composed are no longer imported anywhere shippable. Salvage-ledger row carries a delete date one deploy cycle out (target ≥ 2026-05-12) with the four pre-deletion checks documented inline.
Phases shipped end-to-end this run. Phase 1 (sky foundation), Phase 2 (alive billboard + hilltop sign), Phase 3 (scroll foundation, letterbox cuts), Phase 4 (act vignettes), Phase 6 (interaction polish), Phase 7 (five cinematic moments), Phase 8 (a11y substrate + canvas-2d fallback + scene-description aria-label + forced-colors + prefers-contrast), Phase 9 (memory primitives + caption variants + signature seed + previous-poster flash). Audio bed integration (Phase 5) lands as substrate ready for scene wiring.
2026-05-10 — Skyline takes the homepage
Signal lost the argument. The sky earned the room.
Homepage doctrine — docs/the-skyline-system.md lands as canonical. The homepage is now an earthward sky dynamic by visitor's local hour (eight lib/atmosphere/atmosphere.ts modes), with rey's six rooms visible as architectural landmarks scattered across the horizon and starfield. Kinetic-typography rotating headlines (name-reveal.tsx restored, lib/hero-headlines.ts as the canonical line list) anchor act 1. Scroll-pinned cinema cuts via existing reabal.linger/reabal.cut easings drive the act sequence.
Signal direction superseded — docs/home-signal-system.md retired with a SUPERSEDED header pointing to the skyline. The signal-machine direction failed because it positioned the visitor as someone receiving a transmission from outside an interior the doctrine spends fifteen documents calling rey's. The desk-as-homepage-hero direction (briefly explored, never shipped) also retired — desks don't change. The sky does.
Cottage doctrine — docs/the-cottage.md lands as canonical for /about. The desk metaphor moves there: fifteen interactive objects in three depth bands (lamp, journal, book with wax seal, drawer, terrarium, cork board, mug, headphones, clock, typewriter, photo frame, poster, chair, window). Window pulls live atmosphere from the homepage skyline. Existing page-wide eggs anchor onto cottage objects; retired desk eggs (Dune mug, Bat-Signal lamp, OBLIVIATE post-it, etc.) re-home from docs/easter-eggs.md.
Asset pipeline doctrine — docs/asset-pipeline.md lands as canonical. GetIllustrations Lifetime ($599 one-time) is the single source for site illustrations across all five tier registers and the cottage. The four-layer treatment is mandatory: oklch override + svg filter pass + canvas animation overlay + asymmetry rotation with imperfection. Two new audits (audit:asset-treatment, audit:asset-cohesion) join the prebuild chain.
Lab register correction — /lab shifts from "behavioral observation theater × Severance" to "top-secret skunkworks, off the books, lived-in." Materials stay (cold steel, wired glass, CCTV, caution stripes); the condition changes from pristine to used. References: X-Files Mulder's basement, Annihilation Area X, Primer garage.
Doctrine sweep — CLAUDE.md, AGENTS.md, docs/current.md, docs/studio-os.md, docs/themes.md, DESIGN.md, PRODUCT.md, docs/audits.md, docs/asset-ledger.md, docs/salvage-ledger.md, docs/easter-eggs.md, docs/storage-keys.md updated to reference the new doctrine and retire the old.
Headline #11 exception — PRODUCT.md adds a narrow exception for lib/hero-headlines.ts line 11 (this is where my brain leaks. mind your step.). The "my brain" trigger remains banned everywhere else.
New storage — reabal_cottage_v1 (cottage interior state), reabal-dial-v1.shift-hour (sessionStorage visitor override of skyline time-of-day), GETILLUSTRATIONS_API_KEY (env var, never committed).
2026-05-09 — Archive gets the keys
The essays stop pretending the old route name was the place.
Archive route — Promoted /archive as the canonical public home for interactive cinematic essays. /essays now exists only as a compatibility redirect.
Registry language — Retiered essay content to archive while preserving artifactKind: "essay" so the place and the format stop fighting.
2026-05-06 — Foundation routes get a spine
Public shell work moved ahead of artifact work. The route foundation now has a machine-readable contract.
Foundation contract — Added lib/foundation-routes.ts and audit:foundation so the public shell routes stay declared in one place. Superseded on 2026-05-09 when /archive replaced the old essays path.
Sitemap — app/sitemap.ts now derives static public routes from the foundation contract, including /map.
Map — Rebuilt /map from a generic link grid into a Studio OS orientation wall backed by the foundation contract and the public registry.
Studio logs — Rebuilt /now as a live studio-status surface and /changelog as a cinematic ledger reader while keeping docs/changelog.md as the source.
Route rail — Added a subtle desktop foundation route rail backed by lib/foundation-routes.ts, so public shell navigation is contract-derived instead of hand-written per page.
Catalog landings — Promoted /worlds into the Glass Garden and the long-form writing surface into a dedicated landing guarded by the foundation route contract. The old projection-booth metaphor was retired.
Homepage mobile — Fixed catalog horizontal overflow and removed the desktop-only dial from touch/mobile viewports so the home catalog no longer has a floating control sitting over the rows.
Homepage threshold — Replaced the rejected machined-console first viewport with a procedural space threshold: deep parallax atmosphere, vector route paths, four room beacons, and no gold dashboard machinery.
Docs — Updated Studio OS, roadmap, and audit docs so /now is no longer contradicted by stale roadmap language.
2026-05-06 — Studio OS becomes canonical
The current direction is no longer a numbered rebuild. `docs/studio-os.md` is the route/product authority; old versioned implementation plans were deleted from the working tree.
Governance — Added the Studio OS source-of-truth doc, salvage ledger, and storage/event registry. Cleanup now starts by classifying systems as keep, merge, incubate, archive, or delete-only-after-proof.
Rot purge — Deleted obsolete docs/plans, historical render/primitives docs, tracked coverage output, and old visual baseline specs named after retired directions.
Routing — /dev/* is explicitly admin-only in the request matcher. Registry access remains enforced by canViewExperiment().
Registry — Completionist route tracking now derives public live slugs from lib/experiments.ts, so trophies follow the registry instead of a stale hardcoded list.