This file is rewritten (not appended to) every shipment. It is the single source of truth for: what’s broken right now, what changed last time, and which file owns which visible piece of the page. If you are a new session (or Aaron editing by hand), start here before touching code.
Last updated: 2026-07-21 later (Hangar bay real art shipped — R2 continues.
Aaron sourced 4 folders of AI-generated pixel-art hangar renders — warm/cool
bay interiors + warm/cool blast doors, ~4-6 candidates each. Reviewed all 18
live via an Artifact mockup [full gallery + a real-scale, real-timing
interactive dock/launch demo]; Aaron picked the gpt-image render in all
four categories and said keep the ship art exactly as-is [option A from the
medium-honesty callout — ship art stays clean vector, no redraw]. Shipped:
.bay/.bay-door now use the real art [docs/orbit/assets/hangar/] in
place of the old flat gradient/color placeholders; the old flat .bay-floor
stripe overlay was removed since the new art paints its own floor. Verified
live in both themes, desktop + mobile, full dock→launch cycle, zero console
errors.)
Prior: 2026-07-21 (R1 batch shipped — R1d micro-bugs [issue #2 swept
projectile collision, issue #11 banner timing, issue #19 re-verified already
fixed, issue #12a left as-is per standing instruction], R1g flight-manual
auto-open on first entry, R1i return-to-hangar-doors control, R1j master
audio mute unifying SFX + music. All four verified headless, zero console
errors, both themes + mobile. Merged in a parallel session’s PLAYTESTS.md
docs commits along the way — clean, no conflicts. Still open: R1a blocked on
Aaron creating aarons-3d-portfolio by hand; hangar-bay door/interior art
prompts delivered to Aaron, awaiting sourced images.)
Prior: 2026-07-20 night (THE COLDEST CALL IS LIVE: the full playable
game now replaces the placeholder at docs/orbit/coldest-call/ — real terrain
strips, Aaron’s five chosen character sprites, the orbit build’s real ship art
(Swordfish II / Rocinante) parked at the landing site, full dialogue + clay
workstation + debrief loop. Entry: the AlphaForge mission door + PLAYABLE
BUILD 05 deck card. See RCC in §3. Same day, main-build thread: R1f + R1h
shipped — branded boot loader, real social share card; scanner cursor tilted
like a real pointer + shrunk twice more (still needs Aaron’s real-browser
confirmation — see the known-issues note below, screenshots can’t show a
custom cursor); Rocinante mirrored to face left + launches left, opposite
Swordfish’s right. Earlier today: R2a ship art, R1c scanner cursor, R1b
GitHub link, employment-status correction — see SHIPPED below. R1a still
blocked on Aaron: the GitHub App this session runs under can’t create
repos — aarons-3d-portfolio needs to be created by Aaron by hand, then
handed back. Full remaining roadmap — R1–R6 — is unchanged below; still
paused except for the items Aaron explicitly greenlit.)
Later same night: found and fixed the REAL cause of the cursor-on-ship overlap (an invisible dead-zone bug, not a sizing issue — see the dedicated entry below); also root-caused why the scanner still doesn’t feel like a pointer even shrunk/angled (the source art’s shape has no true tip at any crop), proposed pivoting to a small vector glyph, and — Aaron said go — built and shipped it (R1c v3, see entry below). Merged in “The Coldest Call” (built by a separate session directly on the live branch, in three separate rounds while this work was in flight — social share card, then a share-card content fix) — all clean merges, nothing lost either side.
Prior: 2026-07-19 late, R1 grew six items across two rounds (return-to-hangar, master audio mute, readability→R4a, mobile rework→R4c, picker exit directions, branded boot loader, manual auto-open, social share card); T14 folded into R4a; backlog section dissolved — everything lives in a phase. Before that: the v3 design brief (“Design Notes 2.zip”) landed and shipped in one batch — Phase 2 Stage 5, Phase 4a/4b/4d/4e, the T15 profile window, the §5 “5F” lockup, decoded nav, the T17A signoff.
Orbit used to be one 1,072-line orbit.js. It is now split into labeled files
under docs/orbit/js/, loaded in this exact order (order matters — later files
call functions defined in earlier ones):
| # | File | Owns | Typical edits you’d make here |
|---|---|---|---|
| 1 | 01-config.js |
Theme colors (Swordfish/Rocinante palettes), the 4 station definitions + copy, shared tiny helpers | Change a theme’s accent color; edit station name/tagline/body copy; add a new station |
| 2 | 02-sound.js |
All synth sound effects + the music player (play/pause/next, track list wiring) | Change SFX pitch/volume; change how the player behaves |
| 3 | 03-progress.js |
Achievements/localStorage, the two popup systems (center banner + right-side achievement-toast stack), and the rewards system (earnFromCategory/factDrop — categorized facts/stats/quotes) |
Change popup timing/behavior, add new achievements, change milestone thresholds, change how a reward gets picked within a category |
| 4 | 04-world.js |
Builds the planet/sun/ronin DOM + orbit rings, starfield canvas setup, shared runtime state variables (ship position, bounty, current theme, etc. — used by nearly every other file) | Add/remove a planet from the map; change orbit speed/size |
| 5 | 05-combat.js |
Fire input, weapon switching, asteroid spawn/split/reward, aim direction | Anything about shooting, asteroids, weapon behavior |
| 6 | 06-flight.js |
Proximity lock-on (the “TARGET · LOCKED” logic), the landing sequence, triggering hyperwarp | Change lock-on distance, landing animation timing |
| 7 | 07-environments.js |
The station side-panel (sections list) and every painted planet scene (Mission/sun, AlphaForge/city, Life/forest, Craft/ocean, Notes/lot, hidden desert) | Anything about what a planet’s surface looks like — this is the biggest file, ~360 lines, all drawing code |
| 8 | 08-ui.js |
Theme/ship switching (incl. the hangar-bay swap animation), minimize/expand behavior for the console and player, the next-reward bounty HUD, the ◈ LOG panel, and the reward signal modal (category picker + reveal) | Change how ship-swap or minimize/expand works, how the log or reward modal render/open/close |
| 9 | 09-main.js |
The master per-frame loop (ship movement, stars, particles, warp streaks, HUD text updates) + page boot/resize/deep-link + the window.__orbit test hook |
Ship movement feel, star density, anything that runs “every frame” |
Also: index.html holds all CSS (organized in the same rough order as the file
list above, with /* ─── section ─── */ comment banners) and the page markup.
audio/tracks.js and data/facts.js are separate small data files you can
hand-edit directly (documented inline in each).
Pending content, not a bug: the contact card’s photo landed —
docs/orbit/assets/aaron.jpg now exists (pulled from a Google Drive link
Aaron shared; a directly-pasted chat image doesn’t reach this session’s
filesystem, so that first attempt couldn’t be picked up, but a Drive link
or direct URL works fine). The “Other builds” section is still a
placeholder (“More coming soon…”) until there are other sites to link —
see .contact-more-list in index.html.
Why this split exists: the previous single-file version made it impractical to find or safely change one system without scrolling past nine others. This split is a pure relocation — every line of code was moved, not rewritten; behavior is verified identical to before the split (see §3).
These were reported by Aaron and root-caused by reading the actual shipped code (not guessed). Each will be worked one at a time, per Aaron’s instruction — do not batch-fix these without his go-ahead on each.
| # | Issue | Root cause (confirmed) | Lives in |
|---|---|---|---|
Was: two independent calculations for “forward” that didn’t have to agree — the ship’s visual rotation (ra) is smoothed/eased toward the cursor each frame, but the bullet’s old aimDir() computed a fresh, unsmoothed bearing straight to the raw cursor position, ignoring ra entirely. While turning, those two numbers are rarely equal. Fix: aimDir() now just returns noseDir() — the exact same value that rotates the sprite — so a shot can no longer disagree with where the ship is visibly pointing. Verified numerically: angle between shot direction and nose direction is exactly 0° in settled/still, mid-turn, and continuously-moving tests. |
05-combat.js (aimDir/spawnProj) |
||
| Separate bug, found from Aaron’s screen recording: the anime speed-line effect (shown when flying fast) deliberately started each line 90–470px away from the ship and drew it extending even further away — meaning it was never touching the ship at all, by design. Looked exactly like a disconnected effect because it was. Fix: lines now start 14–34px from the ship (right at the hull) and extend a shorter, more modest distance. Verified visually: lines now visibly fan out from the ship’s position instead of floating in the background. | 09-main.js (speed-line block) |
||
Confirmed with real numbers from Aaron’s own machine via the 10-diag.js overlay: his #fx/#stars canvas elements were rendering at exactly 2x the size of his actual viewport (2698×1578 CSS pixels rendered, on a 1349×789 viewport) — and 2 = devicePixelRatio on his machine. Root cause: <canvas> is a “replaced element” in CSS (like <img>/<video>). #fx/#stars were styled with only position:fixed;inset:0 — no explicit CSS width/height. For an ordinary element that’s enough to stretch it to fill the viewport, but for a replaced element with auto width/height, the CSS spec falls back to its intrinsic size instead — which for a canvas is its width/height HTML attributes, i.e. the drawing-buffer size. sizeCanvases() deliberately sets that buffer to W*devicePixelRatio (bigger than the viewport, for a crisp HiDPI image) and was relying on inset:0 to downscale it back down via CSS — but that downscale never happens, per the rule above, so the canvas just displays its full buffer 1:1 with screen pixels. Since 1 drawn unit maps to devicePixelRatio buffer pixels, everything painted on the canvas (shots, thrust, stars) ends up rendered at devicePixelRatio× its intended on-screen position — always pushed away from the top-left corner, i.e. down-and-right, matching Aaron’s very first report. The shield-offset symptom wasn’t a separate bug at all: the shield’s own collision math is plain JS on logical coordinates and fires correctly — it only looked wrong because the shot that triggered it was being displayed in the wrong place. This is invisible whenever devicePixelRatio is exactly 1 (true of my own test browser the whole time — the reason two prior fix attempts couldn’t find it), and affects any real HiDPI/Retina-class display at any non-1 DPR, not just Aaron’s machine. Fix: sizeCanvases() now also sets c.style.width/c.style.height explicitly to the logical viewport size, forcing the intended CSS downscale to actually happen. Verified, not guessed: (1) reproduced Aaron’s exact broken numbers by emulating devicePixelRatio=2 via CDP — confirmed the canvas rect was 2x the viewport before the fix, and exactly equals the viewport after; (2) fired an actual in-game shot under that same dpr=2 emulation and read the canvas’s raw pixel data (getImageData) at the buffer coordinate corresponding to the shot’s logical position — confirmed a fully-opaque drawn pixel exists exactly there, i.e. the shot visually renders exactly where the game logic says it is; (3) re-checked devicePixelRatio=1 afterward to confirm no regression there. |
04-world.js (sizeCanvases) |
||
| — | (Superseded diagnosis, kept for context — the DPR/resize-drift fix from 2026-07-17 below was real but turned out to be treating a symptom, not this root cause) resize event, which doesn’t fire for e.g. dragging a window between displays of different pixel density |
01-config.js (metrics), 09-main.js (per-frame drift check) |
|
Hit detection was a single point-in-circle check once per frame; a fast bullet could tunnel through a hit circle between frames without ever registering inside it. Fix: new segHitCircle(x1,y1,x2,y2,cx,cy,r) helper (05-combat.js) does a swept segment-circle test — closest point on the projectile’s per-frame travel segment to the target center, compared against the radius — instead of just testing the endpoint. Applied to all four hit targets in the projectile loop (09-main.js): planet shields, the sun, the saucer, asteroids; each now captures prevPx/prevPy before the position update and tests the swept segment. Verified: direct calls confirmed correct true/false for through-center, far-away, and not-on-short-segment cases; live playtest with fast projectiles showed no more tunneling misses. |
05-combat.js (segHitCircle), 09-main.js (projectile/collision block) |
||
The sun’s hit-check used a made-up radius formula (min(W,H)*0.052) that had nothing to do with the sun’s real rendered size (SUN_STATION.r, already correctly computed elsewhere in sunMetrics() from the sun’s actual DOM width — just never used here). At a 1200×800 viewport that made-up radius was ~42px while the sun’s real radius is 60px, so a shot could fly ~18px into the visible disc without registering a hit at all. It also reused the same generic single-color spark burst as everything else, so even when it did register, there was no dedicated “sun” reaction. Fix: the hit-check now uses SUN_STATION.r (matching the exact pattern planets already use, S.r+9), and a hit triggers a purpose-built “sizzle” reaction instead of the generic burst: sunBurst() (05-combat.js) spawns multi-color hot sparks (white → yellow → orange → red) plus a few slow rising wisps, so it reads as the shot evaporating rather than bouncing off; the sun’s own DOM element gets a brief sizzling class (index.html CSS) that flashes brightness and blooms a warm glow — visually distinct from the cool-blue ring planets get when shielded; and a new Sound.sizzle() (02-sound.js, filtered noise + descending zap) replaces the generic shield-hit chime. Target readout shows “MISSION · SCORCHED” instead of “· SHIELDED”. Verified: confirmed SUN_STATION.r (60px) vs. the old formula (~42px at a 1200×800 viewport) to prove the mismatch was real; fired a projectile at the sun’s real edge and confirmed via the live game state that it’s consumed exactly there (not 18px early), the sizzling class fires, the target text updates, and the particle burst uses the new warm, multi-color palette (not the old single flat color); screenshotted the sun before/during/after the reaction to confirm the flash is visible but not blown out and fades back to normal within half a second. |
09-main.js (sun-hit check), 05-combat.js (sunBurst), 02-sound.js (Sound.sizzle), index.html (.sizzle markup/CSS) |
||
Confirmed by Aaron (who wrote the original code): .sign is position:fixed, pinned to the viewport’s bottom-left corner so it can deliberately bleed off the screen edge — pulling it completely out of normal document flow. .hint sat in normal flow in the same .bl container. Padding .bl (the prior, ineffective attempt) only pushes flow content around; .sign ignores that entirely and just paints on top of whatever’s physically in that corner. No amount of flow padding could ever have separated them. Fix: stopped trying to make the two coexist in that corner. The hint sentence no longer lives in .bl at all — it moved to a hidden-by-default popover anchored next to the station nav links (#stnav) in the header, revealed by a new “?” button (#help-btn) and dismissible via outside-click or Escape. .sign is untouched and now has the corner to itself. Verified: confirmed the popover is opacity:0/non-interactive on load, becomes visible and positioned in the header (not the bottom-left) on click, and closes again on outside-click and on Escape; read back .sign’s and the popover’s actual getBoundingClientRect() values live to confirm they’re nowhere near each other (header vs. bottom-left corner); screenshotted the open popover to confirm it reads cleanly next to the nav links with no visual collision anywhere on screen. |
index.html (markup: .navrow/.help-btn/.hint/.bl; CSS same), 08-ui.js (popover open/close behavior) |
||
| 5 | Environments look like squares/circles, colors too dark | Human silhouettes are built from plain rectangles + circles (readable as a figure, but “blocky” up close); the forest’s tree layers and background use closely-related dark greens with too little contrast between layers; the ship inside the hangar bay renders at ~40px, too small for detail to read. | 07-environments.js |
| 6 | Environments don’t visibly change between themes | The theme branch only swaps a handful of colors by one shade and toggles a thin outline — composition/layout/shapes are identical in both themes, so the difference is barely perceptible. | 07-environments.js (cel branches) |
| 7 | Ship swap: “docks, then just appears by my cursor” | The whole hangar animation happens inside the small console panel; the actual cursor-following ship is simply hidden for the sequence and reappears wherever the cursor is when it ends — there’s no connecting motion between “in the console” and “back at the cursor.” | 08-ui.js (shipSwap) |
| 8 | No popup ever seen in the Rocinante theme | CSS for both themes checked and is present/correct on both sides — no confirmed rendering defect. Leading (unconfirmed) theory: milestones are infrequent (gold asteroid every ₩2,500, saucer every 25–75s) and combined with issue #7 making that theme unpleasant to play in, Aaron may not have had a clean run of it. Needs real instrumentation, not another guess. | 03-progress.js |
Was a hardcoded <div class="dock">▶ dock & enter</div> appended to every planet in buildPlanet() (04-world.js) — theme-independent, so it showed under every planet in both themes. Fix: removed the div from the template and its now-dead CSS (.planet .dock, .planet.live .dock). Verified: queried the live DOM for .planet .dock elements and for the literal text anywhere on the page in both themes — zero matches in either. |
04-world.js (buildPlanet), index.html (CSS removed) |
||
| Banner showed for 3.6s total; on a small/narrow browser window, or if the event is off in a corner of the map, that wasn’t reliably enough time to notice + react. Fix: duration bumped 3.6s→5.2s to match the toast stack’s own timing. | 03-progress.js (banner) |
||
| Aaron’s call: the threshold stays exactly where it is. No code change; R4b’s dual unlock (Pong win OR this threshold) will give a second path to the same planet. | 01-config.js (threshold check lives in 03-progress.js) |
||
The hidden planet (Ronin/RONIN) rendered through the exact same buildPlanet() path as every regular planet — no visual distinction once unlocked. Fix: RONIN now carries a secret:true flag; buildPlanet() checks it and adds a .secret class plus two new pieces of markup — .secret-glow (a soft pulsing radial glow in the planet’s own gold color) and .secret-rings (two crossed rings at different angles/speeds, gyroscope-style) — all always-on once unlocked, independent of the normal .live proximity-hover state everything else uses. Verified: unlocked it live and confirmed the .secret class and both new elements exist with their animations actually running (animationName read back, not just “class present”); screenshotted the result to confirm it visually reads as a distinct, deliberate “this one’s different” world rather than another regular planet. |
04-world.js (buildPlanet, RONIN), index.html (.secret-glow/.secret-rings CSS) |
||
setLock() had a themeId==='sword' branch (anime title-card cut-in — never mentions “locked”) and an else (Expanse) branch that set the on-planet reticle label (#tgtlab, in .tgtbox, positioned 22px above the target) to NAME · LOCKED. That’s the theme-specific difference Aaron noticed — Bebop’s cut-in never had that word to begin with, it wasn’t removed there because it was never added there. Fix: the Expanse branch now sets #tgtlab to just the station name. The console’s own “TGT” readout (top-right NAV·COM panel) still shows “· LOCKED” in both themes — Aaron only asked about the label floating above the planet, not that HUD field, so it was left alone. Verified: forced a lock-on in each theme and read back the live text content of both #tgtlab and #target — Expanse’s on-planet label now reads just the name, the console field is unchanged, and the underlying lock-on/dock mechanic itself was untouched (same setLock/positionTgtbox flow). |
06-flight.js (setLock) |
||
Locking onto a planet in Bebop triggers .titlecard, a dark anime-style card that slides in from the left with “SESSION ##” + the station name; that element is explicitly hidden in Expanse (body.t-roci .titlecard{display:none}) and nothing replaced it, so Expanse had no equivalent moment at all. Fix: added .scancard — a new themed popout, not a reskin of the anime card: dark glass panel, backdrop-filter blur, icy-blue border/glow, and the same cut-corner clip-path this theme already uses on its banner and planet tags, so it reads as native to Expanse rather than borrowed. Shows “◈ TARGET ACQUIRED”, the station name, and its short tagline; slides in from the left on lock-on and back out after 950ms, same timing as the anime card. Verified: forced lock-on in Expanse and read back that .scancard gains the .on class with the correct kicker/name/tag text, confirmed it auto-hides after the 950ms window, and re-checked Bebop afterward to confirm its title card and this new card don’t interfere with each other. |
06-flight.js (setLock), index.html (.scancard markup/CSS) |
||
Root cause (explained last round): unlockRonin persisted Progress.d.ronin=1 to localStorage, and boot code silently re-unlocked it on every future page load regardless of the current session’s bounty. Confirmed as a real mechanism, not a guess — it meant Aaron’s browser had genuinely crossed ₩20,000 in some earlier session. Aaron’s call: it should be re-earned every session. Fix: removed the persistence entirely — unlockRonin no longer writes to Progress.d/localStorage at all; RONIN.el (unset until built, and RONIN is a fresh object every page load) is now the only guard against double-unlocking within a session; checkBounty()’s gate checks !RONIN.el instead of the old persisted flag; the boot-time if(Progress.d.ronin) unlockRonin(true); call is gone. The separate ronin_found achievement badge (a one-time “you did this” record) is untouched and still persists normally — only the planet’s visibility resets each session, not the achievement history. Verified: simulated a prior session’s saved achievement in localStorage, reloaded, and confirmed the planet is NOT built (RONIN.el falsy) despite that history; then crossed ₩20,000 within the same fresh session and confirmed it unlocks correctly. |
04-world.js (unlockRonin), 03-progress.js (checkBounty, default d), 09-main.js (boot) |
||
Aaron liked the banner’s existing look (colors/border/glow per theme) but wanted it smaller and flatter — his call on exact sizing was “what do you think is best.” Fix: kept every color/border/glow/clip-path exactly as-is, scaled down the geometry: max width 560px→440px, title font clamp(1.5rem,3.4vw,2.4rem)→clamp(1.05rem,2.2vw,1.6rem), kicker/description shrunk to match, padding 16px 40px 18px→9px 26px 11px, borders/shadows/clip-path corner-cut scaled proportionally down (e.g. Bebop border 3px→2px, Expanse corner-cut 18px→12px) so nothing looks disproportionately heavy at the smaller size. Net effect reads flatter/more rectangular, not just “shrunk.” Verified: triggered a real banner and measured its actual rendered getBoundingClientRect() — meaningfully smaller footprint than before — and screenshotted it next to the header. |
index.html (#banner/.bnr CSS) |
||
Aaron: profile/branding should stay a clear focus, just “not too big.” Fix: modest bump (~15%) to both the Bebop and Expanse wordmark font-size clamps and the station nav link clamp — sizing only, no layout/color changes. Verified: read back live computed font-size at a standard viewport to confirm the increase actually took effect (34.8px wordmark, 15.5px nav links, both at the new clamp ceiling), and screenshotted the header. |
index.html (.wordmark, body.t-roci .wordmark, .stnav a CSS) |
||
Root cause (confirmed with live measured computed styles, not source-reading): on the outbound leg, bay-open (550ms door reveal) and ship-out (500ms exit, but only 300ms opacity fade) fired on the same tick with no stagger — measured mid-run, the ship’s opacity had already hit ~0 by the time the doors were only ~56% open, so the bay always looked empty; the inbound leg works precisely because it staggers (doors get a 260ms head start on the ship). Additionally, ship-in was removed the instant the ship SVG swapped, so the new ship never sat visibly parked. Fix (mirrors the working inbound stagger, in reverse): the new ship now stays parked (ship-in kept) when the theme flips behind closed doors; doors open first at 2300ms revealing the parked ship; ship-out fires at 2850ms — 550ms later, once the doors are essentially fully open; the exit’s opacity fade now holds full opacity for most of the climb (0.3s delay) and fades only near the top; sequence-end cleanup moved 3150→3700ms to fit. Verified by re-running the same 140ms-interval computed-style sampling that diagnosed the bug: doors ≥87% open with the ship parked at full opacity, then the ship visibly climbing at full opacity with doors wide open across multiple samples — the exact condition that never once occurred pre-fix — and the sequence still ends clean (all classes removed, state==='free'). |
08-ui.js (shipSwap), index.html (.ship-out transition) |
||
Toasts self-remove after ~5.2s, banners after ~3.6s, and no history existed anywhere — this needed a new data structure, not a timer tweak. Built: Progress.logEvent() (03-progress.js) records every popup — achievements, transmissions/pilot facts, major-event banners — into Progress.d.log in localStorage (capped at 200 entries), hooked into toast() and banner() at the source so nothing that pops can escape being logged. New “◈ LOG” button in the header nav row (next to the “?” button) opens a scrollable Mission Log panel — same glass-panel styling family as the hint popover — listing everything newest-first with kind/title/description/timestamp; closes on outside-click or Escape; shows a friendly empty state on a fresh profile. Unlike the secret planet (session-only by design), the log deliberately persists across sessions — it’s the “go back and see what you’ve done” record. Verified: fresh profile shows the empty state; generated real achievement/banner/fact events, hard-reloaded the page, and confirmed all three appear in the panel newest-first with correct kind labels (persistence proven, not assumed); Escape closes it; screenshotted the open panel. |
03-progress.js (logEvent, hooks), 08-ui.js (panel behavior), index.html (markup/CSS) |
||
| Re-tested live in Expanse against the current code as part of the R1d batch — the badge/count renders uncut, no clipping anywhere on the log panel or its tabs. Superseded by an earlier dossier-log redesign this session that changed the panel’s layout; no new code needed. | 08-ui.js / index.html (log panel) |
||
Landed as part of R2a’s real-ship-art pass — .rocket’s box went 44×44 → 54×62 alongside the SVG→<img> swap, since both changes touched the exact same CSS rule. The hangar-bay docked ship (the other half of this note) stayed at its existing 40×46 — Aaron’s note was specifically about the flying cursor’s presence, not the bay. |
index.html (.rocket sizing) |
||
| 21 | Mobile touch controls fight the device (LOW priority) | Reported by Aaron 2026-07-18: on a phone the page pans/zooms during play, and a fire touch and a move touch aren’t distinguished — the game can’t tell you’re trying to do both, so multitouch flails. Two layers: (a) the true bug — the page isn’t locking touch gestures (touch-action:none + preventDefault missing) and touch handling doesn’t track fingers by pointer ID; (b) the bigger design question of whether precise dual-touch flight belongs on mobile at all. See the “MOBILE experience” cross-cutting item in §3 for the recommended direction (calmer tap-to-travel / assisted mobile mode, resolved alongside Phase 4 Ground Control). Do NOT ship an average patch here — Aaron cares about the mobile quality. |
touch/pointer handling in 05-combat.js / 06-flight.js / 09-main.js; index.html (touch-action CSS) |
This section is rewritten, not appended to, whenever the plan changes shape — it’s the current, authoritative phase list. It now folds in both the original 2026-07-18 roadmap AND the 2026-07-19 redesign-brief decisions into one ordered set of phases. Historical shipment logs for already-shipped phases live in §3.1+ below; the redesign brief’s provenance/governing rule is in §4.
Standing note: Aaron has said more design material is coming from the same outside design source (“look out”). When it arrives: read it fully (README/brief first, per its own instructions) before folding anything in — it may reshape this list again, the way the 2026-07-19 brief did.
gatesUp), fire-through
HUD chrome, planet-dot cursor, figures stripped from scenes, bigger gate
marks, picker row fix, popover placements.github.com/aselkridge/aarons-portfolio).
R1c done — planet-dot cursor replaced by Aaron’s real sourced scanner
art (idle + a bigger/brighter lit state over anything clickable), picked
via a live hover-comparison mockup: Bebop 2 idle / Bebop 1 lit (warm),
source render idle / newest Drive upload lit (cool). Pure CSS swap on
:root custom properties, no JS touched. R1a started, blocked — tried
creating the aarons-3d-portfolio repo directly via the GitHub API and it
403’d (this session’s GitHub App has no repo-creation permission); Aaron
needs to create it by hand and hand it back before the Walkman move can
continue.<img> per theme, same rotate/translate math, untouched — Rocinante
needed a 90° rotate first, its source render was nose-left), the ship
picker (real renders, nose-right to match the existing launch direction),
the hangar-bay docked ship (shipSVG() reworked for <img>-based
markup), and the signoff slot (a flat red silhouette derived from the
Swordfish render — stays red in both themes, never tinted, per the
standing cross-theme-constants rule). Also closes issue #20 (cursor box
bumped 44→54×62). Verified headless across both themes, desktop + mobile,
zero console errors..brand/.console, the two big pointer-events:auto zones inside
the full-viewport .hud), and the ship — which chases the mouse with a
lag/ease — frequently flies through those exact same corners, so a big
cursor image and the ship end up visually stacked. Fix: shrunk the
cursor a lot (idle 56px→26px, lit 70-76px→32-34px) rather than change the
interaction model — a custom cursor bitmap always paints above all page
content (OS-level, not something CSS z-index can touch), so shrinking is
the real lever, not layering. Caveat: headless screenshots cannot
render the OS-drawn custom cursor at all, so this fix is verified by CSS
value + hotspot alignment only — needs Aaron’s real-browser confirmation
that the smaller size is enough, or whether it wants going further.
Also fixed in the same round: Rocinante now mirrors to face left and
launches off-screen left on the ship picker, opposite Swordfish’s right
— matches the picker copy’s own long-standing ◂/▸ arrow hints, which the
motion direction hadn’t actually honored since Phase 4b shipped..lockup, .loc, and .dz-row are all
block-level flex containers with no explicit width, so each silently
stretched to .brand’s full width (set by its widest row, .navrow —
legitimately wide, it spans to “NOTES”). That left large invisible
pointer-events:auto dead zones — .dz-row measured 534px wide vs
~338px of actual content — with nothing visible there but the cursor
still activating. .brand also had pointer-events:auto set directly
on itself, an independent second bug (its own box, same widest-child
problem, catching hovers regardless of which child was really present).
Fix: width:fit-content on the three offending rows, and moved
pointer-events:auto off .brand onto .brand>*. Verified by
re-testing the exact point Aaron described — now correctly falls
through to the game canvas (cursor:none) instead of showing the
scanner over empty space; confirmed real controls (dossier, help-btn,
nav links) still hit correctly. This was a real, fixable bug — separate
from the cursor-shape complaint below.assets/ships/
and the scanner review artifact are now just historical context, not
live assets for this piece. Verified all four SVG data URIs decode with
correct per-theme/state fill colors and zero console errors through the
full picker→flight→theme-flip flow; built a live hover-test artifact
(screenshots still can’t show a real OS cursor) so Aaron could confirm
the actual tip/hotspot feel in his own browser before it shipped.claude/website-build-nevi30 and claude/session-jrd7sj while
this work was in flight: the initial game, a docs pass (GAMES.md +
README refresh), a social share card + OG/Twitter meta for it, and a
share-card content fix. Merged clean every time (auto-merge, zero
conflicts — different files/sections each round), re-verified the dead-
zone fix and cursor still held after each merge, before pushing on.
Nothing from either side was lost across any of the four rounds.PLAYTESTS.md docs commits along the way (clean,
no conflicts, no shared files). Verified headless: zero console errors,
both themes, desktop + mobile, full return-to-hangar→re-pick cycle
confirmed working end-to-end.docs/orbit/assets/hangar/; .bay-floor removed (redundant against the
new art’s own painted floor). Verified live, both themes, desktop +
mobile, full dock→launch cycle, zero console errors.Fresh numbering (R1–R6) so old phase numbers can’t confuse: this list IS the complete remaining work. Order = build order; R2 is gated on assets and interleaves with anything. NOTE: Aaron called a pause on code changes when this list was written — nothing below starts until he says go.
docs/index.html); the game lives
under /docs/orbit/. Plan: (1) move the Walkman to its own repo with its
own Pages URL — repo name decided: aarons-3d-portfolio; (2) promote the
orbit game to the ROOT of this repo’s site so the main URL opens the game
directly (asset paths + og/meta + favicon updates); (3) remove the two
cross-links (profile “OTHER BUILDS ↗ The Walkman” chip, mobile hint
“Walkman ↗”); (4) optional redirect stub at the old orbit URL so shared
links keep working. BLOCKED 2026-07-20: tried to create the repo
directly via the GitHub API, got a 403 — this session’s GitHub App isn’t
authorized to create repos, only to work in ones it’s already been granted
access to. Aaron needs to create aarons-3d-portfolio by hand on
github.com (empty, public, no init needed) and say so — then this resumes.github.com/aselkridge/aarons-portfolio.autoOpenHelpOnce() (08-ui.js) fires the ? modal 500ms after first
entry into the system, once per visit (a module-level flag guards
against a second fire); wired from both entry paths — the normal ship-
pick flow (pickShip()) and the deep-link path that skips the doorway
entirely. Dismissible by clicking out as normal, same as before.⌂ button (#hangar-btn) in the
console’s COM row. returnToHangar() (08-ui.js) re-arms gatesUp,
clears gatePicked, closes every open panel/modal (help, contact, log,
reward modal, station, immersive flight mode), re-shows #doorway and
hides #shipsel — effectively re-running the front-door sequence on
demand rather than a page reload. Verified: full cycle tested —
return to hangar, doorway settles clean with no HUD bleed-through,
re-pick a ship, flight resumes with gatesUp correctly toggling back.Sound) and music
(Music) were two independent systems with no shared mute path. New
Sound.setOn/isOn and Music.setMuted (native HTMLMediaElement.muted,
not pause — preserves playback position) give both a single boolean
entry point; applyAudioMute() (08-ui.js) is the shared controller,
wired to two .audio-mute-btn instances (a ♪ icon in the console chip,
a text “audio on/off” label in the minimized player) that stay in sync
with each other. Verified: muting via one button updates the other;
confirmed both Sound.isOn() and the <audio> element’s muted
property flip correctly; both button instances render correctly on
mobile with no wrap/overlap.Aaron’s playable GTM-engineering game for AlphaForge. Spec: Aaron’s build
doc (“Moon Transmission, Build Spec & Handoff”, Google Doc, 2026-07-20) —
title/subtitle per Aaron’s direct instruction: The Coldest Call · “a
playable outreach run. help a stranded astronaut introduce ice cream to a
moon.” Placement decided (mockup signed off 2026-07-20): mission door on
the AlphaForge rail + PLAYABLE deck card, both opening a mission-briefing
window whose CTA routes to coldest-call/.
index.html (#mdoor, .has-mission, .bf-*,
.k-brief), show/hide + briefing in 07-environments.js
(openMissionBrief), PLAYABLE card support in 10-content-viewer.js,
placeholder route at docs/orbit/coldest-call/index.html. Verified live
both themes, desktop + 1349×789 + mobile; door only exists on AlphaForge.docs/orbit/coldest-call/index.html (single self-contained page, all CSS
docs/assets/fonts/; art under
coldest-call/assets/: five character sprites Aaron picked from a
21-candidate live audition (pilot=Firefly26, engineer=21, offduty=17,
ronin=07, generic=12, crowd-tinted per person), two terrain strips
(bebop/expanse, world anchors read off the art’s own landmarks), and the
orbit build’s real ships (Swordfish II / Roci) parked at the landing site
with theme-colored ownership glows. Game loop: intro transmission →
suit select → walk the vertical strip through 4 stations → clay
workstation console takeover (live 24-row table, free-column trap) →
the seam (six human-written lines, three approaches) → send → debrief
(reward-card style, insights, honest zero-reply branch). Both themes via
in-game toggle or ?t=roci/#roci; returns to ../#alphaforge.
3-run Playwright suite in the session scratchpad passed on the shipped
build (exact credit math, silent branch, tutorial, free-column trap).
Sources/build pipeline (template.html, build.py for the inline-artifact
version, deploy.py for this site version, playtest.py) live in the build
session’s scratchpad; the shipped page is the artifact of record.docs/PLAYTESTS.md, findings P1.1–P1.12 and P2.1–P2.12) produced 16
approved treatments, ALL now live: the plan tracker (LEARN / CHOOSE /
SAY HELLO), wrist-unit radio vs in-person portrait chips, THE LOG
notebook transcript, live credit ledger + receipt, the wall beat,
group-gate multi-select with live count, column use-hints + run
projection, P.S. explainer + letter seam preview, celebration finale
with fireworks + measuring stick + rival why-flips, THE DECODER jargon
shelf, ice-cream-flavored columns (incl. the sweetTooth semi), idle
nudges, select-screen sequencing, flexible back, quit door. Plus: two
capped side jobs off the main path (Scoop Catch v2 + The Delivery Dash
side-scroller, suit perks confined to them per I-3), a fifth suit (The
Volunteer), themed sky bands + ship parking on open ground, themed
end-screen sign-off paintings (E-1), and the Feedback Door — the debrief
opens Aaron’s Google Form with the run’s numbers prefilled
(entry.600648468). 36 art assets under coldest-call/assets/. 4-run
Playwright suite green on the shipped build (exact credit math, silent
branch, tutorial, side-job smoke). NOTICE.md + © lines added repo-wide.Medium-honesty rule applies throughout (CLAUDE.md): illustrated things are sourced, never hand-coded; I do shells, keying, compositing, animation tiers.
[ SHIP ] slot is now a real flat-red Swordfish II silhouette..bay/.bay-door used before. Aaron
sourced 4 candidates for warm bay, 4 for cool bay, 6 for warm doors, 4 for
cool doors; all 18 reviewed via a live Artifact mockup (full gallery +
a real-scale, real-timing interactive dock/launch demo) and Aaron picked
the gpt-image render in every category. Medium-honesty note surfaced in
that review — the ship art is clean vector, this new art is textured
pixel art, genuinely two different mediums sitting side by side — Aaron’s
call: leave the ships exactly as they are, no redraw. Assets live in
docs/orbit/assets/hangar/ (bay-warm.jpg, bay-cool.jpg,
door-{warm,cool}-{l,r}.jpg — doors pre-split into halves so each
.bay-door.l/.r gets its own half with a true centerline). The old flat
.bay-floor stripe overlay was removed — the new art paints its own
floor, so it would’ve doubled up. Verified: live dock→launch cycle in
both themes, desktop + mobile, zero console errors; bay/door background
images confirmed resolving to the real asset URLs via computed style.showcasing myself” — the site is only done when the words are his)
docs/orbit/CONTENT-INVENTORY.md — that file is now the working
checklist for R3b/R3c; keep it updated as slots fill.data/facts.js, Clay table screenshots into the gallery,
dossier/profile role + chips wording confirm, station decode wording
confirm (his tags vs the brief’s proposals).Guided first-flight for visitors; the flight-manual modal’s “run the tutorial · soon” slot goes live as the replay entry. Needs R4 shape final.
Aaron’s personal plain-English walkthrough of how this was all built: tools, concepts, lessons, mistakes. Last, so it teaches the final shape.
Everything known lives in a phase above (Aaron 2026-07-19: “I don’t want a backlog at all”). T14 folded into R4a; the cars/boats/words/speakers strip shipped 2026-07-19. One standing PROCESS note (not a task): if the outside design source sends another brief, read it fully before folding in — it overrides its predecessors where they conflict, but Aaron’s own direct instructions outrank any document.
Planet approach animation (index.html, 04-world.js, 06-flight.js):
.pglow — a large radial-gradient glow bloom behind every planet
(each planet’s own color via --pc), invisible at rest, blooming in via
a back-out/overshoot easing (cubic-bezier(.34,1.56,.64,1)) when the
planet goes .live (proximity lock) — reads as a flare rather than a
flat fade. The existing .ring/.orb scale transitions got the same
bouncy easing so the whole “coming into range” moment feels like one
cohesive pop rather than several small, disjointed transitions..sun-orbit, one per station, built in buildPlanet())
now carry their own planet’s color and a constant ambient glow
(box-shadow) at all times, brightening further via a new .live state
toggled in setLock() (06-flight.js) alongside the planet’s own — the
system map reads as a lit set of flight paths instead of flat reference
lines..materialize,
added in unlockRonin() and removed ~1.2s later — scaling in from
nothing past full size with its own glow flash, instead of snapping into
existence mid-frame.Log button prominence (index.html, 08-ui.js): the old single
pulsing dot was too subtle. .hasnew now fills the button with a solid
red-tinted background (not just a border), and a new numeric badge
(#log-badge) shows exactly how many rewards are waiting — both driven by
Progress.d.rwNew now being a count, not a boolean. markLogNew()
also re-triggers a brief scale “bump” (.bump, reflow-retriggered) the
instant a new reward lands, so there’s an announce moment, not just a
static glow. Badge clears and the bump stops firing once the log is opened.
Session-only rewards (03-progress.js): Progress.d.rw/rwNew are
now excluded from what’s read from and written to localStorage — deleted
from the saved blob before both load and save — so the reward collection
resets every visit, exactly like bounty and the secret planet already
did. Achievements (d.ach) and the visit/gold/saucer counters are
untouched and still persist normally; only the reward log is session-only.
Redesigned “?” button (index.html): was a plain generic circle
(border:1px solid var(--line)). Now themed per ship to match the
console/log-tab language — amber-bordered with a solid-fill hover for
Swordfish, cyan-bordered with a translucent-glow hover for Rocinante — and
the log button’s base styling was folded into the same shared rule so both
nav buttons read as one consistent chip family.
Verified (live state via headless browser, not source-reading): a
planet’s .live toggle (via real ship proximity, not a one-off manual
call that the per-frame proximity check would immediately override)
correctly sets .live on both the planet and its orbit ring, and the
.pglow opacity settles at the intended 0.55 with the ring’s box-shadow
showing the planet’s own color; Ronin gains .materialize immediately on
unlock and loses it again ~1.2s later; earning rewards increments the log
badge text (1, then 2) and sets .hasnew, both clear on opening the log;
a full reload confirms Progress.d.rw/rwNew reset to empty/0 while a
test achievement survives the same reload; the existing reward
picker→reveal flow and a real gold-asteroid-crack→modal path both still
work end-to-end; zero console errors throughout. Screenshotted the glow
flash, the settled state, Ronin’s materialize moment, the log badge, and
the full nav row in both themes.
Follow-ups shipped after Phase 1 the same day (plus three bug fixes in between: reward-modal click-through/cursor, bigger ?/LOG buttons, slower grander Ronin reveal, and the picker made non-dismissible so a cracked gold asteroid can never be wasted).
Progress.d.rwSeen, session-only like the
rest of the reward state): an earned reward stays “new” until its log
entry is actually clicked and read — NOT merely until the panel opens.
The button badge counts unread; each category tab in the log carries its
own count chip (.tab-new) so you can see where the new items are;
unread entries render lit up — bold near-white with a themed glow, an
accent edge, and a NEW chip — then settle to normal once read.--ink @ .85 opacity (dull
gray) to a bright near-white..rewardmodal.cat-* CSS + setRevealBody
in 08-ui.js): quotes render in Dancing Script with a decorative opening
quote (wisdom mood); career facts render bold with every number blown up
~1.65× in Anton with an accent glow (.rm-num, HTML-escaped then
regex-wrapped); fun facts render in rounded Baloo 2 (relaxed); pilot
facts render as a mono “PILOT FILE // DECRYPTED” dossier readout. Two
new self-hosted fonts added to docs/assets/fonts/ (Dancing Script 600,
Baloo 2 600 — same no-CDN pattern as the rest).Verified (headless browser, live state): badge counts 3 after 3 earns
and survives opening the log; tab chips show per-category counts (2/1);
unread entries carry .unseen + NEW chips and un-light individually on
click, decrementing the badge; career numbers wrap in .rm-num (199/148/
40/93%); quotes/random reveals compute to Dancing Script/Baloo 2 with both
fonts confirmed loaded via document.fonts.check; NEW chip no longer
overlaps text (padding fix); picker renders correctly in both themes;
zero console errors.
Still pending from Aaron: Clay table screenshots (AlphaForge gallery), poems (Oromugai), eating/fatherhood content (Life). All have marked placeholder sections that need no code changes to fill.
Explicitly NOT touched this shipment: issues #2, #5–8, #11, #12a in §2, all still outstanding — folded into Phase 3 (visual fidelity) and Phase 4 (Ground Control) of the roadmap above. Phases 2–5 not started.
Aaron shared a full design handoff from a separate design session
(Design Notes.zip → design_handoff_aaronautics/README.md +
Aaronautics Redesign.dc.html, a 12-turn HTML mockup reference — a design
medium to model from, not code to copy). Its README explicitly supersedes
the old docs/DESIGN-BRIEF.md (stale — describes four aesthetics and a
different flow). Governing rule from that brief: “Lead with wonder.
Guarantee the exit” — keep the full game, but résumé/LinkedIn/career-proof
must be one click away from anywhere; the game delivers the portfolio, it
doesn’t gate it.
The item-by-item decisions from reviewing this brief with Aaron are folded directly into the phase list in §3 above (mainly Phase 2’s palette/type adoption and Phase 4’s sub-parts 4a–4g). This section is provenance/reference only — §3 is the current source of truth for what’s built vs. pending.
One correction worth flagging explicitly: the brief’s own arrow-press warp+font-crossfade theme transition (its Turn 11) is NOT being built. Aaron’s actual ask (confirmed 2026-07-19): after LAUNCH, a second full-screen diagonal-split scene — Swordfish vs Rocinante, each with theme name/copy — where clicking a ship flies it off-screen and warps into flight. This is a new, bespoke front-door sequence, NOT a reuse of the existing in-game hangar-bay ship-swap animation (that stays exactly as it is, untouched, for later mid-session theme switching). See Phase 4b in §3.
Standing note: Aaron has said more design material is coming from this same outside source (“look out”) — when it arrives, read fully before folding in; it may reshape §3 again, the way this brief did.
Corrections sheet (CLAUDE_CODE_FIXES.md, landed 2026-07-19 — overrides the v3 README where they conflict; all seven items applied):
aaron-logo-transparent.png) —
never redrawn. On busy station scenes it renders as a single-color tint
(cream warm / holo-blue cool) via CSS mask of the asset itself.#st-mark, wired to closeStation).#35c1d1 warm, orange
#f0a63c cool — applied to the wordmark i-tittle, the profile-avatar
dot, and the wisdom quote marks..sg-ship — red in BOTH themes, never theme-swaps; Aaron is sourcing
the art, do not fabricate it).v3 brief (Design Notes 2.zip, landed 2026-07-19): a consolidated v3 handoff that supersedes v2 where they disagree. New in v3 and now BUILT: ship picker (T16), fixed signoff (T17A), profile window + three doors (T15/T18), the warm/cool artifact system (T20–24), STAR reward cards (T13/T2), Captain’s Dossier + HUD kit direction (T14), the 5F lockup (§5). Still pending from v3: the T19 Hangar easter egg (needs art; signoff keeps contact duty until then), full T14 NAV·COM restyle (Aaron chose the lighter touch for now), Ground Control 4c (on hold), secret-planet Pong.
Aaron supplies, drops straight in (no code changes unless noted):
docs/orbit/assets/resume.pdf → then swap the three “▤ RÉSUMÉ · SOON”
slots (dossier/console/profile) to real links — small code touch.SECTIONS.alphaforge build cards.SECTIONS.life “Now playing” items.data/facts.js career array (object shape
documented inline).