# Steve Dekorte — Full Documentation
Hi, I'm a programmer interested in the intersection of technology and aesthetics. In the 1990s, I was drawn to this aspect of NeXT computers and worked with research groups and startups using them. Since then I've worked on a number of open source projects, the best known of which is the Io programming language. Lately, I've been working on STRVCT, a naked objects JavaScript framework for building client-side web apps. My current project is Undreamedof.ai, an AI-based interactive fiction game built with STRVCT.
See also: /llms.txt (curated index) and /sitemap.xml.
---
Source: /
Hi, I'm a programmer interested in the intersection of technology and aesthetics. In the 1990s, I was drawn to this aspect of [NeXT computers](https://www.computerhistory.org/tdih/october/12/) and worked with research groups and startups using them. Since then I've worked on a number of [open source projects](https://github.com/stevedekorte?tab=repositories&type=public), the best known of which is the [Io programming language](http://iolanguage.org/). Lately, I've been working on [STRVCT](https://strvct.net), a naked objects JavaScript framework for building client-side web apps. My current project is [Undreamedof.ai](https://undreamedof.ai), an AI-based interactive fiction game built with STRVCT.
[steve@dekorte.com](mailto:steve@dekorte.com)
- **Projects** — Current and past projects.
- **Personal** — Personal writing and reflections.
---
Source: /fun/TRON/docs/
## Controls help shortcut — October 20
- [x] Move the controls-help shortcut from C to Y in CLU and cycle games to reduce accidental activation. Update desktop hints and browser checks; touch controls remain available.
## Continuous multiplayer scenery motion — October 20
- [x] Carrier positions are local, but previously used the snapshot-stepped match clock. Give the carrier, Solar Sailer and clouds a continuous local rendering clock in multiplayer, independent of packet arrivals and round resets. Solo scenery uses interpolated simulation time. Keep the authored opening carrier placement unchanged.
- [x] All 642 unit tests pass, including packet holds, backwards timestamp corrections, round resets and solo pause/reset coverage. Local headless Chrome on macOS verified continuous carrier displacement across 179 multiplayer frame intervals with no page errors (`node test-results/carrier-clock-check.mjs`). Subjective play confirmation remains pending.
## Restore aircraft antialiasing quality — October 19
- [x] Investigate the quality change introduced with ray-cast cycles: the former 1.25–1.5× scene supersampling became a 1× starting level, and temporary frame-budget misses permanently lowered the session ceiling. This affects rasterized Recognizers and Solar Sailer geometry as well as cycles.
- [x] Start scene and ray-cast renderer quality controllers at 1.5× with up to supported 4× MSAA, retaining pixel/texture budgets. Keep the fallback floor at native resolution rather than 0.85×. Retry rejected levels after sustained headroom and a 60-second cooldown, backing repeated failures off up to five minutes. Performance fallback remains available.
- [x] Chrome rendered aircraft comparisons at 1×/1.5× show cleaner Recognizer edges; Solar Sailer captures also generated. Real framebuffer test confirms 1.5×/4× initial quality, native-resolution fallback, later recovery, and restoration of independent normal/ray-cast controllers. Unit tests cover sample support, startup quality, temporary stalls and bounded retry behavior. Higher starting resolution costs more GPU work; actual hardware can still reduce quality under sustained load.
## Multiplayer delivery resilience and host diagnostics — October 19
- [x] Send authoritative snapshots at 30 Hz (120 Hz simulation unchanged). After catch-up, encode/broadcast only the latest state, retaining all simulation steps and trail corners. This avoids queuing obsolete intermediate snapshots. The additive metadata and cadence change remain compatible with protocol 5.
- [x] Adapt confirmed-path playback between 75 and 125 ms using a bounded arrival-gap window. Healthy delivery returns to the 75 ms floor; clock adjustments remain monotonic and capped at ±2%. Local input prediction remains immediate. Extreme outages still hold the last confirmed pose.
- [x] Expose `tronMultiplayerReport()` while connected: bounded RTT, arrival gaps, send/arrival interval variation, buffer size/holds, and host pump counters. Server diagnostics report late callbacks, maximum callback gap, catch-up steps and discarded elapsed time. They measure scheduling, not CPU execution cost. Arrival minus send interval variation includes network and receiving-process scheduling, without requiring synchronized clocks.
- [x] Seeded 60-second moderate-jitter replay: 20 Hz fixed buffer held 37 frames; 30 Hz adaptive buffer held zero after warmup. A previous production trace with much longer outages improved only from 148 to 142 held frames; buffering is deliberately capped rather than hiding those outages with excessive latency.
- [x] Straight-motion interpolation skips full trail-history scans when the snapshot pair contains no turn. Corners still walk confirmed trail segments.
- [x] Instrumented production sample (65 seconds): snapshot-gap p99 60.9 ms, 11 gaps over 75 ms, ~20 KB/s, ~119.8 ticks/sec. Host runtime counters: maximum callback gap 16 ms, no callbacks over 50 ms, no discarded time. RTT was higher in this separate sample (median 374 ms), so before/after network numbers are not a controlled performance comparison. Host timers are I/O-updated and cannot measure CPU execution cost; see [Cloudflare timer semantics](https://developers.cloudflare.com/workers/runtime-apis/performance/). This does not conclusively exonerate the host or identify the RTT source.
- [x] A separate 20-second production repeat measured median RTT 79.4 ms, p95 RTT 93.6 ms, maximum snapshot gap 57.7 ms, and zero gaps exceeding the base 75 ms buffer. Runtime counters again showed no late callbacks. The large RTT variation between runs remains unattributed.
- [x] Reproduce a bounded probe with `node tools/validation/measure-multiplayer.mjs`; it joins as one participant. `MP_PROBE_SECONDS`, `MP_PROBE_URL`, `MP_PROBE_ORIGIN`, and `PROBE_REPORT` configure the run. Long observation gaps are flagged rather than silently counted as normal scheduling.
- [x] 639 unit tests, production build/asset audit, and rendered two-client/14-connection multiplayer coverage pass. Regressions cover bounded buffer recovery, clock continuity, independently decodable catch-up snapshots and diagnostic attribution.
## Gentler multiplayer clock corrections — October 19
- [x] Local WebKit capture found playback-clock rates spanning approximately 0.914–1.10 despite no buffer underruns or backward motion. Packet scheduling jitter can vary the arrival-based clock even at near-zero RTT.
- [x] Correct local prediction and remote playback clocks over two seconds, capped at ±2% instead of ±10% over a quarter-second. Keep the existing 75 ms remote buffer, immediate local input prediction, monotonic time, and pause recovery.
- [x] Repeat WebKit capture measured rates 0.991–1.008 with no buffer holds or backwards motion. Eight playback regression tests pass, including timer batching, packet jitter, corners, and combined server/render stalls. This addresses measured speed pulsing; subjective play verification and other sources of stalls remain possible.
## Single-line boot progress — October 19
- [x] Replace the current boot status in place rather than appending completed stages. Retain the ENCOM heading, animated dots/cursor, half-second stage threshold, and full console timing report.
## Reuse startup patrol connectivity — October 18
- [x] Investigate the Safari screenshot showing 23,328 ms of enemy placement in a 26,481 ms boot. Random patrol candidates could repeatedly search the large labyrinth for a route to the same destination. Reuse collision-checked route points within each placement pass; nearby candidates/search nodes can join a proven route through a swept-clear segment. Sealed pockets still require rejection; gameplay pathfinding remains unchanged.
- [x] Keep connectivity proofs local to a placement pass so seeded results do not depend on earlier games. Include layout/run seeds in the console loading report for exact reproduction of outliers.
- [x] Local headless WebKit 26.6 on macOS: fixed-seed placement 1.4–1.8 s → 0.39–0.46 s; eight randomized boots reached the menu in 3.59–4.92 s, placement 0.66–1.99 s. The user's exact 23-second seed was not captured, so this is not proof that all outliers are eliminated.
- [x] Full 634-test suite and production build/asset audit pass. Additional regression records swept-clear segments and independently checks that every spawned labyrinth tank is connected to its beam; cache tests cover separating walls, distant points, and reproducibility after another seed.
---
## Lazy Solar Sailer — October 18
- [x] Remove Solar Sailer loading from boot and from eager credit-preview imports. Start its optional background load after 30 seconds of local active play in CLU, solo cycles, or multiplayer; server match age does not trigger it immediately. Credit preview loads it on demand. Existing flyby scheduling joins naturally when resources arrive; no mode waits for this scenery.
- [x] Yield between model adaptation phases so that resource preparation allows rendering. GLTF parsing and later GPU upload still use the renderer/main-thread pipeline; this is not a worker-decoding implementation or a guarantee of zero frame stalls.
- [x] Browser checks verify no renderer/model request during boot or program entry, followed by a single model load in each of the three game modes. Offline PWA asset caching remains unchanged and may fetch the source asset in the background.
## Reuse materialization edge calculations — October 18
- [x] Trace materialization edges once per shared mesh geometry, then clone the resulting line buffers per vehicle. A weak cache follows source geometry lifetime and invalidates on position/index replacement or version changes. Materials, colors, animation uniforms, GPU buffers, and disposal remain independently owned.
- [x] Fixed layout/run seed 1982 in local Chrome 155 headless: cached boot approximately 5.84 s → 3.86 s; rendering-world setup approximately 2.32 s → 1.07 s; arena setup approximately 1.33 s → 0.72 s. This reuses CPU calculations without reducing geometry fidelity.
- [x] All 632 unit tests pass. Browser comparison of a warmed-cache Recognizer materialization against freshly calculated Three.js edges produced identical geometry and pixels (1,432 wireframe vertices). Unit regression covers buffer independence, disposal, and source-geometry changes. Production build and asset audit pass.
## Startup scheduling — October 18
- [x] Keep seeded simulation initialization sliced into 8 ms CPU budgets, but yield through `scheduler.yield()` (ordinary timer task fallback) instead of waiting for a full animation frame per slice. Rendering setup retains its existing paint boundaries. Asset caching avoids transfers, not simulation construction, model adaptation or GPU setup.
- [x] Chrome 155 headless on macOS, fixed layout/run seed 1982: initial simulation approximately 2.44 s → 1.07–1.11 s; cached terminal ready approximately 7.02 s → 5.84–5.86 s. Timings are local development measurements, not production/Safari guarantees. Browser startup responsiveness checks pass in Chrome and WebKit: animated dots, repeated world-build paints, and cycle launch. WebKit startup frame gaps stayed under 25 ms in the measured runs. Full 631-unit suite passed.
## ENCOM boot transcript — October 18
- [x] Show stage-specific ENCOM progress messages for work lasting at least 500 ms, and retain only completed stages meeting that threshold. Omit aggregate startup timings and checkpoints from the transcript; no elapsed times appear on screen. Keep detailed console timing diagnostics unchanged. Append individual lines without rebuilding the transcript; preserve the animated working indicator and scroll within the existing terminal margins on small screens.
## Continuous grid speed — October 18
- [x] Solo, multiplayer authority/prediction, and AI forecasts now use a smooth additive increase of 1% of initial speed per elapsed racing second, retaining the 200% cap (reached at 100 seconds). New rounds reset to 100%; staging and paused arenas do not advance the clock.
- [x] Keep GRID SPEED visible throughout countdown, racing, and results. Remove speed-increase beeps and temporary warning countdowns entirely; match countdown audio is unchanged. Multiplayer protocol advances to 5 because predicted movement changed; deploy client and Worker together.
- [x] Unit checks cover linear progression, the cap, scaled turbo/brake and AI forecasts, reset/pause behavior, and persistent indicator text.
## Per-play analytics and exit announcement — October 18
- [x] Production play markers distinguish `space-paranoids`, `cycles-solo`, `cycles-mp`, and `bit`. Space counts once when a fresh run reaches running mode; cycles count each racing round with a living assigned player (not spectators); BIT counts once after microphone permission and actual activation. Pause/resume and menus do not count. Markers honor analytics opt-out, are excluded from offline precache, and use one temporary iframe. App-start tracking is unchanged.
- [x] Chrome headless production-build fixture verified exact mode/restart reports, BIT deduplication, no menu/spectator/development/opt-out reports, and one iframe; all Cloudflare report uploads intercepted and aborted. Unit test verifies run/round transitions. Scratch Vite builds now honor their output directory in the public-assets plugin.
- [x] Replace the runtime illegal-exit announcement with the approved Runway transport-reference voice and subtle loudspeaker effects. Preserve the dry source and generation provenance. Add 14.95 dB with peak limiting to match the previous clip at −21.23 LUFS (measured true peak −1.85 dBTP). Existing per-player escape trigger and playback gain remain unchanged. Prior Leda source is retained; the original isolated film audition remains source-only.
title: Implementation plan
topTitle: TRON
subtitle: Current simulation scope, decisions, and acceptance criteria
---
Extended model and open-source acknowledgments: [Extended credits](credits_extended.txt).
## Archived original grid-announcer recordings — October 18
- [x] Preserve approved clean ElevenLabs transport isolation and its loudspeaker/echo version under `assets/source/audio/grid-announcer`, alongside the original illegal-exit isolation and matching effect audition. All four are catalogued source-only with provenance and verified hashes; no runtime references or playback changes. The existing generated exit announcement remains active pending approval of the original-voice replacement.
## Solo roster player marker — October 18
- [x] Show the black/white player slash on the solo player's roster circle only, with PLAYER accessibility text. Do not change cycle human flags or the rendered bike. MP spectators remain unmarked unless following an actual human. Headless Chrome roster checks pass, including no bike-state mutation and solo/MP marker transitions. Local change; not deployed.
## Three-cycle team announcements — October 18
- [x] Starting team speech only plays for an assigned team with exactly three cycles in solo or MP. Count team roster slots rather than surviving cycles so an early crash does not cut off speech. Smaller teams do not consume the first eligible announcement; eligible rounds retain the 20% later-match probability and equal original/alternate selection.
- [x] All 18 team-voice unit tests pass, including one/two/four-cycle exclusion, other-team isolation, solo/MP coverage and teammate death during playback. Browser fixture updated to use three-cycle teams. Local change; not deployed.
## Arena escape alarm — October 17
- [x] A confirmed player cycle escape alerts all active arena-patrol Recognizers with a one-time position/velocity report. Guards leave their patrol route, investigate the exit and pursue upon visual acquisition using existing perception, search, attack and target-retirement behavior. No unseen live tracking; ordinary non-grid patrols receive no direct alarm. Initial outside testing spawns do not trigger it.
- [x] Alarm clears stale neutralization state for the new escape but does not activate destroyed/materializing/teleporting guards. Multiplayer still silently derezzes escaped riders, so this pursuit applies to the surviving solo road cycle. Tests cover multiple guards, immutable observations, exclusions and an actual GameSession breach crossing. All 630 unit tests and production build/asset audit pass; pursuit feel has not been browser-playtested. Local change, not deployed.
## Local illegal-exit announcement — October 17
- [x] Use the approved Gemini Leda flight-controller reading with loudspeaker EQ and subtle echo when a rider clears the arena boundary through a breach. Shared simulation increments an escape revision, carried in MP snapshots; only that client's assigned seat hears it. Solo uses the player cycle. Spectator targets, ordinary crashes, initial outside spawns and joining after an escape do not trigger speech.
- [x] Play once per confirmed exit, including subsequent solo exits after reentry. Mute, home/disconnect, reassignment and new rounds cancel; missing/muted audio does not replay later. Allow the line through the result hold after a silent MP exit. Stop any overlapping team-leader line. Existing MP derez behavior is unchanged.
- [x] Preserve the dry generated master, generation direction and processing provenance; runtime contains only the approved processed recording. All 628 unit tests and production asset audit pass. Headless Chrome verifies actual sample loading/decoding and playback (10.17 seconds) without errors. Local change; not deployed, and MP requires the updated Worker snapshot field.
## Escalating light-cycle grid speed — October 16
- [x] Solo, multiplayer authority and JavaScript AI training share a 10% compounded speed increase every 15 seconds of active arena racing. Each increase ramps over one second, caps at 2× starting speed, and resets each match. Cruise, turbo and braking speeds scale together; reserve capacity/recharge and reaction times are unchanged. Paused/outside-arena play and staging/result time do not advance the ramp.
- [x] Display a silent SPEED INCREASE IN 3/2/1 countdown, followed by one confirmation beep when the increase begins and GRID SPEED: N% for three seconds (October 17 adjustment). No further warning at the cap; no backlog of beeps after mute/network gaps. Uses terminal HUD styling.
- [x] AI speed forecasts and local MP prediction account for grid speed. Snapshots include the grid multiplier and protocol advances from 3 to 4 to reject outdated current clients. Requires coordinated client/Worker deployment. Existing AI policies are unchanged; no evolution run has been performed under the new rules yet.
- [x] All 620 existing unit tests plus five new speed-ramp tests pass. New coverage checks proportional cruise/turbo/brake motion, AI forecasts, cap, staging/pause/reset, warning timing and beep deduplication. Production build/asset audit passes. Headless Chrome at 1280×800 verifies the rendered solo 3/2/1 countdown, 110% notice and actual accelerated cycle speed without page errors; screenshot inspected (`test-results/cycle-speed-ramp.png`). Local implementation; not deployed.
## Solo team radio — October 16
- [x] Enable the existing team radio path for solo cycle play using the player's cycle team. Preserve the one-second launch delay, first-match guarantee, subsequent 20% chance, equal choice between original/alternate recordings, and existing gains. Non-cycle play and disconnected multiplayer sessions remain ineligible. Returning home resets the guarantee; solo retries preserve it.
- [x] Seventeen focused unit tests pass, including solo selection/retry/session reset and disconnected multiplayer isolation. Headless Chrome verifies all eight recordings decode, schedule and play in both solo and MP paths without page errors. Production build and asset audit pass. Local change; not deployed.
## Alternate team radio lines — October 16
- [x] Add a second generated line for each team, selected 50/50 whenever an announcement plays. Keep the first-participated-match guarantee and subsequent 20% playback probability. Selection happens once per launch, never per frame. All generated clips use the increased gain; Blue's original film recording retains its existing gain.
- [x] Revised alternate scripts: Gold “Gold team, stay focused. Make every turn count.” Blue “Blue flight, on me. Hold formation. Wait for my mark.” Green “All Green cycles. Keep them turning. Leave no way out.” Red “Red team, move in. Press the attack. Give them no room.” Same user-supplied Alan reference and commanding generation direction; masters and provenance catalogued. Gold, Blue and Green files run 4.08 seconds; Red runs 5.07 seconds. Radio treatment without time stretching. SHA-256 checks confirm all four original recordings are unchanged.
- [x] Fifteen team-voice unit tests pass, including 50/50 selection boundaries, one selection per playback, original/alternate Blue gain, first-match and 20% behavior. Headless Chrome verified all eight buffers preload/decode and remain active after their scheduled delay without errors. Alternate set is integrated locally, not deployed.
- [x] Prior release e209382 (website fb8c596, Pages run 38982565833) deployed successfully: first voice set, fourfold generated-voice gain, first-match/20% playback, multiplayer-first menu, early loading dots and hidden-mobile-label correction. Live browser check verified published menu, gains/probability and all four original voice assets.
## Occasional multiplayer leader announcements — October 16
- [x] Guarantee the local team's announcement on the first participated launch of a multiplayer session, then make one independent 20% draw per subsequent participated launch. Queued spectator rounds do not consume the first launch. Muted/missing audio consumes that match's decision without delayed replay. Returning home/disconnecting resets the session; changing rounds or teams does not restore the guarantee.
- [x] All 11 team-voice unit tests pass, including exact probability boundary, one decision per round, first-match/reset behavior and spectators/mute. Timing, team selection and volume remain unchanged. Local change; not deployed.
## Preserve hidden menu labels during transition — October 16
- [x] Transition copies use rendered text rather than raw textContent, so the hidden CLU mobile restriction no longer appears on desktop when selecting CLU or another program. The actual mobile notice is retained when visible.
- [x] Headless Chrome regression covers selected/unselected desktop CLU copies and visible mobile notice preservation. Whitespace checks pass. Local fix; not deployed.
## Multiplayer leader radio for all teams — October 16
- [x] Add user-approved Gold, Green and Red leader takes using the same local assigned-seat radio path as Blue. Preserve the original Blue sample. Every team hears only its own clip, once per round, scheduled one second after racing begins. Solo, queued spectators and mid-match joins remain silent; mute, disconnect, exit, reassignment and round/result transitions cancel playback.
- [x] Gold: “This is Gold-1 to Gold-2 and 3. Split up. Take them one-on-one.” Green: “Green-1 to Green-2 and 3. Draw them in. Close the trap.” Red: “Red-1 to Red-2 and 3. Break formation. Cut them off.” Generated with Runway seed_audio from the user-supplied Alan voice sample. Approved masters, generation metadata, processing and underlying rights constraints are catalogued. Runtime uses approved radio treatment without time stretching. Gold, Green and Red playback gains are four times the original level (3.4 versus the unchanged Blue gain of 0.85), following the user’s second doubling request.
- [x] Eight targeted unit tests pass, including every assigned team and a different spectator target. Headless Chrome loads/decodes all cycle samples without errors and verifies the correct buffer remains active after the one-second delay for each team (Gold 4.55 s, Blue 3.91 s, Red/Green 4.05 s). Production build/asset audit passes with all three new content-hashed audio assets. Local integration; not deployed.
## Immediate loading status and multiplayer-first menu — October 16
- [x] Put critical loading layout and dot-animation CSS directly in the entry HTML, so the status can paint before external scripts or styles arrive. Keep the discrete dot status active with reduced motion; cursor and other motion preferences remain separate.
- [x] LIGHT CYCLES MULTIPLAYER is the first menu item and default selection. The explicit development cycleStart route still selects solo cycles.
- [x] Headless Chrome and WebKit 26.6 verify all four dot states while every external script and stylesheet is blocked, in normal and reduced-motion modes. Chrome menu checks verify the first/default item, all hover targets, click/tap activation, credits return, keyboard selection and CLU launch. Production build/asset audit passes. These changes are local; no claim that all subsequent main-thread stalls are eliminated.
## Foreground startup before offline installation — October 16
- [x] Delay service-worker registration/update until the opening terminal is ready. Previously the window load event could start the approximately 90 MiB offline installation while the foreground was still fetching modules, models and audio. Existing installed workers still serve cached resources immediately; new installations begin after startup.
- [x] Split startup instrumentation into entry-document/bootstrap, styles/terminal paint and game-module download/evaluation measurements. Safari unused-preload warnings alone do not identify a failed request or the cause of the reported 16-second pre-startup delay; download/evaluation timing is now separately observable.
- [x] Production build/asset audit passes. Headless Chrome PWA test verifies registration occurs after menu enablement, installability, offline reload/play, ranged audio, and waiting updates through play/home until explicit update.html activation. Corrected an obsolete test expecting automatic activation on return home. This local change has not been deployed; no production speedup is claimed.
## AI follow-up training — October 16
- [x] Completed one bounded generation: current policy plus three mutations, three training seeds and four spawn rotations each, 60-second encounters, two workers; seed offset 360000. Current approach-340000-0-0 ranked first (fitness 1.0175 versus 0.7862, 0.6856 and 0.6053). No weights promoted.
- [x] Selected policy then completed eight fresh seeds/four rotations: 14 wall eliminations and 7 trap-assist credits across 32 matches. All reached the 60-second limit without a winner, so this run does not establish full-match win-rate improvement. Candidate and baseline are identical because the incumbent won selection. Serial AI p99 0.204 ms and tick p99 0.276 ms pass the existing performance gates. Summary: tools/ai/results/mobile-followup-360000.json; full local record: test-results/cycle-ai-mobile-followup.json. Trainer exited normally.
## Mobile programs and cycle controls — October 16
- [x] Enable the terminal on mobile; disable only CLU, labeled “(UNAVAILABLE ON MOBILE)”. Mouse hover/click and touch taps select programs; keyboard navigation skips disabled choices.
- [x] Cycle touch controls: tap left/right to turn (or follow while spectating), hold top turbo or bottom brake, with simultaneous steering and pedal input. MENU opens a tappable exit confirmation; CONTROLS explains gestures; eligible players can tap NEW MATCH. BIT and MCP also have touch exit menus. Pedals release on cancellation, blur, hidden controls and disposal.
- [x] All 607 unit tests and production build/asset audit pass. Headless Chromium iPhone 13, iPad Pro 11 and Pixel 7 emulations verify menus and CLU restriction; desktop pointer tests pass. CDP multitouch verifies simultaneous turbo/turn, release/cancellation and brake. Actual solo launch, help, exit cancellation and return home pass. Physical mobile Safari/Chrome performance and feel remain to be checked. Deployed client 3883d5b via website 7bdeda8 (Pages run 38927792047 succeeded on retry after a dependency download timeout). Production headless iPhone emulation verified CLU disabled, multiplayer launch, touch guide and touch-only exit without page errors.
## Multiplayer roster controls guide — October 15
- [x] The multiplayer C / CONTROLS panel now explains team groups, filled active versus empty eliminated circles, the local player's white outline, gray waiting circles, wall-kill and hollow trap-assist dots, and opponent-colored dots. It explicitly identifies the black-and-white diagonal slash on both human cycles and their roster circles. Solo help is unchanged.
- [x] Overflowing help starts at a reachable top edge and scrolls in short windows. Headless Chrome at 1280×800 and 1280×600 verified the actual MP controls content and heading accessibility with no page errors; inspected the rendered panel capture. Syntax and whitespace checks pass. Included in the mobile release authorized for deployment.
## Blue-team multiplayer launch dialogue — October 15
- [x] Preload the user-approved `cycles_blue_leader_speech.mp3` as cycle audio. Only clients with an assigned BLUE seat schedule the non-spatial team-radio clip, one second after observing the countdown-to-racing transition. It plays once per round; solo play, queued spectators, other teams and mid-match joins do not receive it. Assignment, not the spectator camera target, selects the team.
- [x] Mute, leaving play, disconnect, seat reassignment, round changes and results cancel pending/active playback. Audio nodes disconnect on completion/reset. Runtime asset is an exact copy, catalogued with film provenance and use constraints.
- [x] Ten targeted multiplayer audio unit tests pass, including the exact one-second schedule and eligibility/lifecycle cases. Headless Chrome (`TRON_URL=http://127.0.0.1:5188/ node tests/browser/multiplayer-team-voice.mjs`) decoded the 3.91-second clip, preloaded every cycle sample without errors, and kept the scheduled voice active beyond the delay. Production build and binary/immutable-asset audit pass. User confirmed the blue speech sounds perfect. Approved for deployment with the ten-second result hold and initial placement responsiveness fix.
## Initial placement responsiveness and longer result hold — October 15
- [x] Initial enemy placement now consumes the same seeded simulation generator in approximately 8 ms work slices, yielding to browser paint between slices. Spawn-cell checks and maze-exit route searches both cooperate; synchronous simulation callers and restarts retain the same results and random order. This addresses the initial pause that preceded the earlier model-loading improvements.
- [x] Headless Chrome 155, 1280×800 (`TRON_URL=http://127.0.0.1:5188/ node tests/browser/loading-responsive.mjs`): initial/cached navigation recorded 271/234 animation frames during placement, maximum frame gaps 16.8/21.4 ms, all four dot states, and successful cycle launch without page errors. Cooperative scheduling adds elapsed loading time to preserve UI responsiveness. Individual GPU compilation operations can still block later in loading.
- [x] Multiplayer win/tie hold increased from 5 to 10 seconds before selecting the next roster. Regression verifies the roster stays fixed for the full hold. Seeded synchronous/asynchronous session equivalence, reset behavior and initialization failure propagation pass. All 602 unit tests and production build/asset audit pass. Isolated browser server stopped. Changes are local; the hold requires a multiplayer Worker deployment.
## Responsive initial resource loading — October 15
- [x] Model adaptation and world/cycle presentation construction now yield to browser paint between expensive phases, including cached loads. View factories finish asynchronously before the application uses them; background tabs have a timer fallback.
- [x] `node tests/browser/loading-responsive.mjs` passed in headless Chrome 155 at 1280×800 on initial and cached loads: all four loading-dot states appeared, 54 frames were rendered during world construction in each run, and solo cycles launched without page errors. All 600 unit tests pass. Remaining limitation: initial simulation placement and individual GPU compilation operations can still block briefly; these are not moved to a worker.
## Direct rotation into multiplayer formation — October 15
- [x] Formation travel now interpolates toward the final shot's fixed orientation, rather than recalculating the destination orientation from each intermediate camera position. This removes a moving aim target that could cause an initial yaw detour or reversal. Position travel, wall-clearance arch, materialization and moving launch hold remain unchanged.
- [x] Regression checks the rotation follows one shortest arc and never turns away from the final heading for all four teams. It fails on the previous implementation and passes with the correction. All 42 multiplayer unit tests pass. Headless Chrome 155 at 1280×800 (`TRON_URL=http://127.0.0.1:5188/ node tests/browser/multiplayer-transition.mjs`) verifies camera arrival, materialization, the moving launch hold and player follow with no page/shader errors. Production build/asset audit passes; isolated server stopped. Local change; not deployed.
## Moving multiplayer launch hold — October 15
- [x] Corrected the launch hold placement: server materialization lasts only the shared solo formation duration (1.833 seconds). Cycles then race and lay trails while the client holds the completed formation shot fixed for the shared 0.6-second launch hold, before cutting to player follow. Previously those 0.6 seconds extended the stationary countdown and the camera cut immediately on movement. A new round or result still releases an obsolete shot.
- [x] All 41 multiplayer unit tests pass, including camera position remaining fixed while bike positions advance, then cutting after the hold. `TRON_URL=http://127.0.0.1:5188/ node tests/browser/multiplayer-transition.mjs` passed on headless Chrome 155 at 1280×800: all-camera barrier, visible materialization, fixed launch shot with growing trails, and subsequent player view. Inspected `test-results/mp-team-launch-hold.png`; no page/shader errors. Isolated test server stopped afterward. Local change; deployment requires both Worker timing and client camera updates.
## Monotonic recovery after combined stalls — October 15
- [x] Reproduced a backward-clock correction when a render pause over 200 ms coincides with slow server tick progress and a newly received snapshot. Restoring the normal playback buffer could then place the remote clock behind its last displayed pose; the predicted local camera clock had the same recovery hazard. Both now preserve their previous clock minimum within a round, while retaining fresh-clock initialization for a new round. No extra interpolation delay or invented remote turns.
- [x] Seven playback regression tests pass, including the new combined-stall cases for remote cycles and local camera. The remote regression failed before this correction. A separate headless Chrome 155 diagnostic across two actual local matches recorded 843/750 racing samples, zero backward clock steps or held samples, and frame p99 17.6 ms in both rounds; it did not reproduce the user's ongoing jitter. The controlled stall regression establishes one real cause, not proof that every reported jitter source is fixed. Browser/direction clarification requested. Local change, not deployed.
## First multiplayer formation readiness — October 15
- [x] Camera readiness also waits for the asynchronous cycle renderer selection, completion of the home transition, a visible document and existing audio readiness. Previously the initial camera could acknowledge readiness while the detailed renderer was still loading/compiling, allowing the server's short formation timeline to advance underneath first-use work.
- [x] Detailed cycles warm both transparent formation and opaque racing pipelines with asynchronous compilation plus small offscreen draws before reporting renderer preparation complete. The warmup restores the render target and material flags and disposes its temporary target. Later matches reuse the prepared renderer.
- [x] Headless Chrome 155, 1280×800: `node tests/browser/multiplayer-first-formation.mjs` deliberately holds the detailed renderer module for six seconds, confirms the server remains in camera staging with cycles hidden, then observes 2.41 seconds remaining at the beginning of visible materialization and a successful launch without page/shader errors. Inspected `test-results/mp-first-materialization.png`. This reproduces the readiness race with an artificial cold load, not the user's exact Safari/GPU timing. Local change; no deployment yet.
## Multiplayer connection recovery and terminal errors — October 15
- [x] Initial multiplayer connection retries up to three times, with an eight-second timeout per attempt and 500 ms between attempts. Dispose failed sockets and heartbeat timers; explicit cancellation stops retries, and protocol mismatch requests a reload immediately. This mitigates transient handshakes; it does not identify or repair a persistent network/server failure. Three production Chromium handshakes succeeded (about 1.2–3.1 seconds). The reported Safari failure remains unreproduced: current Playwright WebKit was unavailable, and the older installed engine stalled and was stopped.
- [x] Shared game errors now use the ENCOM terminal font, blue text, black background, normal heading scale, wrapping and logo. Hide the tank/menu chrome, focus the return control, and provide RETURN TO PROGRAMS plus RELOAD TERMINAL. Returning after multiplayer failure resets the menu; early loading failures can reload the home URL.
- [x] All 597 unit tests pass, including retry success, finite failure, timeout cleanup, version mismatch and cancellation. `node tests/browser/multiplayer-errors.mjs` in headless Chrome 155 verifies three injected failures, terminal styling, keyboard focus, no overflow at 1280×800 and 640×600, and return to selection without page errors. Inspected `test-results/multiplayer-error-terminal.png`. Changes are local pending deployment.
## Reference organization by subject — October 15
- [x] Reorganized local `docs/references` material into subject folders, merging image/video locations for cycles, CLU/tanks and Recognizers. Maze trace overlays now live under `mazes/`; generator and documentation-copy paths follow them. Updated reference-page links and added subject aliases for the archived film collection while retaining legacy URL mappings. Original files and the existing archived collection are preserved; Finder metadata from the old folders was moved to temporary storage. Verified all 19 reference-page local links, both built trace backgrounds, the organization invariants, production build/asset audit and Worker dry-run.
## Tight spiral and pocket avoidance — October 15
- [x] Shared solo/MP AI now checks recent turn handedness. After two same-direction turns, another turn that leaves less than 2.5 cycle lengths for the following corner is marked risky; wide loops and opposite-direction exits remain allowed. This is a bounded warning heuristic, not a guarantee against every self-trap.
- [x] Replace occupancy-only room estimates with bounded swept-edge reachability after projecting 0.6 seconds of travel and the proposed own trail. Respect fractional walls, trail lifetime and emitter clearance. Prefer safe exits over proven small pockets; exhausted search budgets do not prove confinement. Cap each search at 512 segment tests and reserve 2,048 tests for safety checks. Reuse tactical results in the emergency guard instead of choosing right on equal-clearance ties; absent cached scores, prefer the target-facing direction when adequately clear. Existing reaction delays remain intact.
- [x] Validation on Apple M2 Max / Node 22.22: all 592 unit tests pass, including both handednesses, wide-loop exceptions, fractional/expired trails, budget exhaustion, opponent-facing escape ties and a simulated narrow-pocket approach. `node tools/ai/stress.mjs`: 12 bots / 2,048 segments, AI p99 0.615 ms, maximum 0.952 ms, no overflow. Headless Chrome 155 (`node tests/browser/cycle-ai.mjs --trial`): rendered-frame AI p99 0.60 ms, dense AI p99 0.40 ms, no page errors. Inspected the rendered capture; this does not establish subjective play quality.
- [x] Four paired 90-second-limit matches (seeds 1982–1985), using isolated baseline/current snapshots with self-wall death instrumentation: self-wall crashes 32 → 23, direct opponent wall kills 6 → 10. Per-match current self-wall totals 5/7/5/6. Small exploratory sample, not proof of general improvement; unavoidable and other self-traps remain. User reports the AI is becoming more interesting to play against. No new training or deployment is part of this change.
## Selected offense policy release — October 15
- [x] Final bounded run: population 4, one generation, three training seeds, eight final seeds, 60-second horizon, two workers, seed offset 350000, initial `cycle-ai-early-offense-round1.json`. Saved `test-results/cycle-ai-early-offense-round2.json` plus checkpoint. The new mutation earned 2 wins / 7 direct kills / 11 trap assists in 32 final matches; previous candidate `approach-340000-0-0` earned 4 / 9 / 9; deployed `aggression-1-0` earned 4 / 7 / 4. Selected the previous candidate rather than the new mutation. Its combined two fresh-seed evaluations total 7 wins / 19 kills / 15 assists across 56 matches, versus deployed 6 / 15 / 8. These remain small, correlated seat samples; the second evaluation's paired fitness improved in four of eight groups, so gains are not uniform.
- [x] Promoted selected weights into `shared/simulation/cycle-ai/policies/aggression-v2.js`, used by the common solo/MP default. Preserved `aggression-v1.js` for rollback. Updated the plateau trainer's starting import to the selected policy; training tools still never promote automatically. Selection summary is retained in `tools/ai/results/early-offense-promotion.json`.
- [x] Headless Chrome 155 at 1280×800 verified the exact trial policy, tuning values, rendering and execution with no page errors. AI work per rendered frame p99 0.40 ms (max 1.00 ms); dense 12-cycle/2,048-trail p99 0.40 ms (max 0.80 ms). Inspected `test-results/cycle-ai-trial.png`; report `test-results/cycle-ai-trial-browser.json`. This is automated rendered validation, not a human judgment of enjoyment. Training stopped before browser/performance checks.
- [x] Release validation: all 586 unit tests pass with the selected default in both solo and MP. Updated the regression test's explicit policy ID after it correctly flagged the default change. Production build/asset audit and Worker dry-run pass; 506 files, 65 immutable media names, no reference footage. Both film-sound and Disney BIT credit updates are included. User authorized production deployment after this run.
## Early confirmed offense and film credits — October 15
- [x] Offline fitness version 2 gives a confirmed enemy wall kill or successful trap assist one base point plus up to one early-elimination point, decaying linearly with death time over the match horizon. Direct owners and trappers share a single victim pool (half to each role when both exist); duplicate victims/owners cannot multiply credit. Dead wall owners remain eligible, allies and escaped cycles are excluded. Unfinished confinement no longer contributes fitness, but remains in diagnostic counts. Approach stays capped at 0.15 per team. Earlier wins keep the existing bonus. Scores from older fitness versions are not directly comparable.
- [x] Capture confirmed death times/credit shares in the offline benchmark and tournament artifacts only. No live simulation or roster attribution code changed. Successful trap timing uses death/confirmation time, not the earliest historical confinement, because a stale trap might have reopened. Formation-time bonuses would need validated active-trap timestamps. All 586 unit tests pass, including early/late timing, shared victim pools, duplicates, dead owners, unfinished confinement and excluded victims.
- [x] Added Frank Serafine / Serafine FX Studios to `docs/credits_display.txt` and `docs/credits_extended.txt`: Sound Effects Design and Synthesis for TRON (1982), with an explicit statement that gameplay uses sampled film effects. Verified against [BFI programme credits](https://bfidatadigipres.github.io/pdf/2023-07-15-tron.pdf) and [Serafine's interview](https://tron.fandom.com/wiki/Tron_Wiki:Frank_Serafine_Interview). Added that BIT is owned by Disney and comes from TRON (1982), separately from the program's MIT software license. All six existing credits/terminal tests pass, including eight-line/60-character display bounds. Changes are local, not deployed.
- [x] Bounded fitness-v2 run: population 4, one generation, three training seeds, six final seeds, 60-second horizon, two workers, seed offset 340000, initial policy from `cycle-ai-approach-round1.json`. Artifact: `test-results/cycle-ai-early-offense-round1.json` plus checkpoint; 48 training matches and 72 final comparisons. Candidate `approach-340000-0-0` scored 10 direct kills / 6 trap assists / 3 wins in 24 fresh-seed matches versus deployed 8 / 4 / 2 and previous candidate 7 / 4 / 3. Confirmed offense points: 25.106 versus 18.950 deployed and 18.755 previous. Mean fitness: 1.19794 versus 0.89348 and 0.93079. Four of six seed groups improve paired fitness over deployed; much of the direct-kill gain comes from one group, so this is exploratory rather than proof of general improvement.
- [x] Candidate serial timing passes: AI p99 0.184 ms, full tick p99 0.279 ms, max 8.063 ms. Training finished; no background run remains. Candidate is saved for play review; deployed/local default weights remain unchanged. No production deployment is part of these credit/training edits. Final production build and asset audit pass (506 files, 65 immutable media names); `git diff --check` is clean.
## Continued approach evolution — October 15
- [x] Round 2 continued from `test-results/cycle-ai-approach-round1.json`: population 4, two generations, two training seeds, six final seeds, 60-second matches, two workers, seed offset 320000. Trainer supports `--initial`, retains initial-policy results alongside the deployed baseline, and offsets the mutation RNG as well as match seeds. Saved `test-results/cycle-ai-approach-round2.json` and checkpoint. The requested battery pause was interrupted; the run completed before process inspection. Confirmed no training processes remained and did not automatically restart until the user said the battery was okay.
- [x] Round 2's training winner `approach-320000-0-0` regressed on unseen matches: 0.3994 fitness versus deployed 0.4482 and previous candidate 0.6874. Retained the first candidate (`approach-310000-1-0`) as the next starting point. In this fresh 24-match evaluation, that first candidate earned 5 wins / 9 direct kills / 9.5 shared trap assists versus deployed 2 / 7 / 7, with positive paired fitness in four of six seed groups. No policy promotion or deployment.
- [x] Round 3 after battery recovery: `node tools/ai/train.mjs --population=4 --generations=1 --trainSeeds=3 --heldOutSeeds=6 --seconds=60 --workers=2 --seedOffset=330000 --initial=test-results/cycle-ai-approach-round1.json --artifact=cycle-ai-approach-round3`. 48 training matches and 72 final evaluation matches. `nice -n 10` was attempted but sandbox priority adjustment was denied; Node still ran with two workers, so do not claim reduced OS priority. Run has finished.
- [x] Fresh-seed round 3 results (24 matches per policy): candidate `approach-330000-0-1` 7 wins / 5 direct kills / 6 trap assists; deployed 4 / 10 / 5; first candidate 4 / 6 / 7. Mean fitness 0.62145 versus 0.61196 deployed and 0.56602 previous. Candidate fitness improved in only three of six paired seed groups versus deployed; more wins accompanied fewer direct kills. This is not sufficient evidence of improved aggressive play. Serial timing passed: AI p99 0.171 ms, full tick p99 0.267 ms; full tick max 8.932 ms slightly exceeded the 120 Hz budget. Saved `test-results/cycle-ai-approach-round3.json` and checkpoint; no policy change or deployment.
- [ ] Before further weight-only evolution, consider stronger early confirmed kill/trap rewards and short interaction scenarios. Current outcome-weighted fitness can favor winning without increasing direct offense. No automatic training restart is scheduled.
## Bounded approach-reward evolution — October 15
- [x] Ran `node tools/ai/train.mjs --population=4 --generations=2 --trainSeeds=2 --heldOutSeeds=4 --seconds=60 --workers=3 --seedOffset=310000 --artifact=cycle-ai-approach-round1`. The trainer now starts from the deployed `DEFAULT_POLICY`, supports disjoint seed offsets and named artifacts, and records the initial policy/reward configuration. 64 training matches plus 32 fresh-seed evaluation matches; each seed uses all four team seats. No live-policy file was changed.
- [x] Candidate `approach-310000-1-0` versus deployed `aggression-1-0` on four unseen seed groups: 2/16 versus 0/16 wins; 11 versus 16 timeouts; 5 versus 6 direct wall kills; 5 versus 2 trap assists; mean fitness 0.5708 versus 0.3891. Paired fitness improved in three of four groups. Approach credit itself was slightly lower, so the fitness improvement is not merely more proximity. Small, correlated seat samples and symmetric baseline mirrors are not proof of general superiority or enjoyment. Direct kills did not improve; do not promote based on this score alone.
- [x] Serial candidate performance check after the tournament: AI p99 0.253 ms, full tick p99 0.367 ms, full tick max 2.583 ms; timing gate passed on this Mac. Ten targeted approach/fitness tests and `git diff --check` pass. Artifact: `test-results/cycle-ai-approach-round1.json` (plus checkpoint). Training completed; no background training remains. No candidate browser/playtest or production promotion was performed.
- [ ] Review candidate play and direct-kill behavior before promotion. Earlier kill/trap timing rewards and authored interaction scenarios remain unimplemented.
- [x] Investigated MP renderer fallback: `CycleRendererGuard` receives browser-frame `dt`, not network RTT. At target 60 FPS, three consecutive two-second windows below 39 FPS trigger the normal renderer after four seconds of warm-up. CPU stalls, snapshot work and local training can contribute to frame time; this does not establish the cause of the reported MP fallback. The fallback is one-way until manual Shift-R/reload. No renderer code changed; GPU/CPU profiling is still needed to attribute it.
## Obstacle-aware approach reward — October 15
- [x] Deployed reaction/overtake release `0109fd6` via website `a68a1ad`, Pages run `38792346237`, Worker version `67ade4d5-f5ae-4311-9849-4442e306cfb8`. Public headless Chrome confirmed menu entry, 120 decoded snapshots, twelve cycles, restart/camera readiness/materialization and no page errors. Production contains the reaction and charge-aware overtake changes, not the subsequent offline training work below.
- [x] Added offline approach instrumentation in `tools/ai/approach.mjs` and connected it to all three evolution/training entry points. Once per simulated second, build a 2.4 m cardinal route graph with swept-disc edges against the shared trimmed/damaged/expired trail geometry and solid arena boundary. Thin walls between free nodes still block traversal. Invalid endpoints and disconnected opponents earn no approach reward. This is a conservative geometric route estimate: narrow off-grid passages can be missed, heading/turn constraints and future moving obstacles are not predicted. No straight-line fallback. Training arenas remain closed; exterior breach navigation is outside this evaluator's scope.
- [x] Compare previous/current AI positions against the same previous target position and previous geometry; additionally require current connectivity. This excludes passive opponent approach and topology-only distance changes, and avoids treating the AI's newly emitted trail at its old position as a blocked starting point. Reward progress only in the 20–120 m route-distance band. Signed progress with a per-pair high-water mark prevents fixed-target approach/retreat farming. Pair credit is bounded and averaged over initial enemy pairings, so deaths do not inflate scores. Total team approach bonus is at most 0.15 fitness points, below one direct kill (0.5) or trap assist (0.35).
- [x] All 583 unit tests pass, including detours, thin barriers, open/too-narrow doorways, disconnected endpoints, passive approach, disappearing walls, reward caps, fixed normalization and simulation invariance. Three 60-second smoke matches (seeds 1982–1984) exactly match the uninstrumented outcomes/decision totals; team approach fractions span 0.068–0.194 instead of all saturating. Instrumentation costs 1.73–1.84 wall-clock seconds per 52–60 simulated-second match on this Mac; these are offline costs, not live-tick work. A four-seat 30-second tournament worker smoke passes and records approach credit in fitness. Artifacts: `test-results/cycle-ai-approach-smoke.json` and `test-results/cycle-ai-approach-tournament.json`. Config/version is saved with training artifacts. Timing passes exclude reward collection; concurrent smoke measurements are not a new performance guarantee.
- [ ] New reward is implemented and locally verified. A subsequent bounded evolution round is recorded above; no new policy has been promoted. Earlier confirmed kill/trap rewards, no-offense penalties and authored short interaction scenarios remain the next training work. Do not interpret reward implementation or unchanged-policy smoke results as evidence of improved aggression.
## AI reaction fairness and overtake setup — October 15
- [x] The evolved controller previously bypassed the classic reaction profile: its emergency guard could turn on the next 120 Hz tick. Tactical and emergency turns now schedule a seeded, per-intention delay; changing the intended turn restarts the delay. Keep the six-tick turn cooldown. Human input remains immediate.
- [x] Delay is `max(0.08 seconds, 0.65 × cycle-length travel time) + U(0,1) × cycle-length travel time`, sampled at the current speed and rounded up to simulation ticks. A 10,000-sample seeded check found 32.57% of braking-speed reactions, 12.28% at cruise and 0% at full turbo fit within one cycle length. This is a reaction-to-new-intention constraint, not a ban on preplanned close-clearance turns. At changing speed, traveled distance naturally differs from the sampling speed.
- [x] Allow turbo to set up an overtake beside a nearby parallel enemy before a direct cutoff exists. Require actual forecasted ground gain, available charge/reserve, clearance and a safe exit. Reward progress toward the lead needed for a subsequent crossing, not mere proximity. Braking remains a timing/escape candidate; empty brake reserves cannot produce a braking request. Keep the same nine candidates and existing query/work caps.
- [x] All 577 unit tests and production build pass. Three 60-second, 12-cycle smoke matches yielded five direct wall kills versus three with the reaction change alone. AI p99 was 0.141–0.262 ms and full-tick p99 0.211–0.427 ms; one cold full-tick maximum was 10.69 ms, similar to the reaction-only baseline's 10.68 ms. This small sample does not establish improved win rate or player enjoyment. Reproducible artifacts: `test-results/cycle-ai-reaction-smoke.jsonl` and `test-results/cycle-ai-overtake-smoke.jsonl`. These reaction/overtake changes were subsequently deployed as `0109fd6`; no new evolution run was part of that release.
- [x] Headless Chrome 155 on macOS passed the solo AI browser check with no page errors: live-frame AI p99 0.70 ms, dense 12-cycle/2,048-trail fixture p99 0.50 ms. Inspected `test-results/cycle-ai-arena.png`; report is `test-results/cycle-ai-browser.json`. This checks rendering and execution, not subjective driving quality.
- [ ] Next training proposal: mix 30–60-second interaction scenarios with full arena matches; add earlier confirmed kill/trap rewards, a modest no-offense outcome penalty, and bounded progress toward attack positions rather than per-frame proximity rewards. Count a victim once across direct/assist credit, retain performance gates, and evaluate unseen seeds plus human play. Existing training already limits matches to 180 seconds and rewards faster wins, but not earlier individual kills. Do not promote a policy solely for surviving longer or increasing proximity.
## Production release — October 15
- [x] Deployed TRON `ed06122` via website `7176923`, Pages run `38789514609`, and Worker version `06141c2d-3243-4d4d-8c4b-6799d21eece6`. All 574 release tests, production build and Worker dry-run passed. Public Chrome verified protocol 3, 106 decoded snapshots, camera readiness, materialization and solo restart into racing with no page errors. This includes spectator framing, five-second result hold, team formation, human wire slashes, solo materialization audio and the revised error panel. The subsequent AI changes above are not part of this release.
## Multiplayer materialization sound — October 15
- [x] MP now plays the solo `cycle-entry-startup.wav` cue 0.3 seconds into materialization, at the same gain and matching playback offset. Do not replay the transport dialogue; stop the cue at the same formation/launch boundary as solo. Suppress the older generic cue that fired prematurely when the roster changed.
- [x] Wait for cycle samples to finish loading before acknowledging camera readiness. Regression coverage checks no cue during staging, correct sample/gain/offset, no duplicate sources on repeated snapshots, and pause/resume/launch cleanup. All 31 focused audio/camera tests and the production build pass. Headless Chrome confirmed the 3.058-second solo startup buffer played once during MP materialization, with a 0.006-second catch-up offset and no staging playback. The initial browser probe copied full game state from audio initialization and timed out; lightweight audio instrumentation passed. Local changes only.
## Materialization timing and human paint — October 15
- [x] Latest direction: five-second winner/last-cycle hold; once all cameras arrive, materialization begins immediately without a countdown. Solo and MP now share one formation/launch-hold timing object so their timing cannot drift.
- [x] Apply each human cycle's black/white slash to construction lines as well as the solid body, in both the mesh and analytic renderers. Preserve the slash through the analytic solid glow. Unmarked AI cycles retain their body-colored construction lines; the existing palette still sets each wireframe's body hue and brightness controls remain active.
- [x] All 573 unit tests and production build pass. Headless Chrome confirms the readiness barrier, immediate materialization without countdown, camera hold and launch in the normal renderer without shader errors. Analytic renderer captures verify human/AI image differences during wire and solid stages, plus all four team hues. Inspected the blue-team mesh reveal with two human markings. Restarted port 5173 with the current authority; changes remain local.
## Connection error panel and local server recovery — October 15
- [x] Give the error panel balanced inset padding, a smaller responsive heading, readable message wrapping and a compact reload button. Add an accessible alert label. Inspected headless Chrome captures at 1440×900 and 390×844; no horizontal overflow. Production build passes.
- [x] Confirmed the existing port 5173 server accepted protocol 2 and rejected the new protocol 3 client. Restarted it with current code and verified protocol 3 connects. The usual local URL works again; production is unchanged.
## Multiplayer team materialization transition — October 15
- [x] Hold the winner/last-cycle result shot for five seconds before selecting the next roster. Move each seated player's camera smoothly over three seconds to the solo materialization perspective, rotated for their assigned team's wall. Use the solo close formation spacing for every MP team; queued spectators prepare a gold-team view.
- [x] Keep cycles hidden and simulation stopped while cameras travel. Each client acknowledges its rendered arrival with its round and seat; the authority waits for every connected client before immediately starting the shared 1.833-second materialization and 0.6-second hold (no countdown timer or countdown beeps), then clients cut to normal cycle follow. Preserve the end-state camera position as the next flight's departure point. Reduced motion jumps to the destination but still acknowledges readiness.
- [x] Clear readiness on roster changes, reject stale acknowledgments, release disconnected clients from the barrier, and disconnect clients that cannot prepare within 45 seconds. Late arrivals during materialization queue for the next round. Return for a sole human now runs this camera sequence instead of skipping directly into racing.
- [x] Remove the player's team label from MP countdown text. Retain team names in winner announcements.
- [x] Protocol 3 requires the matching client and server; production remains on protocol 2 until a coordinated deployment. Older connections are rejected rather than silently bypassing camera readiness.
- [x] The initial transition passed 572 unit tests, production build and Worker deployment dry-run. The multiplayer browser lifecycle also passes with two rendered clients, 14 connections and an overflow queue. Headless Chrome on macOS verifies a delayed second client holds materialization, cycles remain hidden until ready, the camera holds throughout the reveal, and launch cuts to the player's cycle. Inspected close gold-team materialization and launch captures in `test-results/mp-team-materialization.png` and `test-results/mp-team-launch.png`. Changes are local, not deployed.
## Spectator opponent framing — October 15
- [x] When following a cycle before a match or after death, orbit the existing camera boom to frame the nearest living opposing cycle as well. Keep the current zoom, height and speed-dependent follow distance; ease the orbit with a bounded angular speed and aim between the two viewing directions. Exclude allies, dead cycles and escaped cycles; resume ordinary follow when no opponent remains.
- [x] Preserve opening shots, target-switch transitions, death holds and camera collision clearance. Walls can still shorten the boom, and abrupt opponent changes can temporarily leave the frame while the orbit catches up.
- [x] Production build passes. Headless Chrome on macOS rendered the full arena with a gold followed cycle and nearby blue opponent both visible; inspected `test-results/spectator-opponent-framing.png`, with no page errors. Dynamic feel still needs user playtesting.
- [x] All 568 unit tests pass, including pair visibility in landscape/portrait views and countdown/racing states, unchanged follow distance, nearest-opponent filtering, bounded rotation and unchanged driving framing.
## First-match jitter and startup update recovery — October 15
- [x] Reproduce clock lag after a two-second first-load rendering pause while snapshots continue. Local prediction and remote playback now re-anchor after a gap longer than their 0.2-second frame bound, instead of discarding elapsed time and advancing at snapshot boundaries while catching up. Normal frames retain continuous rate correction.
- [x] Reproduce a separate local freeze/jump when high RTT drives prediction into its 200 ms extrapolation cap. Reserve two snapshot intervals below that cap (100 ms maximum RTT-derived presentation lead at current settings). Keep wire timestamps based on estimated authority time, independent of capped visual lead; immediate turns remain local and fractional. Extend input coverage to 500 ms RTT.
- [x] Remove automatic home-screen service-worker activation/reload. Background updates wait until existing tabs close; explicit `update.html` recovery remains available. Chrome verifies a waiting update does not reload the menu and old-cache/missing-module recovery still works.
- [x] 565 unit tests, production build, and isolated headless Chrome multiplayer lifecycle checks pass with 250 ms transit each way plus injected jitter. The first browser attempt shared the ordinary development server and timed out on sole-player restart; the isolated arena passed.
- [x] Deployed TRON `afa6c09` through website `0e78e3a`; Pages run `38761215638` succeeded. Production Chrome confirmed the prediction-headroom code in the served client, 52 decoded snapshots, immediate solo restart and no page errors. A 30-second public packet recording had 48.1 ms median, 64.1 ms p95 and 118.2 ms maximum packet gaps, no backward remote poses, and 32 held cycle-frames in 25,923 samples. A separately observed 1.7 m local jump was a race-start transition, not evidence of continuous driving jitter. Synthetic first-load/high-latency regressions reproduce concrete failure modes; user-device confirmation remains necessary.
## Cycle formation body colors — October 15
- [x] Use the cycle body color for materialization wireframes: copy the actual body material in the mesh renderer and use the existing body-color uniform for contours, grid and halo in the analytic renderer. Preserve line intensity, coverage, halo attenuation, fade stages and solid-body glow timing.
- [x] Production build and Chrome materialization shader/lifecycle check pass; capture all four team colors and inspect blue/red wire stages. Publish with the verified multiplayer prediction-clock correction below. These are client-only changes; the existing multiplayer Worker remains compatible.
## Multiplayer follow-camera packet jitter — October 15
- [x] Reproduce backward local follow-target motion under 20–50 ms variable snapshot delivery. Remote cycles already use monotonic buffered playback, but the local prediction/camera clock reset from each packet arrival and RTT sample. Keep a continuous prediction clock, correcting its rate by at most ±10% over a 0.25-second correction interval; reset on round changes. Preserve immediate fractional input prediction, authoritative collision state and the existing 75 ms remote buffer.
- [x] At 144 Hz rendering, the regression reproduces up to 0.885 m backward travel with the old clock; the new clock has no backward steps and at most 0.0267 m deviation from the nominal per-frame distance. Also exercise an RTT change, new-round reset, immediate input, joined trails and no early future turn. Correct an older test's artificial backward wall-clock jump so its future input occurs after preceding frames.
- [x] 564 unit tests and production build pass. Headless Chrome on macOS passes the two-rendered-client multiplayer lifecycle test with injected packet delays (`TRON_MP_JITTER=1 node tests/multiplayer-browser.mjs`). Runtime/browser tests do not establish subjective production smoothness. Deployed in TRON `e5f9d72` through website `59f21d0` (Pages run `38758748338`). Production Chrome received 68 snapshots with no page errors and verified immediate solo restart. Long stalls, speed changes and authoritative input reconciliation can still require position corrections.
## Evolved AI and trap attribution release — October 15
- [x] Release checks: 563 unit tests, production build/asset audit, Worker dry run, and headless Chrome roster, MCP game-launch and two-client multiplayer checks pass on macOS. Publish the playtested `aggression-1-0` policy for solo/MP, persistent trap-assist attribution and hollow roster dots, and MCP closing-sentence fade. Later training found no qualifying improvement; retain the playtested weights.
- [x] Deploy compatible multiplayer Worker `3f4f21f1-73fa-43c5-ba0d-c383a16eecdb`. Optional trap-assist snapshot data preserves the existing protocol. Training and research artifacts remain offline; no automatic training runs in production.
- [x] Publish TRON `9863ee0` through website `e090204`; Pages run `38754625290` succeeded. Production Chrome entered multiplayer, decoded 55 snapshots with no page errors, and verified solo Return restarts immediately without countdown. Historical local-only notes below describe their state when written. This completion record was added after deployment.
## JavaScript cycle AI plan — October 14
- [x] Write the [fast, compelling cycle AI plan](cycle-ai/index.html), prioritizing bounded runtime cost above tactical sophistication. Cover cutoffs/containment, joint turn and reserve planning, competent seeded variation, offline win-rate evolution and an optional tiny neural scorer.
- [x] Implement the first bounded shared JavaScript controller, joint turn/turbo/brake scoring, cutoff/containment incentives, continuous safety queries, deterministic near-best variation and development tuning. Retain classic/open-road control and isolate human seats. Cycle JEV requests are skipped for the new mode.
- [x] Add seeded baseline/stress/comparison tools, versioned offline policy evolution and an optional tiny neural scorer. No evolved/neural policy is promoted. Reference M2 Max measurements: dense twelve-bot AI p99 about 0.49 ms in Node and 0.4 ms in Chrome; new team wins 9/12 spawn-rotated mixed matches, a small correlated sample. Detailed commands, runtime limits and evidence are on the plan page.
- [ ] Complete slower-device/server measurements, advanced coordinated maneuvers, statistically supported policy promotion and real-player review. This change is local; no deployment requested.
## Cycle roster wall eliminations — October 14
- [x] Track each opposing cycle eliminated by a cycle's trail and show a 3 px dot in the victim's team color beneath the wall owner's roster circle. Center each row, wrap at five dots and preserve roster placement. Retain awards after owner death; exclude self/friendly crashes and duplicate victim credit; clear on new rounds.
- [x] Include the optional award list in full and compact multiplayer snapshots. Remove the redundant USERS and WAITING totals from the active multiplayer status line; retain latency and the spectator's own queue position.
- [x] Headless Chrome roster checks pass for colors, centered wrapped rows, unchanged active-roster position and round reset; rendered capture inspected. Twenty-one focused collision/reward/multiplayer tests pass, including full/delta snapshot awards and reset. Implementation is local pending a requested deployment.
## Multiplayer waiting roster — October 14
- [x] Show one plain hollow gray circle per waiting player without a seat (eliminated seated players remain only in the colored roster), linked by gray lines and followed by a chevron toward the active teams. Position the queue independently to preserve the centered active roster; hide it when the queue is empty.
- [x] `node tests/cycle-roster.mjs` passes in headless Chrome on macOS: three/one/zero waiting players, unchanged active-circle coordinates, hollow gray styling without stripes, connectors, restart and hiding. Inspected the rendered roster capture. Long queues may extend beyond the left edge on narrow screens.
## Content-addressed media and incremental offline updates — October 14
- [x] Extend existing Vite-hashed models/music to 65 public media files: audio, fonts, images/icons and BIT models use `{name}-{sha256-prefix}.{extension}` in production. Keep logical development paths and old production aliases. Build `asset-manifest.json`, resolve dynamic audio/model requests through the manifest, and rewrite copied BIT documents/scripts without modifying Vite's already-hashed chunks.
- [x] Precache only hashed media URLs. Record per-file SHA-256 metadata and copy matching cached responses into a new release cache; download only missing or changed files. Migrate older caches without metadata by verifying their stored bytes once. Preserve the working cache if installation fails, and remove old caches only on activation.
- [x] Remove version-query audio requests from current callers. Resolve older `?v=` audio aliases to the hashed cached response, including byte-range requests, without intercepting API URLs. Fix BIT's font-preload path.
- [x] Validation on macOS/headless Chrome: 536 unit checks pass; five cache regressions cover unchanged/changed files, legacy migration, versioned ranges and failed updates. `node tests/browser/pwa-incremental.mjs` uses the actual production cache and changes one script: only `bit/config.js` downloads, while all media is reused and the active cache survives until activation. `node tests/pwa.mjs` and `node tests/pwa-update.mjs` pass offline play, hashed install icons/fonts, ranged audio, non-interrupting updates, older-cache recovery and missing-module recovery. Async browser assertions now poll for completed worker installation rather than accepting a pending promise.
- [x] Production build audits verify every emitted public-media filename against its actual content and original source. Offline cache remains approximately 89.7 MiB; compatibility copies remain deployed but are excluded from precaching.
- [ ] Faster game initialization remains separate: the cached-load profile measured about 5.8 seconds dominated by simulation setup, scene construction, model preparation and shader compilation. This change reduces network traffic across releases, not that CPU/GPU work.
## Module-load recovery — October 14
- [x] Route the error screen's Reload action through the uncached `update.html` recovery page so an old entry document is not simply reloaded from its existing service-worker cache. Identify module-loading failures with an uppercase update instruction.
- [x] Let the recovery page return home after first-time service-worker activation as well as replacement of an existing worker. Preserve the normal game's policy of waiting until the home screen to activate background updates.
- [x] `npm run build` and `node tests/pwa-update.mjs` pass on macOS/headless Chrome. Browser regressions cover an old cached entry updating into a playable game, a deliberately missing multiplayer module, failed first cache installation, the actual Reload click visiting the update page, and return to a usable program menu. Fresh production multiplayer also passed with advancing snapshots and no page errors; the user confirmed the original tab works again. The precise failing request in the user's Safari tab was not captured.
## Repository organization and video removal — October 14
- [x] Implement [organization stages 1–6](organization/index.html): focused shared simulation modules, discovered unit tests, grouped browser/tool scripts with CLI compatibility, canonical runtime/source/reference directories, asset catalog and explicit production packaging.
- [x] Remove reference videos entirely and ignore video extensions. Rewrite video paths out of Git history; retain runtime audio and still images. Extraction inputs must be supplied separately.
- [x] Verify 531 unit checks, Worker dry run, PWA offline/update behavior, multiplayer browser lifecycle with jitter, and production subdirectory paths on macOS/headless Chromium.
- [x] Deploy TRON `de3453e` through dekorte.com `b3e161d`, Pages run `38617728189`. Worker version `0352103a-89e1-4d2b-880c-3bb9be2c2c41`. Production Chromium check: menu entry, 48 decoded snapshots, 12 cycles, solo Return skips countdown, no page errors; canonical/legacy cannon audio bytes match. Protocols 1 and 2 each delivered validated snapshots. Initial clean CI build exposed a missing audit-output directory; fixed and verified without a preexisting directory.
- [ ] Retire compatibility paths only after deployed-client cache review. Real-player feel remains unverified.
## Immediate multiplayer turns near fixed walls — October 14
- [x] Remove the eight-tick input lead from outgoing commands and local motion prediction. The authority validates timestamp freshness but applies accepted commands on its next 120 Hz tick, including valid future-dated commands from cached clients. Preserve sequence checks, turn-rate limits and authoritative collisions; do not backdate impacts.
- [x] Retain a local fractional keypress tick for pending-input replay. Apply the turn at the currently displayed position in the same frame, finalize the preceding trail at that corner and continue the new segment. Only integer timestamps are sent on the wire. Remote playback remains buffered at 75 ms.
- [x] `npm test`: 528 checks pass. Fixed-wall regressions approach an existing arena boundary with 1.8 m of collision-shape clearance at 38.4 and 96 m/s. Immediate scheduling survives at zero and two ticks (16.7 ms) of simulated input transit; the former eight-tick schedule crashes. At six ticks (50 ms) transit, both approaches are already too late. With two ticks of transit, accepted corner displacement is 0.64 m normally and 1.6 m at turbo. These are deterministic simulation checks, not measured real-network latency.
- [x] Local prediction tests cover zero/80 ms RTT estimates, fractional keypress times, same-frame direction changes without position jumps, joined trail corners and no repeated turns across frames. `npm run build`, `npm run worker:check` and `git diff --check` pass. Restarted local Vite on port 5173 so its authority loads the new handler. `TRON_MP_JITTER=1 node tests/multiplayer-browser.mjs` passes in headless Chrome on macOS with two rendered clients, twelve cycles and fourteen connections.
- [ ] Real-player fixed-wall feel and realistic network-transit review; bounded rollback remains unimplemented. Included in the October 14 reorganized production release above.
## Remote cycle playback under packet jitter — October 14
- [x] Replace remote forward prediction with a bounded snapshot history and continuous monotonic playback clock. Packet arrival adjusts playback speed by at most 10%, rather than resetting the time origin. Use a named 75 ms presentation buffer; the tested 50 ms buffer occasionally ran dry under 15–50 ms delivery jitter. Local input prediction, server timing and collision authority are unchanged.
- [x] Interpolate remote positions along confirmed trail corners, trim their displayed trails to the same pose and remap active segment indices. Keep current authoritative HUD/reward data and deaths; never resurrect a dead cycle to play an old pose. Hold at the latest confirmed point during network stalls; clear history on new rounds and disconnect disposal.
- [x] `node --test tests/multiplayer-playback.test.js tests/multiplayer.test.js tests/multiplayer-audio.test.js`: 21 checks pass. At 144 rendered frames/sec with alternating 15–50 ms snapshot delivery delays, the straight-line normal-speed test reduces worst frame-step error from 1.344 m to 0.0267 m, with no backward/held frames after warm-up. Also check multiple corners, trail attachment, local isolation, immutable snapshots, history bounds, stalls and round reset.
- [x] `TRON_MP_JITTER=1 node tests/multiplayer-browser.mjs` passes in headless Chrome on macOS with two rendered clients, twelve cycles, fourteen connections and jittered WebSocket delivery. Inspect the normal-renderer capture. `npx vite build` and `git diff --check` pass; existing build warnings remain.
- [ ] Real-player motion and close-cutoff review. The buffer adds approximately 75 ms behind received state (plus network transit); it improves presentation at a latency cost. Longer stalls can still hold remote motion. Deployed TRON `b9b1948` through dekorte.com `c5a52e0`, Pages run `38608501930`. Full release checks: `npm test` passes 525 tests and `npm run build` passes. Production Chrome verified the published 75 ms playback code, 53 decoded snapshots, a six-cycle arena and immediate solo Return restart, with no page errors. The existing server was unchanged.
## Multiplayer production release — October 14
- [x] Validate compact snapshots, fractional-tick presentation and retained eliminated-human roster stripes: `npm test` passes 522 tests; `npm run build` and `npm run worker:check` pass. Headless Chrome on macOS passed the two-rendered-client multiplayer check in the preceding change.
- [x] Deploy the compatible Worker first: version `b669337a-c12e-4c6d-8bdc-81fc946f58ec`. Protocol 1 remains available for cached clients.
- [x] Publish TRON `e4612e7` through dekorte.com `8991c03`; GitHub Pages run `38602487215` succeeded. Production protocol 1 and 2 probes passed. Headless Chrome on macOS decoded 50 compact snapshots in a twelve-cycle arena, entered from the menu and verified solo Return skips countdown, with no page errors. The first game-entry check timed out at 60 seconds; a diagnostic retry with a 90-second limit passed. Sustained real-player smoothness remains unmeasured.
## Multiplayer motion between simulation ticks — October 14
- [x] Remove whole-tick quantization from multiplayer presentation prediction. Keep integer ticks for input scheduling and pending turns, and advance the fractional remainder for bikes, attached trail endpoints and the following camera. Preserve server simulation and collision timing.
- [x] `node --test tests/multiplayer.test.js tests/multiplayer-audio.test.js`: 18 tests pass, including 2 ms presentation steps, no early future turn, unchanged authoritative state, trail endpoint alignment and stale-state bounds. `npx vite build` passes with existing script/font/chunk warnings. `node tests/multiplayer-browser.mjs` passes in headless Chrome on macOS: two rendered clients, twelve cycles, fourteen connections, queue and restart lifecycle checks, no browser errors.
- [ ] Measure live frame pacing and snapshot correction jumps. Snapshot arrival still resets the prediction baseline; this change addresses quantized motion, not all potential multiplayer stutter. Subjective smoothness requires playtesting. Included in the October 14 production release below.
## Multiplayer low-latency hosting plan — October 14
- [x] Record the selected [multiplayer migration plan](multiplayer/index.html): compact-protocol production baseline, existing WebSocket authority on a measured Google Cloud VM, then loss-tolerant WebTransport with WebSocket fallback and a same-server comparison.
- [x] Preserve 120 Hz simulation; specify reliable trail/input recovery, explicit sequence acknowledgments, baseline repair, shared motion prediction and bounded tick validation. Mark the earlier browser-host proposal as historical.
- [ ] Measure production latency/jitter and costs, validate packet loss/reordering and browser fallback, then stage an explicitly authorized rollout. Protocol 2 is included in the October 14 release; no VM or WebTransport service has been provisioned. Documentation changes do not constitute deployment approval.
## Multiplayer focus, team sizes and HUD — October 13
- [x] Disable automatic blur/visibility pause and any-key resume in multiplayer. Clear held inputs and send neutral controls while unfocused; preserve Escape confirmation and single-player pause behavior.
- [x] Cap teams at three cycles. Use two to four teams, expanding during countdown when more humans join without delaying launch; up to twelve humans can play.
- [x] Preserve race and cycle object identities between multiplayer snapshots so engine loops and launch cues do not restart every frame. An audio regression verifies continuous engine ownership and a real reset on the next round.
- [x] Put C / CONTROLS below multiplayer status rather than on the same line.
- [x] All 511 unit tests plus the multiplayer audio regression, production build and Worker dry run pass. Headless Chrome verifies no blur pause, three-cycle teams and non-overlapping controls/status, plus both renderers.
## Multiplayer local prototype and human stripes — October 13
- [x] Add LIGHT CYCLES MULTIPLAYER with one authoritative twelve-cycle room, AI vacancy filling, randomized teams, FIFO spectators and individual survivor seat retention.
- [x] Share fixed-step simulation with the server; sequence and bound inputs, stream trail deltas, predict client motion, and leave collision outcomes with the server. Stop empty rooms and keep multiplayer code out of the single-player loading path.
- [x] Mark human cycles with a thick diagonal half-black, half-white stripe in both renderers and the roster; remove provisional dots and floating markers. Preserve transparent eliminated roster interiors.
- [x] Remove grid clouds from gameplay loading and extended model credits; retain archived asset provenance.
- [x] Validation: 510 unit tests pass, build and Worker dry run pass. Headless Chrome on macOS verifies two rendered clients with fourteen total connections, queue overflow, shared state, no local match reset and no cloud asset request. Both ray-cast and fallback stripe shaders compile without browser errors; inspected both racing captures.
- [x] Authoritative Worker deployed and production-origin WebSocket snapshots verified; static client release follows.
- [ ] Real-world latency/cutoff playtests. No rollback, reconnect identity or measured hosting budget yet. See [multiplayer](multiplayer/index.html).
## Wheel reflections after player death — October 13
- [x] Preserve each cycle’s reflection clock across AI random-state changes; reset only on a new round or simulation time rewind. Surviving cycles continue their speed-dependent flicker after the player dies.
- [x] Verified with `node tests/home-menu-order.mjs` in headless Chrome on macOS: ray-cast renderer stays active, all surviving reflection phases advance after player death, and the dead cycle has zero reflection strength. Initial sandboxed Chrome launch aborted; rerun outside the sandbox passed.
## MCP game-grid transfer and search — October 13
- [x] Add bounded native Gemini function calls: `send_to_light_cycles` is the only game destination, allowed before spoken text; Brave `web_search` runs only on the relay with its private credential, at most once per turn. Preserve Gemini thought signatures through tool results. Require a newline after every spoken sentence.
- [x] Reveal the animated face full-screen at announcement audio start, wait for speech completion and one additional second, then dissolve directly into the prepared cycle scene. Fade the MCP music over the same 1.1 seconds and stop/reset it; lower its normal level by 30% (0.60 to 0.42). Escape invalidates pending transfers.
- [x] Show `CONNECTING TO CORE...` immediately while MCP audio/resources prepare. Cache its music Blob in a dedicated `tron-mcp-music` IndexedDB store, keyed by the asset URL; release Blob URLs when leaving.
- [x] P toggles a smooth first-person cycle camera; hide the player’s cycle in the cockpit and restore it on exit. First-person mode is limited to a living player, with the normal spectator camera after death.
- [x] Add immediate two-note confirmation feedback to Return Home, allowing 200 ms before navigation so the beep is not cut off. Honor main-game mute.
- [x] Restrict the gray window return to the projecting rear cut face, excluding the roof lip. Give that downward-facing panel a small reflected-light floor while retaining the underbody pigment and the rest of the cycle’s deep shadows.
- [x] Validation: 504 unit tests pass; headless Chrome on macOS passes `tests/mcp-game-launch.mjs` (audio timing, face, one-second hold, direct scene transition and music-fade trigger) and `tests/exit-confirm.mjs` (CLU/cycles/BIT confirmation and cancellation). Inspected the normally lit rear-window render in `test-results/cycle-window-rear-solid.png`. `tests/cycle-first-person.mjs` passes cockpit toggling, height and hidden-player checks; `tests/mcp.mjs` passes microphone permission, speech timing and music continuity checks. A live relay search returned an answer. These automated checks do not establish subjective sound quality.
## Cycle attack tactics and cockpit fade — October 13
- [x] JEV favors closing on observed enemies, intercepting ahead, cutting escape lanes and coordinated boxing-in, with self-preservation and friendly escape routes retained. Clearance score is not attack value. Local collision avoidance and observation limits remain authoritative.
- [x] Separate materialization color coverage from cockpit opacity so entering first person fades the cycle transparently without changing its colors.
- [x] Validation: 23 JEV/protocol/Worker unit tests passed; production build passed; headless Chrome on macOS passed the first-person entry/exit and hidden-player check. Tactical effectiveness still needs live play assessment.
Multiplayer proposal: [Multiplayer light cycles](multiplayer/index.html) compares timing, authority, hosting cost and staged acceptance checks. The public server-authoritative arena is deployed; compact protocol 2 remains local, and real-network latency testing and the staged hosting migration remain pending.
## MCP transfer reliability and panel activity — October 13
- [x] Require the real light-cycle transfer tool when the user asks to enter the grid or MCP sentences them to it; forbid dialogue-only promises of a transfer. Require the announcement to end with “END OF LINE.”
- [x] Animate the six existing ENCOM badge lamps while listening, awaiting Gemini or generating speech. Stop the animation for playback, cancellation and errors; reduced motion uses a steady brightness change.
- [x] Remove completed cycle-smoothing, soundtrack-cue and mobile-warning items from TODO.md; retain partially completed projects and clarify the remaining spoken dialogue clip.
- [x] Validation: real Gemini initially returned only a promise, then returned the actual cycles action and END OF LINE with the revised prompt. MCP unit checks and production build pass. Headless Chrome passes STT/LLM/TTS lamp states, face/audio timing, one-second hold and direct game transfer. Inspected the enlarged badge capture.
Project cleanup proposal: [Project organization plan](organization/index.html). Inventory and migration stages only; no folder moves have been performed.
## Project documents
## Light cycle arena asset preparation — September 23
- [x] Isolate Daniel Preti’s arena architecture from the supplied OBJ; preserve source scale and retain original files.
- [x] Add a runtime film-reference edge treatment: near-black walls with blue-gray outlines and deep purple symbols with violet borders. Inspect the Chrome preview; retain original model materials in the GLB.
- [x] Remove the dense modeled floor and render a separate white-on-black procedural grid with the existing game grid spacing, antialiasing and fade. Use this in both the arena preview and the game.
- [x] Reload the 447 KB, 8,349-triangle standalone GLB in Chrome and inspect rendered overview/interior captures. Geometry excludes the original floor tiles, bikes, trails and exterior scenery.
## Hit-centered Recognizer explosions — September 24
- [x] Use the projectile impact as the world-space blast center. Each detached section receives an outward impulse from that point in all three dimensions, without a shared directional bias or added upward kick.
- [x] Preserve vehicle momentum, existing impulse magnitude tuning, Earth gravity, section timing and debris cleanup. Explosions without a recorded hit use the vehicle origin.
## Film-style cycle explosions — September 24
- [x] Replace the expanding crash ball with a white-blue needle burst, two staggered red semicircular arcs, tumbling bright chips and wheel rings, and fragment ground silhouettes.
- [x] Use the supplied `references/images/cycle explosion` frames as the visual reference. Assuming consecutive captures at 24 fps, the main rays occupy nine frames (0.375 s); fragments fade out by one second. Effect animation uses 24 fps samples without changing gameplay simulation cadence.
- [x] Pool six bursts, derive motion from crash age and stable IDs, orient arcs using the crashed cycle's heading, and retain the existing independent three-second trail hold/flash/lowering sequence.
## Cycle trail and lighting follow-up — September 24
- [x] Give opaque trails 18 cm thickness, a grounded curved recess around the rear wheel (with tire clearance, not an axle-height taper), brighter blue, periodic vertical bands and highlighted edges.
- [x] Keep a destroyed cycle's barrier for three seconds, flash and settle over 0.24 seconds, then lower it to zero over 0.7 seconds. Release only that cycle's collision cells when the trail disappears.
- [x] Keep sharp right-angle turns independent of the decorative floor grid. Remove the floating arena scoreboard; matches continue internally.
- [x] Repair duplicate/quantized model surfaces, preserve authored smooth normals, and replace cracked hub caps with smooth geometry at measured source dimensions.
- [x] Add a compact shadow tile per cycle for self-shadowing and reduce ambient wash on cycle materials. Dispose the atlas with the arena scene.
## Arena visual refinements and uninterrupted inspection — September 24
- [x] Make trail walls fully opaque with saturated amber/deep-blue bodies, a soft vertical gradient and a pale flare behind each bike. Match their height to the bike body (1.5 m). Interpolate and trim trail ends to the rear of each bike; brightness follows distance along the trail through corners.
- [x] Darken the blue bike's painted body and trim while retaining white/silver components and original source materials in the GLB.
- [x] Double arena grid-line width, reduce cell spacing by 10% to 4.32 m, and add a subtle navy floor tint. Cycle movement uses independent 4.8 m decision lanes so turns need not coincide with the 4.32 m floor grid.
- [x] While free-camera inspection is active, protect Clu from bullets, stomps and debris damage and suppress end-screen transitions. Leaving inspection restores damage; restart clears inspection. This keeps running spectator matches from being interrupted by Clu's off-camera death.
## Light-cycle competition and arena patrol — September 24
- [x] Extract gold, blue and red bikes into standalone GLBs; use gold and blue for two teams of three inside the arena. Separate authored dimensions, simulation and rendering. Use 3.6 m bikes at 38.4 m/s on independent movement lanes.
- [x] Use deterministic local steering with bounded free-space search, forward clearance, friendly-lane avoidance and opponent interception. All bikes share the arena map and visible trail occupancy; this race does not call JEV.
- [x] Resolve each grid crossing simultaneously. Arena bounds, own trails, friendly trails and opposing trails eliminate bikes; head-on same-cell collisions eliminate both. Trails persist until the round resets. Last surviving team wins; after two minutes compare survivors, otherwise draw. Keep team scores, a three-second starting countdown and six-second result interval.
- [x] Render imported bikes, team-colored trail walls and crash flashes. Cap trail geometry by maximum round travel. The race uses the fixed simulation timestep and resets with the game.
- [x] Add one Recognizer circling above the arena wall with original flight dynamics. Sightings, radio contact and audible threats interrupt patrol; stale target knowledge returns it to the perimeter. The routing works in classic/local/JEV modes without querying JEV for routine patrol.
- [x] In free-camera mode, Space runs/pauses the world while retaining camera control; camera keys do not drive or fire Clu. C/Home/Space provides a quick way to watch the arena.
- [x] Enemy tanks clone Clu's colors and materials without the former enemy recoloring; materials remain independently owned.
- Race obstacles and participants are confined to their own simulation: cycles do not fight Clu or become Recognizer targets yet. Source texture limitation is recorded in Models. Browser captures establish initial rendering; human play and Safari review remain pending.
## Loading terminal — September 24
- [x] Show a static LOADING... terminal before application initialization; keep the access terminal and start controls hidden until assets, physics, scene setup, and the first rendered frame are ready. Initial HTML supplies the loading text, with the shared terminal typography and ENCOM logo. Three fixed-position dots appear successively on a 1.8-second CSS opacity cycle; reduced-motion preferences show static dots. This is an activity indicator, not measured progress.
## Arena placement and inspection camera — September 23
- [x] Make the light-cycle floor grid five times denser (4.8 m cells), scale its line width down proportionally, and dim the lines. Preserve subpixel line coverage so distant grid lines do not expand into a glowing field.
- [x] Place the arena east of the central labyrinth, grid-aligned with a narrow gap; avoid overlap with maze bounds for alternate layout seeds. Retain the imported scale, colored wall outlines, and white-on-black floor.
- [x] Cut the arena footprint out of the main ground shader to remove overlapping floor surfaces. Add a continuous exterior enclosure behind the imported decorated inner walls, using the same near-black surface color and blue-gray edge outlines.
- [x] C toggles a free inspection camera during play or pause. Freeze simulation and JEV planning while inspecting; restore the previous running/paused state on exit. WASD moves, Q/E descends/ascends, drag or arrow keys looks, Shift moves faster, Home frames the arena, C/Escape returns to Clu.
- [x] Chrome browser regression verifies startup, arena loading, camera movement with frozen simulation, and restoring both running and paused states. Inspect the rendered arena/labyrinth overview. Production build passes.
- Arena architecture is scenery to Clu and the main-world enemies; their wall collision and arena entrances are not yet implemented. The separate light-cycle race enforces its own arena bounds and solid trails. Safari visual review remains pending.
## Current direction — open-ended simulation
### Central labyrinth placement and population — September 23
- [x] Fix its beam to the measured center of the blueprint’s circular courtyard (pixel 724, 514), instead of choosing a random central navigation cell.
- [x] Scatter eight tanks and six Recognizers among interior annular sectors. Tank spawns must have a swept-clear route to the courtyard; other mazes retain their existing population.
- [x] Separate clipped wall-shadow decals from their receivers by 0.02 m before float32 upload, retaining depth testing and stencil union. This prevents coplanar rounding artifacts without softening the shadow edges.
### Central labyrinth — September 23
- [x] Trace the supplied Big labyrinth image into 116 wall islands, preserving the circular courtyard and three enclosed openings. Share polygon extrusion/collision code with the original blueprint maze, including hole-aware roof triangulation.
- [x] Add the labyrinth as a fifth, grid-aligned site at the center of the four existing mazes. Use 2.4 meters per image pixel and the existing 54-meter wall height. The default layout fits without moving the outer sites; placement can move overlapping sites outward if needed.
- [x] Include a fifth beam, four transport pads and normal maze patrols. Keep authored/reference fixtures unchanged. Verify collision/roof agreement, separation across four layout seeds, courtyard route accessibility, game startup and rendered geometry.
### Smarter stomps with original motion — September 23
- [x] Preserve the earlier film-like movement: thrust 24.2 m/s², yaw cap 0.62 rad/s, yaw acceleration 0.8 rad/s², ordinary drag/braking, 1.3-second fold, and 85 m/s² powered stomp descent. Remove all close-attack performance overrides. Cruise/max speeds are unchanged.
- [x] Improve decisions rather than physics: approach overhead before descending, retain a progressing attack lead, align for nearby walls, and allow a healthy pursuing lead to take a safe attack opportunity.
- [x] Brake as soon as the predicted stopping path provides a valid stomp, rather than insisting on an exact waypoint or redundant final heading. Keep fresh personal sight, cooldown, drift prediction and swept wall clearance checks.
- [x] Stomp drop/landing contact with debris cannot damage the attacker; normal flight and weapon hits remain vulnerable.
- [x] Double horizontal cannon scatter: Clu ±1.2°, enemies ±2.4°. Level aim has zero vertical scatter; gunner shots remain exact. Deliberate vertical auto-aim to airborne targets is retained.
- The user’s motion-fidelity correction supersedes both the universal four-second stomp deadline and the subsequent three-second full-rotation tuning. Regression cases check successful attacks from eight headings without exceeding original thrust/yaw limits, not an artificial deadline.
### Pursuit attacks and JEV toggle — September 23
- [x] Healthy lead Recognizers may commit a safe stomp while pursuing, without requiring JEV to select a separate strike maneuver. Pursuit endpoints use the wall-aligned landing pose; fresh sight, cooldown, support assignment, braking drift and full descent clearance remain mandatory.
- [x] N toggles JEV/local tactical control during play or pause without restarting. The stats box displays ON/OFF. Disabling JEV disengages autoplay; U enables JEV when starting autoplay. Mode preference persists.
- [x] Verify simulated JEV pursuit responses in a tight oblique corridor and a two-Recognizer encounter, plus Chrome keyboard/autoplay behavior. Full simulation suite: 260 passed. Live provider choices and actual play remain subject to review.
### Progress-aware attack leadership — September 23
- [x] Renew the lead recognizer’s twelve-second assignment after at least five meters of combined closing/descent progress. Do not rotate a progressing attacker out midway through its approach; retain blocked/dead/ineligible handoff and expiry for stalled attackers.
- [x] Reproduce two aircraft approaching stationary Clu from opposite sides at 120 m: previously no stomp in 120 seconds; corrected local control completes the stomp in approximately 24.27 seconds with one consistent lead. Keep one active strike at a time.
### Victory, surface impacts and attack support — September 23
- [x] Add the explicitly requested win condition: complete every data beam, freeze the round, fade to the terminal and print `docs/victory.txt` continuously at 45 characters/second. This supersedes the earlier no-win-screen direction for this condition only; empty beam lists do not win.
- [x] Drive victory printing from monotonic elapsed time rather than the capped simulation delta; reveal at most one character per frame as an immutable glyph element inside stable line nodes (no catch-up word bursts or DOM geometry/computed-style measurements) and skip hidden 3D rendering on the victory page.
- [x] Use the victory typography size (`min(1.5vw, 2.15vh)`, 1.12 line height) across opening, death, victory and credits text, without content-dependent sizing.
- [x] Add temporary Shift+8 victory preview for end-screen review (also available while paused).
- [x] Leave the victory page visible until Return; then play the existing credits text/music without the detachment message. Preserve death behavior and clean restart.
- [x] Reuse the optical double-ring burst for projectile wall/floor contacts and visible vehicle armor. Orient effects to surface normals with an 8 cm offset, retain depth testing, cap active effects, freeze them on pause and dispose them on expiry/reset.
- [x] Retain recognizer support assignments outside the old 140 m recruitment radius and when support units lose personal sight. Choose one lead attacker, one spotter and separated exit-cover positions using ground-connected map branches; use overhead slots where no branch is available. Cache formation goals until leadership/membership or recorded contact changes. Preserve radio limits, fresh target knowledge, wounded withdrawal and attack handoff.
### Teleporter border placement — September 22
- [x] Generate floor-shader pad borders from the active scenario’s maze sites, matching simulation placement. Remove the renderer’s use of the legacy authored-world pad constants. Include pad geometry in the shader cache key so different seeds/site counts cannot reuse stale positions.
- [x] Check all 52 pads across two blueprint seeds, an authored seed and a single-site authored layout. Preserve floor-integrated borders, grid-line thickness and horizon fading.
### Confined stomp positioning — September 23
- [x] Search nearby landing positions in sixteen directions at two radii and include exact local wall-edge headings. Keep the whole aircraft clear and reserve positional tolerance inside the existing crush trigger; do not widen the damage radius.
- [x] Settle strike approaches within 0.35 m and brake angular motion before committing to a descent. Preserve forward-only movement, collision checks, attack coordination, and fresh visual-contact requirements. This applies to Tactical/local and JEV maneuver execution; Classic behavior is unchanged.
- [x] Verify oblique narrow-corridor and close-wall poses, a complete collision-free corridor stomp, and the full simulation suite.
### Recognizer spotlight surface reach — September 23
- [x] Extend downward search/target beams to the floor using their current projector height and direction, including the ribbon width. Preserve wall clipping and sensing limits. Do not fade the final segment when a ray ends on a surface.
- [x] Check high-altitude ground/raised-surface endpoints and browser rendering, wall clipping, pause and state visibility.
### Wall-shadow depth stability — September 23
- [x] Increase the exact wall-overlay depth bias from slope/units −1/−1 to −2/−4. Independently rounded clipped vertices at distant maze coordinates could otherwise compete with the receiving wall and make patches flicker under tiny camera motion.
- [x] Verify 81 perspective camera poses across 0/4/10 km offsets, wall orientations and centimeter camera movement. Keep depth testing and stencil union, and preserve exact geometric edges.
### Carrier reveal panel and audio study — September 22
- [x] Use `Carrier derezed.mp4` at 2:37–2:38 to replace the carrier’s red rectangle with a softly feathered, translucent blue-white field and faint green rim. September 23 refinement: use a yellow pulsing rim, retain 0.5 base gray opacity at the center, and fade the gray fill to zero before it meets the rim. Add a 7 Hz pulse limited to 22% opacity variation, driven by simulation time so pause/reset remain stable.
- [x] Extract 2:38.5–2:40 (the second half of the initial clip) as a stereo WAV and prepare a 120 ms crossfaded loop candidate. Record source hash, timing and filters in `public/audio/carrier-derez-source.json`; add both to the sound library for audition.
- [ ] Audition the mixed-soundtrack excerpt/loop before choosing gameplay playback. No claim of isolated SFX or auditory approval; not yet loaded by the game.
### Solar Sailer transit and carrier arrival — September 22
- [x] Import the user-supplied JIHS Solar Sailer DAE locally as GLB with all seven textures. Preserve proportions at uniform 2.5× scale; use the film clip `carrier and solar sailer.mp4` at 1:34–1:46 for direction, silver sails and amber beam.
- [x] Add a fixed horizontal +X lane parallel to the carrier, at 520 m altitude and 600 m lateral offset. Traverse 12 km at 280 m/s every 180 seconds, starting three minutes into the round; fade at the distant ends. The beam fades in during the three seconds preceding each pass and fades out over nine seconds afterward, remaining invisible between passes. Pause/restart follow simulation time. This is background scenery without combat or collision participation.
- [x] Replace the model’s short source beam with a 24 km amber beam split around the complete vehicle span; the sail end leads travel. Expose scale, altitude, speed and period in development tuning.
- [x] Materialize the carrier at round start using the existing opening-line/rectangle, one wireframe sweep and solid fade, driven by the carrier’s 22 m/s translation (about 57 seconds for the hull to emerge, then the two-second solid fade). Reveal the +X front before the rear through a rectangle fixed in world space; its local position cancels the carrier’s translation. Sweep along the carrier’s long X axis, include existing hull seam lines in the reveal, and fade its shadow with the solids. Repeat on restart without recreating effects. Carrier wireframe ignores ground fog so it stays red at sky distances; its linear color is tuned to (1.5, 0, 0) for a softer red glow rather than the original overbright orange cast; running lights and beacons become active as they cross the reveal plane, independently of the final armor fade.
### Local documentation styling — September 22
- [x] Embed the colvmn stylesheet during `npm run docs` generation so Safari local-file viewing does not require a sibling-folder CSS load. Keep upstream colvmn unchanged; author Markdown/JSON and regenerate HTML with the project wrapper.
### Wall rims and landing tactics — September 22
- [x] Prevent moving aircraft shadows from exposing sawtooth strips on self-shielded maze wall rims. Use the slab face orientation and caster identity; give decorative trim explicit surface normals. Keep foreign-wall occlusion and exposed roof shadows.
- [ ] Allow deliberate Recognizer landings as future tactical choices: block Clu’s path to give an ally a crush opportunity, or land to rest. Ground contact is not inherently a navigation failure; distinguish purposeful blocking/resting from getting stuck. No new landing maneuvers are implemented by this rendering fix.
### Pursuit frame pacing — September 22
- [x] Replace repeatedly sorted ground/air search frontiers with stable priority queues. Spread enemy ground route searches across ticks with a shared 192-node budget and 24-node round-robin slices; keep movement, sensing and collisions running every tick.
- [x] Admit one new tactical candidate set per simulation tick across aircraft and tanks. Preserve existing maneuvers while waiting, defer expensive search-map construction to admitted planning work, and precompute search candidate rankings.
- [x] Reject distant teleporter pads with a conservative radius check before building exact vehicle footprints. Verify exact overlap/containment behavior and lifecycle isolation.
- [x] Record a reproducible eight-unit pursuit profile and validate bounded work, fair route completion, destination replacement and independent round budgets.
### Maze tank pursuit — September 22
- [x] Require a swept exit route when choosing blueprint ground patrol spawns; free floor inside an enclosed blueprint pocket is not a valid spawn. Cache checks per world and hull radius.
- [x] Retain valid ground routes to stable destinations. When the ordinary pursuit search fails, try finer 8 m routing with a bounded larger search; permit wall-safe partial progress toward a recorded contact if the full destination remains unreachable.
- [x] Verify patrol exit routes and actual radio-directed movement in Classic, local and JEV modes without revealing hidden Clu state. Preserve original observation age and physical wall collision.
### Recognizer moving turns — September 22
- [x] Remove heading-alignment stops from Classic, tactical/JEV and carrier escort flight. Preserve forward-only thrust, positional/angular inertia and level attitude while turning in flight.
- [x] Limit approach speed by remaining distance and turn time to prevent orbiting nearby destinations. Carry speed through collinear tactical waypoints; retain braking for arrival, acquisition, deliberate holds and confined descent. Permit a settled attack from an already clear actual pose without waiting for redundant final yaw.
- [x] Check right-angle/reversing turns, multi-location search, wall-side clearance, coordinated stomps and browser momentum/pause/reset behavior.
### Aircraft shadow occlusion — September 22
- [x] Gate Recognizer, carrier and detached-part shadows against the existing static maze light-depth atlas. Preserve shadows on exposed roofs while preventing additional dark silhouettes on obscured wall faces and covered ground.
- [x] Apply the same visibility test to projected Recognizer/debris ground silhouettes and rebind it when reinforcement growth rebuilds the atlases. Verify pixel coverage, materialization and restart resource stability.
### Gravity, aerial zoom and attack coordination — September 22
- [x] Remove vertical damping from production debris; retain horizontal drag, angular damping and contact friction. Regression now measures actual production free-fall velocity/displacement for four seconds without disabling damping in the test.
- [x] Restore scroll-wheel aerial zoom through `CameraRig`; verify both running and paused operation.
- [x] Use one active-maze-width radio radius for enemy observations, neutralization, materialization awareness and tactical ally information. Preserve observation age/delivery delay and vertical-distance checks.
- [x] Reserve one attack lead among nearby aircraft with compatible recorded target knowledge. Retain that lead for a bounded twelve-second approach; supporters use separated 90 m flank positions and cannot initiate a stomp. Reassign on destruction, blocked approach, lost visual contact, injury/cooldown or lease expiration. Coordination uses radio-visible allies, never unseen live Clu coordinates.
### Carrier escorts and debris avoidance — September 22
- [x] Preserve carrier escort duty in Classic, local and JEV modes when no current target/sound investigation interrupts it. Air escorts match a moving carrier slot above roof clearance; ground escorts retain separate perimeter detours, then use a bounded catch-up speed (1.4× ordinary tank speed) to regain formation. Combat movement retains its existing speed cap.
- [x] Publish detached physical debris poses/velocities/bounds to the session. Player perception filters these by 160 m range and wall visibility; JEV receives up to 24 nearest observed pieces, never hidden wreckage.
- [x] Autoplay checks visible falling/crossing/settled debris every tick against a 2.5-second predicted path, including turbo and braking. It can brake or steer using normal inputs and wall clearance, independent of API latency. Manual input overrides remain intact; damage remains real.
- [x] Verify carrier rejoining after blueprint traversal, visibility isolation, turbo prediction, physics observation cleanup, and otherwise-lethal settled/falling debris encounters. Full suite: 232 passed; Chrome autoplay regression passed without provider calls. Prediction is approximate and cannot guarantee escape from unavoidable impacts.
### Refactoring roadmap — September 22
Design review: [Refactoring review and design](refactoring/index.html). The ownership refactor is implemented locally. Keep this checklist as the plan source; the linked review now records the implementation, compatibility boundaries and remaining human checks. No deployment was performed.
- [x] Review current world selection, simulation/control boundaries, application lifecycle, rendering/audio ownership, and JEV transport/accounting. Document composition-based OOP recommendations grounded in the code.
- [x] **0 — Baseline:** explicit blueprint/authored scenarios and seeds, failure captures and boundary contracts; preserve known unresolved third-maze navigation as a tracked defect.
- [x] **1 — World:** explicit scenario/world construction, including derived pads/beams/rosters, shared by browser and tests; support independently constructed worlds without browser globals.
- [x] **2 — Navigation:** extract route execution/cursor/arrival behavior from mission/JEV selection, preserve controller behavior and expose blocked/progress diagnostics.
- [x] **3 — Lifecycle:** compose application, session, loop, input and HUD owners; preserve fixed-step ordering and isolate debris physics from presentation ownership.
- [x] **4 — JEV:** separate typed transport, scheduling/application policy and shared protocol/cost contracts; preserve retry, quota, privacy and lifetime behavior.
- [x] **5 — Presentation:** extract camera coordination, then clarify effect/resource ownership with captures and lifecycle checks; retain current shader ordering.
- [x] **6 — Audio/cleanup:** isolate music and voice responsibilities, remove competing implementations and reconcile documentation; retain explicit default-world adapters for standalone fixtures.
Remaining acceptance work: Safari audio-unlock/listening and side-by-side visual review on the user’s hardware. Automated results are recorded in [Validation](validation/index.html); these checks do not establish artistic fidelity. The seeded replay captures three beams, then holds near 686 seconds; full multi-maze completion remains unresolved.
### Debris gravity and faster panels — September 22
- [x] Use shared Earth gravity (9.81 m/s²) for every detached vehicle part; remove the Recognizer-specific 14.7 m/s² override. Preserve inherited velocity, blast impulse, damping and collisions.
- [x] Divide only panel formation and retraction durations by three: 0.5 s closing, 11 s enclosed hold, approximately 1.167 s opening. Total transfer is approximately 12.667 s. Camera movement rates, blast settings and animation ordering are unchanged.
### Beam panels, timeout retries and request stats — September 22
- [x] Replace transfer shafts with 16 translucent blue polygon panels, bright vertical edges and 0.2 m corner gaps. Preserve sequential closing/opening, height, timing and disabled blast. Match Recognizer contact damage to panel segments.
- [x] Retry JEV timeouts twice (three consecutive attempts total), with 0.5/1 second minimum backoff and existing pacing. Continue local piloting during retries, reset the failure streak on a response, and disengage after exhaustion. Non-timeout errors/budgets still disengage immediately. Pause cancellation is not a timeout.
- [x] Display this round’s total player/enemy request attempts, rolling 10-second requests/sec and USD cost in a bordered box beneath the top-left U / autoplay control. Count retries, preserve totals across pause/autoplay toggles, reset for a new run. Reported billable input usage replaces conservative request estimates; prefix cost with ~ while estimates remain. This round’s display is separate from the persistent hourly relay budget.
### Pad horizon and carrier spotlights — September 22
- [x] Bound pad-border shader anti-aliasing footprint and fade unresolved pads instead of letting grazing floor derivatives expand red borders across the horizon. Keep borders on the floor surface.
- [x] Default `CARRIER.searchlightsEnabled` to false, clearing light tracking/illumination and stopping spotlight-derived radio reports. Recognizer spotlights remain independent.
### Post-transfer damage bubble disabled — September 22
- [x] Default `DATA_BEAM.blastEnabled` to false. Skip post-capture wave damage and hide its expanding sphere/ring. Keep capture, healing, color transition, camera sequence and transfer-shaft contact behavior. Retain the effect behind this flag for later re-enabling.
### Corner arrival stall — September 22
- [x] Reproduce a motionless stall at about 147 seconds after the first blueprint beam: waypoint distance about 0.5 m, next segment occluded, throttle and steer both zero.
- [x] Distinguish intermediate corners from final destinations. Keep steering to 2 cm at intermediate points and creep at up to 2 m/s within 2 m, instead of applying final-stop/steering dead zones. Preserve final beam stopping behavior.
- [x] Extend the actual-blueprint regression through departure and capture of a second beam, rejecting prolonged motionless/no-steering stalls between transfers.
- [ ] A longer exploratory run encountered a separate hold/route-failure near the third maze around 689 seconds. Full multi-maze completion remains unverified.
### Blueprint route correction — September 22
- [x] Reproduce hold at the enemy-free browser-default blueprint spawn. Prior autoplay simulation fixtures used the historic authored layout and missed this failure.
- [x] Search outward from the constrained beam destination using an 8 m grid and larger bounded search/detour allowance, then reverse the swept route for Clu. Keep coarse open-grid routing as fallback. Default enemy route-search settings remain unchanged.
- [x] Add permanent blueprint coverage asserting a collision-clear route from the actual spawn and capture of its local beam. Browser autoplay coverage now begins at the blueprint opening before the separate empty-grid control fixture.
### Post-combat autoplay — September 22
- [x] Distinguish visible contacts from maneuver threats: enemies within 100 m, or within 250 m approaching Clu faster than 2 m/s. Include both visible contacts and threat IDs in player snapshots; no private enemy pursuit state is read.
- [x] Replan immediately when threats appear/disappear, clear route-failure cooldowns on that transition, and reject delayed escape choices once threats are gone. Resume the beam mission after destroying pursuers.
- [x] While red beams remain and no threat is present, a missing route permits maze-entry recovery or hold/retry, never unrestricted open-grid exploration. Verify five destroyed pursuers, nonapproaching distant contacts and temporary route failure.
### Autoplay cornering and turbo — September 21
- [x] Preserve waypoint progress when JEV confirms the current option. Consume visible route waypoints continuously and brake/turn in place for sharp headings; approach occluded corners closely enough to see the next segment instead of stopping at the waypoint acceptance radius.
- [x] Prioritize uncollected beams within 2 km over previously selected distant destinations. Once local beams are blue, travel to the next maze remains intentional.
- [x] Activate turbo through normal simulation input only when aligned, moving, and a clear route segment exceeds the full boosted run plus braking distance. Regulate boosted speed; avoid automatic activation during manual driving overrides. Expose turbo cooldown and duration in player snapshots.
### Background autoplay — September 21
- [x] Keep autoplay running on window blur or hidden-tab transitions. Clear held manual inputs on focus loss. Preserve explicit pause and pause unattended play when autoplay is disabled or JEV fails.
- [x] Use one scheduled loop: animation frames while visible, a 250 ms timer while hidden. Retain fixed 60 Hz simulation steps with bounded one-second catch-up; skip hidden rendering. Cancel both scheduling paths on disposal. Browser/OS suspension can still reduce background progress.
### Autoplay beam navigation — September 21
- [x] Expose all beam colors with distance in meters and hull-relative bearing in radians (positive right), plus the active destination. Remove the 500 m objective cutoff.
- [x] Retain a selected uncollected beam across replans; advance when it becomes blue. Without visible enemies, offer the mission route rather than unrelated short wandering moves. Preserve combat maneuver options and hold during transfer.
- [x] Connect local swept maze routes with long open-grid legs around maze bounds; retain route waypoints across replans and recheck clearance. Retry failed objectives after a bounded delay. Verify two captures in different authored mazes during a 600-second enemy-free simulation. Full combat completion remains unverified.
### Local JEV request pacing — September 21
- [x] Pace local client calls at least 650 ms apart in wall time, including across restarts, to respect the relay’s 600 ms minimum. Distinguish busy, short-interval and hourly-cap warnings; include retry delays. These limits are independent of purchased provider credits. Replace the request-count allowance with a $1 USD rolling-hour local spend budget. Configure server-only `JEV_HOURLY_BUDGET_USD` in `.env.local`; restart Vite after changes. Persist reservations and settled usage in ignored `.local/jev-spend.json`. Public Worker quotas remain separate.
### Beam camera and persistent autoplay — September 21
- [x] Animate a presentation-only aerial rise and L-direction orbit during beam transfer, at half keyboard speed. Rise and descent each take 2.4 seconds; descent starts at 10.1 seconds and reaches Clu at 12.5 seconds as the ring opens. The beam continues its color transition during opening. Freeze with simulation pause, skip automatic motion for reduced-motion users, and allow V/P to cancel. Do not turn the weapon.
- [x] Keep autoplay selected until U or its HUD toggle is used, or JEV becomes unavailable. Service errors/timeouts/rate limits disengage the pilot and clear its plan; no automatic re-enabling after recovery. Retain the warning while off. Held driving/aim controls override their own channels, and Space/mouse fire combines with automatic firing without interrupting navigation. Release returns held channels to the pilot; existing cruise/aim-lock modes retain their normal behavior.
- [x] Verify camera timing/pause/return and persistent manual overrides in simulation and headless Chrome.
### Beam objective, longer hold and teleport audio — September 21
- [x] Make turning every red data beam blue explicit in the player Jev prompt and snapshot. Include remaining/total counts, red/transitioning/blue state, remaining transfer time and the stop-and-wait activation rules. Keep survival and the open-ended simulation; no win screen. Candidate routes remain bounded, so successful completion of all sites is not guaranteed.
- [x] Extend the closed ring hold from 6 to 11 seconds without changing the 1.5-second closing or 3.5-second opening sweeps. Total transfer is now 16 seconds. Visual color progress, healing and release use the same total; only the hold duration is extended.
- [x] Add original synthesized teleport audio: descending phase collapse and rising resonant reassembly, with a short digital snap and stereo echo. Trigger only on completed relocation. Nearby vehicles produce spatial cues at departure/arrival; Clu hears one continuous 0.78-second cue at arrival. Master mute/pause and reset cleanup apply. Source: `src/audio/teleport.js`.
### Autoplay driving continuity — September 21
- [x] Treat temporary route endpoints as handoffs: request a new feasible route before the braking zone, roll through straight intermediate waypoints, and skip already-passed waypoints after delayed decisions. Use a turn/distance speed target instead of an all-or-nothing angular throttle cutoff; retain braking at obstacles and data destinations.
- [x] Steer the turret toward the active visible target or along the driving route when there is no contact. Retain a tracked target across replans unless another is substantially nearer, to reduce camera swings. Keep the ordinary turret motor limits and temporary manual overrides.
- [x] Verify sustained cruising above 19 m/s after acceleration in a clear 20-second route, momentum through collinear waypoints, stopping at a data destination and reorienting a sideways turret. Browser/build validation is recorded in Validation.
### Optional Clu autoplay — September 21
- [x] Add an off-by-default U key and HUD button to let Jev choose Clu maneuvers. Keep the enemy AI mode independent. Movement/turret/fire input temporarily overrides the corresponding pilot controls; pause suspends requests and motion. Label Jev/local fallback on the button.
- [x] Supply Clu's own pose, health, heading/speed, nearby visible enemies, static walls/data destinations, recent positions and bounded feasible maneuver routes. Use a distinct server-owned player prompt. No hidden enemy positions, memories or tactical plans enter its snapshot. Local motor code steers/brakes and only fires through the existing observed target/aim-cone checks.
- [x] Share one serial Jev client, rate/cooldown budget and alternating player/enemy opportunities. Reject stale or pre-teleport replies; local tactics continue during failures. Check moving inputs, information isolation, selection, pause and temporary manual overrides. A live local-relay player request returned an accepted decision; encounter quality and long autonomous runs still need human review.
### In-game service warnings — September 21
- [x] Add a compact top-right warning area below the health/turbo meters, available in production as well as development. Show JEV OFF for deliberately selected Classic/local modes, JEV LIMIT for rate/budget failures, and JEV UNAVAILABLE for service/network failures. Explain the reason and continuing local AI. Keep warnings through pause, resets and retries until a successful response; normal idle/low-confidence decisions are not service failures. Allow other named warning/error notices to share this area, and announce changes politely to assistive technology.
### Public Jev hosting — September 21
- [x] Keep the game/assets on GitHub Pages and add a Cloudflare Worker for the fixed Jev decision contract, with a server-side secret and public build-time API URL. Preserve the local Vite relay.
- [x] Enforce body size, timeout, per-IP pacing and durable global daily request/input-byte limits before calling Jev; bound concurrency and honor Retry-After with local AI fallback. Anonymous access is intentional; CORS is not authentication and shared IPs share limits. See [Jev design](jev/index.html) and README for limits, disabling and deployment.
- [x] Verify quota persistence/rollover, concurrency, invalid requests/answers, secret isolation and client cooldown with Node tests; dry-run the Worker and check its SQLite budget in the actual local Cloudflare runtime.
- [x] Deploy the Worker and server-side secret; verify a real provider decision and the production build in headless Chrome at the public origin. Website publication uses its pinned TRON checkout and public API-base build setting.
### Carrier shadow edge correction — September 21
- [x] Increase the moving carrier's shadow tile from 1024 to 4096 pixels over its 1500 m light-space span (about 0.37 m per texel). Use a compact 6×6 Gaussian-weighted depth-comparison filter at texel centers, retaining receiver-plane correction; never interpolate packed depth values. This supersedes the first four-tap refinement and softens the remaining fine edge serrations. Reduce the carrier-specific depth bias for its long depth range. Keep maze/Recognizer tile sizes unchanged and preserve the floor grid and emissive materials.
- [x] Compare shadow edges against independent ray/box intersections for stationary and translated casters, check the actual carrier on walls/floor, and inspect before/after captures. This remains a finite-resolution filtered dynamic shadow, not exact vector geometry; the larger tile increases GPU memory/render cost. See validation for browser results.
### Optional tactical movement and Jev — September 21
See [Jev enemy control](jev/index.html) for the implemented architecture, input/output examples, scheduling, validation, and current limits.
- [x] Start the browser in Tactical + Jev with the full enemy population (small encounter disabled), and retain Classic/local tactical modes in Shift+T tuning, with an explicit Apply/restart button and persisted preference. Optional comparison roster: two Recognizers and one ground tank. Tactical modes suppress pursuit reinforcements; Classic restores the original roster/controller. Tactical run seed is 1982; page-local maze layout remains stable for comparisons.
- [x] Replace the tactical stomp's circular clearance rejection with conservative oriented aircraft bounds, triangle-level wall checks, and bounded swept tests for translation, rotation and descent. Generate staged climb/turn/travel/align approaches and orientation-aware low-corridor routes. Follow them with the existing inertial forward-thrust/lift/yaw dynamics. Recheck movement and separation corrections against geometry; a blocked maneuver replans rather than driving through walls.
- [x] Give airborne and ground units choices for withdrawal, regrouping, pressure, cover/ambush and attacks. Wounded units prefer retreat; ground units cease firing while withdrawing. Preserve committed movement, but invalidate stale routes and react to injury. Coordinated choices see nearby allies' intentions within radio range; no hidden live Clu state enters planning or requests.
- [x] Use Jev only for units with a fresh recorded Clu position within one maze length (horizontal distance); distant or unaware units use local tactics. Never gate using hidden live Clu coordinates. Send structured self state, dated target memory, nearby allies, local static wall geometry and candidate pose sequences to Jev. Include precomputed travel distance, visibility relative to the last known target and supporting-unit counts. Jev chooses only a supplied candidate ID; local collision/controller code remains authoritative. Inspect recent requests, probabilities and real elapsed latency in the tuning panel.
- [x] Connect the ignored server-side credential file or environment key through a local Vite dev/preview endpoint. Bound request size/rate/hourly total and timeout; label fallback explicitly. Abort/discard stale answers on pause/reset/mode changes and reject invalid IDs or low-confidence choices. Protect credential files from Vite static serving and Git.
- [x] Check provider browser access: CORS preflights from local origins are rejected, so direct browser calls are not viable in the tested setup. Keep the same-machine relay; initial real end-to-end calls measured 317 ms and 292 ms. This was initially a local-only service; the public Cloudflare relay above now supports static production clients.
- [x] Replan on sight loss/reacquisition, newly received or expired contact, an observed velocity change of at least 8 m/s, or target displacement of at least 45 m. These events bypass the normal commitment gate with a 0.65-second event cooldown. Preserve momentum and committed attack phases; reject Jev answers based on superseded observations/revisions. Healthy units actively follow the bounded last-known path, then inspect nearby branches instead of favoring passive ambush. Search hypotheses never become sightings or authorize blind attacks. Ground route refresh follows a new tactical plan.
- [ ] Human comparison of encounter quality, retreat behavior and longer Jev sessions. The planner is bounded and conservative, not a globally optimal flight/attack planner. A geometry-valid maneuver is not a guaranteed successful strike on a moving target; some narrow spaces remain inaccessible. Choosing better actions than the local policy remains an experimental question.
September 21 hearing: aircraft and ground enemies receive bounded, expiring acoustic observations for engines, cannon fire, impacts and explosions. Use distance attenuation, wall muffling and deterministic bearing/range error; never copy a sound into confirmed sighting memory. Unknown sounds permit local investigation and nearby Jev requests with `target: null`; friendly sounds are context only. Hearing is independent of player audio volume and pauses with simulation. Range controls are available in development tuning. See [Jev enemy control](jev/index.html#hearing-input) for the report format and limits.
September 21 connected-search follow-up: persist whether the last-known location has been checked, so moving into a branch cannot re-enable a return-to-origin maneuver. Build a bounded graph of tank-passable corridors from the predicted contact point (8 m steps, 160 m path budget, swept wall checks); use its reachable locations instead of nearby open cells across arbitrary walls. Retain completed checks for the search episode, advance both ground and air units on arrival, and share `search` progress plus each candidate's `searchPath` with Jev. Aircraft already above the roof search from above instead of descending at every checkpoint; feasible low search routes remain available to settled low aircraft. Regression checks cover an L-shaped route, disconnected pockets, continuous aircraft search and ground progression.
September 21 opening-pursuit correction: above-roof aircraft proceed toward the travel waypoint while turning/climbing, rather than returning to the route origin after inertial drift. Healthy units receive a pursuit/support maneuver and no longer prefer regrouping simply because allies are spread out. Reserve attack spacing only for a nearby strike or active stomp; retain local swept collision checks and knowledge-limited target prediction. A moving-opening regression requires all five pursuers to close distance and exceed Clu’s normal speed.
Design references: the [Tetris example](https://github.com/Yasserbhb/Agent-JEV-Tetris) measures reachable action outcomes and avoids conflicting directional hints; the [Doom example](https://github.com/lukaske/jev-doom-agent) uses structured observations, selected maneuvers and local motor control. This implementation is independently written; no external game code or assets were copied. API shape follows the [TypeSafe quick start](https://docs.typesafe.ai/introduction/quickstart).
September 21 grid alignment: maze-site origins now snap to the 24 m world grid, with seeded rotations restricted to 0°, 90°, 180° or 270°. The initial maze stays at the origin and unrotated. Rendering, collision, patrol locations, data beams and teleport pads derive from the same site transforms. Preserve the authored diagonal wall ends and blueprint shapes within each site.
### Spatial teleport pads — September 21 (supersedes the timed transfer)
- [x] Preserve four grid-aligned 48 m square borders outside each maze and their links to other sites. Extend each invisible portal volume from the floor to four maze-wall heights; aircraft above that volume do not transfer.
- [x] Clip vehicle solids and their shadows inside the volume, drawing glowing edge wireframe only on those portions. Partial entry/exit is spatial, independent of time. Clu, ground tanks, folding aircraft, turrets, projected floor shadows and aircraft depth-atlas shadows share the same clipping planes. No sweeping rectangle or fade is used for teleportation; reinforcement materialization remains unchanged.
- [x] On full conservative hull/turret/vertical containment, transfer instantly with the same local pad offset, altitude, heading, linear/angular motion, turbo and player controls. Continue driving normally while entering/leaving. Block an occupied destination without freezing the vehicle; process transfers atomically to avoid overlapping arrivals. Require complete 3D exit before rearming. Pulse the source/destination borders briefly; snap camera/interpolation history at transfer.
- [x] Replan arriving enemies and synchronize nearby recorded awareness immediately, without inventing live Clu coordinates. Vehicles remain subject to ordinary movement, visibility and weapon/collision rules during the spatial effect.
- [x] Verify partial/complete entry, turret and ceiling bounds, altitude exit, offset/momentum, occupied/simultaneous arrivals and awareness in simulation. Drive through in Chrome; inspect half-wireframe entry, full-wireframe arrival and solid exit, with paused simulation and no browser errors. Preserve separate reinforcement animation checks.
### Wireframe rezzing and pursuit reinforcements — September 20
September 21 awareness correction: on completing reinforcement materialization or an enemy teleport arrival, synchronize with active airborne and ground allies within the existing 304.8 m three-dimensional radio radius. Copy the freshest recorded sighting and alert state, retain observation timestamps, and share confirmed target destruction. Exclude unconfirmed spotlight acquisitions. Fresh pursuit knowledge suppresses searching spotlights; arriving units still need their own visual observation to set `canSee`.
- [x] Reveal a vehicle with one front-to-back wireframe sweep. The rectangle starts as a horizontal line and expands vertically for 0.6 seconds, then travels for 1.8 seconds from 6 m ahead of the nose to 6 m beyond the tail. After the wireframe sweep, fade the whole solid vehicle in over 0.35 seconds while the rectangle and temporary edge overlay fade out. Activate the vehicle immediately when that fade finishes (2.75 seconds total). This supersedes the earlier two-pass design at the user's request. Animation follows simulation time, including pause. The supplied Homeworld Hyperspace clip is a motion reference, not a shipped game asset.
- [x] Use one shared continuous-pursuit clock for airborne Recognizers. Every twenty seconds in Classic mode, create exactly one reinforcement near one of the pursuers, regardless of pursuer count. Continue repeating while pursuit persists; reset the clock when no Recognizer is actively spotting/pursuing Clu or Clu is destroyed. Choose a clear nearby air position above the maze and copy only the summoning observer's recorded sighting.
- [x] Hold the incoming vehicle stationary, silent and intangible during materialization. After completion it independently senses, navigates, reports and attacks. Give new aircraft IDs separate from ground tanks; extend visual, spotlight and audio pools and reuse them after reset.
- [x] Hide shadows during the wireframe sweep, then fade ground shadows and dynamic atlas attenuation with the solid vehicle. Rebuild shadow receiver bindings without stacking shader hooks. Keep the dynamic aircraft atlas bounded to the twelve nearest visible aircraft; other aircraft retain projected floor shadows. There is no reinforcement-count cap; very long uninterrupted pursuits still increase simulation, geometry and audio work.
September 21 interval follow-up: the shared reinforcement period is now 20 seconds (previously 15). Tactical modes continue to suppress reinforcements. The browser now defaults to Tactical + Jev with the full enemy population; earlier experimental preferences are superseded once, and subsequent explicit selections persist.
- [x] Fix phantom square/diamond shadows after reinforcement shadow-atlas rebuilds. Evict the receiver material's cached GPU program bindings when attaching replacement shadow uniforms, even when the generated shader key stays the same. Otherwise Three can retain uniforms pointing at a disposed atlas. This preserves the actual vehicle silhouettes on roofs and ground; the materialization rectangle remains excluded from casting.
Tuning lives in `src/game/materialization.js` and `src/simulation/reinforcements.js`. The reusable materialization presenter is first connected to incoming Recognizers; starting vehicles still appear normally. The user endorsed carrier launches, data-beam arrivals and reverse-sweep recalls as further uses; those remain future work.
September 20 moving-target interception: add the observed target velocity along the approach direction to the Recognizer's distance-based arrival speed. Previously, an aircraft approaching from behind settled into speed-matched pursuit short of a valid landing prediction. It now continues closing until folding/fall time and residual braking drift permit a committed stomp. Navigation still uses only its own sighting memory; the committed drop does not track subsequent evasive movement.
September 20 tuning: increase Recognizer cruise speed by 10%, from 27 to 29.7 m/s. The existing pursuit multiplier requests 34.155 m/s, compared with Clu's normal 22 m/s and turbo 55 m/s. Raise forward thrust authority from 22 to 24.2 m/s² so drag does not cap flight below the new pursuit speed. Turning, lift, drag and braking settings remain unchanged; carrier formation speed retains its carrier-relative setting. Verification: `npm test` passes all 132 checks; a 60-second fixed-step straight-flight calculation reaches 34.155 m/s. Browser driving feel has not been rechecked for this tuning change.
The user's latest direction supersedes the structured chase roadmap below. The purpose is the feeling of being in the 1982 maze: free exploration and independently simulated Recognizers, without an assigned objective or tuned encounter sequence. [Goal](../Goal.md) and `../AGENTS.md` record the active requirements.
### Maze and shared geometry
- [x] Replace the one-dimensional route with connected branches, loops, dead ends, open courts and four exterior openings.
- [x] Correct the initial square layout into long oblique slabs with diagonal clipped tips. Use the supplied overview as a visual reference; distinguish reconstruction from unseen/inferred layout.
- [x] Share convex prism geometry among rendering, hull collision, camera obstruction, projectile blocking and three-dimensional sight tests.
- [x] Preserve a fixed seed/layout for repeatable comparison. Allow travel onto the surrounding grid and turning in place in narrow passages.
### Autonomous Recognizers
- [x] Spawn five persistent aircraft outside the maze, with independent patrol destinations, headings and motion.
- [x] Limit vision by range, field of view and actual wall occlusion.
- [x] Maintain timestamped local sightings and share immutable observations through delayed, range-limited radio messages. Relays retain the original timestamp/source.
- [x] On losing sight, investigate a bounded projection of the last observed velocity, then search plausible nearby passages. Expire stale knowledge and resume wandering.
- [x] Keep navigation separate from live-player perception so unseen Clu motion cannot steer an aircraft.
- [x] Halve the old model scale to put upright legs at roughly the user's 30-foot estimate. Match physical hit volumes, roof clearance and aircraft spacing.
### Presence and controls
- [x] Remove mission progress, capture zones, waves, victory and defeat. Keep exploration running until paused or reset.
- [x] Keep a level, independently controlled turret and vertical-only assistance for aligned, unobscured targets.
- [x] Render and spatialize all five aircraft. Muffle their sound when walls obstruct the listener.
- [x] Hide instruments by default; add optional H instruments, Tab survey and V aerial view. Keep pause, mute, reduced motion and lower quality.
- [x] Import the user-supplied arabinowitz tank and Shriker1 Recognizer GLBs, preserving proportions, turret yaw, muzzle alignment and reduced aircraft scale. See [Models](models/index.html).
- [x] Add a fixed reference studio for side-by-side film comparisons; iterate on tank trim, gold shoulders, highlights, shadows, floor visibility and oblique maze views.
- [x] Apply the renamed film references and new wall-detail study: thin seams, sparse panels and shallow angled ledges, with a fifth reference-studio view.
- [x] Trace the supplied blueprint and use it as the default game maze, retaining the original at /?maze=authored. Preserve concave notches in roofs, collision and sight; provide a top-down overlay and editable outline coordinates.
### Verification and fidelity review
- [x] Unit checks cover connected branching topology, diagonal-tip openings, hull sweep/sliding, occlusion, sighting memory, radio delay/expiry, independent hidden-player counterfactuals, weapon behavior, resets and fixed-step consistency.
- [x] Complete browser controls, multi-agent lifecycle, production preview and sustained traversal checks for this revision; record their actual outcomes in [Validation](validation/index.html).
- [ ] User review of diagonal forms, scale, patrol presence and sound. Exact film topology and final visual approval remain unresolved.
Run `npm run dev` and visit http://127.0.0.1:5173. Supporting documents: [Assets](assets/index.html) · [References](references/index.html) · [Validation](validation/index.html).
### Superseded turn-speed experiment — September 23
Historical experiment, now superseded by restoring original motion above: maximum yaw rate is 120°/s (one revolution per three seconds at full speed), with 240°/s² acceleration/braking and the normal smooth heading response. All flight uses the same limit, including close attacks. Close-attack speed reduces when a turn needs a tighter radius, avoiding repeated orbits. A turn from rest includes acceleration and settling time, so a backwards-facing attacker can exceed the four-second stomp target. Eight-heading tests verify bounded yaw rate/acceleration and successful stomps; aligned attacks retain the four-second check.
### Helicopter-style Recognizer translation — September 23
Recognizers can thrust sideways and backward independently of yaw, in local/JEV navigation, Classic navigation and carrier escort flight. The acceleration vector shares the original 24.2 m/s² budget across all directions (no diagonal bonus); drag, braking, speed caps and angular inertia remain unchanged. Tight-corridor planning includes lateral/reverse translations with swept hull clearance. A wall-aligned hull can slide into position without first turning broadside. Existing pursuit-to-stomp opportunity checks and support assignments remain active.
### ENCOM terminal signature — September 23
- [x] Add a local SVG ENCOM mark based on the supplied MCP terminal reference, with pale lavender glow at the lower right of the shared terminal overlay. Opening, detached/credits, victory and victory credits share the mark, and it fades with the opening transition. Typed text is unaffected.
### Lead and intercept approaching Clu — September 23
- [x] Choose a reachable crossing ahead of observed motion, accounting for approach, normal acceleration/braking, folding and fall time. Bounded prediction uses recorded position/velocity and stops at mapped obstructions.
- [x] Hold the chosen crossing while sightings follow the predicted path, instead of continually moving it farther ahead. Changed velocity, lost sight, injury or an expired interception still permits replanning. JEV candidate facts include approach time and predicted impact time.
- [x] Keep the lead's attack plan when only supporting positions change. Supporting units still refresh their own routes.
- [x] Preserve a useful wall-aligned facing during overhead translation. Start folding while Clu approaches; commitment still requires fresh personal sight and a physically clear descent, and a committed drop cannot track an evasive maneuver.
- All existing speeds, accelerations, fold/drop timing and health rules remain unchanged.
## Stored shots and extended credits — September 24
- [x] Add a three-segment stored-shot meter beneath health. Keep CLU’s normal cannon cooldown at 1.14 seconds. Restore one stored shot per ten seconds without firing, up to three; firing resets the next-shot timer but preserves banked shots.
- [x] Follow the normal credits with `credits_extended.txt`, in bounded terminal pages, before the final END OF LINE. Preserve normal credits pacing; print the longer acknowledgments faster.
- [x] Archive all four arena signature/date inscriptions in a separate GLB instead of loading their geometry with the arena.
## Carrier and maze ground-shadow overlap — September 24
- [x] Stop cutting moving ground shadows with the coarse maze visibility atlas where the floor already receives exact geometric maze shadows. This removes mismatched sawtooth boundaries at overlaps; ground layers multiply smoothly. Keep atlas occlusion for maze walls and vehicle receivers.
## Optional outer mazes — September 24
- [x] Default browser gameplay to the central labyrinth only. Restore all four smaller mazes with `?outerMazes=1`; authored/reference scenarios keep their existing layouts.
- [x] Filter disabled placements before constructing walls, collision data, open cells, patrols, beams, pads, geometry and shadow maps. Build the blueprint wall source lazily only when enabled; retain lightweight layout metadata for stable placement and opening coordinates.
- [x] Preserve the central labyrinth’s position and retain the original outer placement/spacing algorithm. Reindex active sites consistently for navigation. With one maze, connect each of its four teleport pads to the opposite pad.
## Tank-to-cycle progression and deferred scenery — September 24
- [x] Completing the central labyrinth beam transfers the player to the middle gold cycle, with a three-second countdown. The arena race remains inactive beforehand. A/D or left/right queue 90-degree turns; forward motion is automatic. V offers an aerial view, Escape pauses.
- [x] A destroyed player cycle waits for the team result. A gold-team win opens the victory terminal; losses/draws consume one of three match chances and reset the match after six seconds. Exhausting all three chances opens the existing loss terminal. Tank combat/JEV planning stop during the cycle stage.
- [x] Defer the arena/cycle module and assets until tank play begins. Wait safely with a loading message if the beam finishes before they are ready. Load the solar sailer after 30 simulation seconds, before its first pass at 180 seconds. Dispose late arrivals if the app has closed.
- [x] Move the initial tank position south of the central labyrinth, preserving the previous south-edge approach distance (about 1,338 m), with opening Recognizers translated by the same offset.
## Cycle testing, turbo and audio — September 24
- [x] Temporarily start new browser games in the cycle arena. `CYCLE_TESTING.startInArena` in `src/game/light-cycles.js` owns this switch; `?cycleStart=0` restores the maze-first game, `?cycleStart=1` explicitly selects the test start. Entry waits safely for deferred arena assets, skips the opening pursuit/camera fly-in, and starts a three-chance player match without marking the beam collected.
- [x] Hold **I** for player-cycle turbo, including partial charge. Releasing I immediately preserves remaining charge and resumes recharging. Capacity is 10 seconds, refill 60 seconds, speed 2.5×; no automatic boost or full-charge prerequisite. Holding an empty tank stays at normal speed until released, avoiding repeated boost/recharge pulses. Each retry resets charge. T remains the tank-only turbo key.
- [x] Per-cycle movement progress and chronological cell crossings keep boosted movement, trails, camera and collision checks aligned. Other cycles retain their normal speed. The top-right meter shows I / TURBO in the arena.
- [x] Extract six short film-mix sound candidates (startup, launch, drive loop, turn, crash, trail deactivation) and connect them to spatial cycle playback. Reproduce with `python3 scripts/extract-cycle-sfx.py`; timestamps, source hash, filters and attribution are in `public/audio/cycle-source.json`. These are picture-selected extracts, not isolated stems; auditory review remains pending.
- [x] Arena entry stops music and clears queued transitions. Tank gameplay music cannot requeue during the match; terminal credits music remains available. Cycle audio stops on pause, death, reset and disposal, and resumes engines without replaying launch events.
## Cycle arena patrol and sample corrections — September 24
- [x] Keep the live arena Recognizer on its perimeter flight route during cycle matches, including countdowns and retries. The cycle-stage simulation previously suspended this guard along with the tank encounter. Clear its obsolete tank attack/spotlight state, unfold gradually and retain normal patrol flight constraints; destroyed guards stay destroyed. Other tank enemies remain paused. Restore the guard's spatial flight sound during cycle play.
- [x] Replace the driving-loop source with the user-selected 0:47–0:48 video interval (0.1-second seam crossfade, 0.9-second finished loop) and the turn cue with 0:51–0:52 (one second, short edge fades). Version the two URLs to bypass stale cached WAVs. Other sound selections remain as before.
- [x] Extend the cycle explosion to 2.0 seconds (75.85–77.85), preserving its tail. Add a separate player cabin loop from 1:36–1:38 (1.9 seconds after seam crossfade), retaining 0:47–0:48 for other cycles.
- [x] Preserve stereo source channels through paired HRTF emitters 0.6 m apart. Drive, launch and turn emitters follow their cycle; explosions/trail collapse stay at the crash position. Apply smoothly changing Doppler from source/listener radial velocity to other cycles, keeping the player's cabin pitch stable apart from turbo. Camera motion supplies listener velocity.
## Loading diagnostics and cycle spectator flow — September 24
- [x] Print plain-text `[TRON loading]` phase timings and complete copyable reports. `tronLoadingReport()` returns the current report in Safari's console, including browser, viewport, navigation-to-app delay, maze setup, enemy placement, physics wait, model fetch/decode/adaptation, rendering-world creation, first render submission, opening readiness and deferred arena/player entry. Parallel model durations are labeled non-additive; first render submission is not a GPU completion measurement.
- [x] With the temporary cycle-testing start enabled, begin loading the arena and launch its countdown automatically, without Return. Normal `?cycleStart=0` maze play still uses the opening terminal. Browser audio gesture restrictions may still require a click/key before sound can play.
- [x] On player-cycle destruction, enter a movable spectator camera while the team match continues: WASD, Q/E, drag/arrows, Shift, Home and Space pause. Restore the follow camera and clear spectator inputs at the next automatic round. Victory or exhausted chances exits spectator mode so end screens are not blocked.
- Collision timing and trail-ending changes were withdrawn after the user's correction; behavior remains unchanged.
## Cycle camera stability — September 24
- [x] Translate the follow camera and its look target with the exact visible cycle pose; smooth only the relative orbit and height. Easing absolute camera positions independently of fixed-step cycle movement caused vertical screen bob, amplified by turbo. Preserve eased turns and aerial changes, and clear the tracking anchor on reset.
- [x] Add smoothed J/L left/right and I/K up/down spectator camera controls. Post-death WASD/QE movement uses 36 m/s with eased acceleration/deceleration; ordinary free inspection retains its existing speed.
- Safari's supplied loading report isolates 19.442 seconds in initial simulation/enemy placement out of 20.849 seconds to opening readiness. Model loading is comparatively small; startup placement optimization remains outstanding.
## Startup search acceleration and cycle glances — September 24
- [x] Accelerate ground-route obstruction tests by stopping at the first wall hit when only a clear/blocked answer is needed. Preserve nearest-hit queries for projectiles and other callers. Reject triangle bounds conservatively, including the extended corners of padded acute triangles; skip stale A* entries instead of expanding them repeatedly.
- [x] Hold J/L in cycle follow view for a fast, smooth 120-degree left/right rear-quarter glance. Release to return forward; both held cancel. The camera turns in place without steering the bike. Post-death spectator J/L remains free-camera steering.
- [x] Shorten full player-cycle turbo capacity to five seconds; retain 2.5× speed, partial-charge use on I and 60-second recharge.
- Local WebKit initial placement fell from the earlier 1,820 ms to 529 ms; opening readiness from 3,202 to 2,246 ms. Reload measured 506 ms placement / 2,146 ms opening. These are local Playwright WebKit measurements, not a new run of the user's actual Safari (previously 19,442 ms placement). Actual Safari confirmation remains pending.
## Cycle motion and level-horizon glance refinement — September 24
- [x] Run cycle matches at a 1/120-second fixed timestep; retain 1/60 second in tank mode. Catch-up budgets cover the same elapsed duration and render alpha follows the active step size.
- [x] Interpolate live cycle poses and trail endpoints between simulation steps, with the follow camera using the same presentation pose. Preserve right-angle paths through corners and leave collision state untouched. Destroyed cycles use their collision pose immediately.
- [x] Make J/L glances faster (28/s response), rotating around world-up to keep the horizon level. Release returns smoothly to forward view. No camera banking.
- [x] Keep tank combat suspended during cycle matches and avoid starting the solar-sailer background load there. Retain the arena Recognizer patrol.
- Actual Safari 26.6.2 user confirmation: initial placement **2,995 ms** (previously 19,442); opening ready **4,463 ms** (previously 20,849); arena ready **5,048 ms** (previously 21,398), same reported 1262×1424 viewport / DPR 2 at startup.
## Cycle pedals and repeatable materialization launch — September 24
- [x] W is cycle turbo; S is a half-speed pedal. Both approach their targets with 8/s exponential response (about 0.38 seconds to 95%), including return to cruise and boost depletion. S takes priority when both are held, without spending turbo. Preserve five-second turbo capacity, 2.5× maximum speed and partial charge use. Engine pitch follows actual speed; tank controls are unchanged.
- [x] Temporarily disable cycle win/loss endings via `CYCLE_TESTING.endingsEnabled=false`. Player death permits free-camera spectating; Return starts a fresh match immediately. Completed team matches wait for Return, with no attempt limit. The original ending rules remain available through the session setting.
- [x] Replace the three-second countdown with a 0.3-second hidden-to-wireframe-to-solid materialization using the carrier's translucent scan panel/yellow border treatment, then launch immediately. Each bike owns reveal materials/uniforms; reset repeats the effect and disposal releases depth resources.
- [x] Extract the materialization beat at 26.7–27.4 seconds from the supplied TRON light cycles video. Play one team cue during the reveal, with its sound tail continuing into launch; avoid stacking six identical copies. This is a picture-selected film-mix extract, not an isolated stem, and listening review remains pending. Keep the camera steady.
## Breakable cycle arena enclosure — September 24
- [x] A cycle striking intact arena enclosure dies and leaves a jagged, full-depth opening. Subsequent cycles can pass through beside the fallen cycle's persistent trail; trail collisions retain their separate timing.
- [x] Subtract the opening from actual inner-wall, symbol and exterior-shell mesh triangles at runtime, preserving material groups and regenerating outlines. Add exposed fracture surfaces through the wall thickness. The original GLB stays unchanged; each new match restores its original geometry.
- [x] Share the opening profile and dimensions between collision and presentation. Keep intact wall portions solid, store trail occupancy beyond the arena grid, and retain collisions with maze geometry outside. Escape continues into the outer world for now; no transition or JEV control is added.
- [x] Perform mesh surgery only for new breaches, not every frame. Restore/dispose replacement geometry and exposed surfaces on restart/disposal.
## Motorcycle handling after arena escape — September 25
- [x] Once a cycle fully clears the enclosure, stop extending its trail and switch to continuous road handling outside. Arena reentry restores racing behavior as of September 26. Retain the existing trail and its collision/death lifetime. Preserve exit speed and displayed position; remove queued arena turns.
- [x] Outside: W throttle, S brake (priority over throttle), A/D or arrows steer. Releasing throttle coasts with rolling/aerodynamic drag. Acceleration tapers with speed; braking can stop the bike without reversing. Speed-dependent steering limits lateral acceleration and the bike leans into turns, easing upright afterward. The follow camera remains level.
- [x] Share interpolated road poses with the model and camera; engine pitch/Doppler use actual speed and heading. Replace the turbo meter with a km/h speed display outside. Retain swept wall, maze, trail and live-cycle body checks. Road riding continues when the arena match reaches a result; Return after death restores the original arena match.
- Tunables live in `ROAD_CYCLE`: 18 m/s² nominal drive acceleration, 26 m/s² brake deceleration, 14 m/s² lateral acceleration limit, with drag and steering/lean response. These are an initial superbike-inspired approximation, awaiting driving-feel review.
## Breach interior finish — September 25
- [x] Apply the same arena styling adapter to exposed fracture surfaces: near-black wall faces and blue-gray edge outlines, including edges along the passage through the wall. Dispose both edge and face materials on rebuild/reset.
- Crack shapes currently use one fixed authored jagged profile translated to each impact, not random generation. Collision clearance and the mesh cut share that profile.
- [x] End the arena grid at the inner wall boundary; use a continuous dark teal floor through the wall thickness, visible in breach passages. Keep the existing outer-world ground beyond the enclosure.
- [x] I/K also operate cycle turbo/slow alongside W/S, with slow taking priority. Outside the arena they also mirror throttle/brake. Spectator I/K camera controls remain available.
- [x] Hide the sweeping rectangle and its border for cycle materialization only. Preserve the fast wireframe/solid reveal and the carrier materialization plate.
## Film-inspired arena crack — September 25
- [x] Replace the short opening with a tall, kinked central split and five tapered splinter branches radiating from the impact, based on the supplied film close-up. Retain the driveable base, dark passage and colored edge outlines. The authored pattern remains deterministic.
- [x] Cut branches through the wall and remove buried lining faces where they intersect. Reject disjoint/coplanar polygons before subdivision to avoid duplicate geometry at shared profile boundaries.
- [x] Remove spurious coplanar outline seams after fracture cuts: split collinear outline fragments at shared endpoints and cancel paired overlaps, preserving true silhouette/crease edges. This runs when geometry changes, not per frame.
- [x] Temporary escape-testing setup: `CYCLE_TESTING.startWithBreach` opens the far wall straight ahead of the player’s starting lane in each player match, including Return restarts. Set it to false to restore intact starts.
## Cycle escape testing and progressive steering — September 25
- [x] Temporarily disable the middle opposing cycle in player matches via `CYCLE_TESTING.hideMiddleOpponent`, including its occupied starting cell; retain stable cycle IDs and restore other cycles normally on restart.
- [x] Reuse the cycle shadow atlas for both the procedural arena floor and outer-world ground, preserving materialization clipping and cycle self-shadows.
- [x] Road steering builds continuously with held A/D input. Full input takes approximately 0.6 × (1 + speed / 35 m/s) seconds; release eases upright. Apply this normalized demand to the speed-dependent cornering limit, avoiding immediate saturation of lean at high speed.
- [x] Retain W/I turbo outside the arena with the same charge/recharge timings; it doubles drive acceleration and raises the speed ceiling from 100 to 140 m/s. S/K braking overrides turbo. Keep the charge bar, with speed also shown in its label; release coasts and recharges.
## Outside-arena cycle impact damage — September 25
- [x] Replace unconditional road collision death with normal-impact-speed response. Direct impact at 18 m/s (64.8 km/h) or accumulated health depletion destroys the cycle. Below 2 m/s normal speed, bumps do no damage; intermediate damage increases quadratically. These are gameplay tunables, not a real-world safety model.
- [x] Nonfatal contacts retain 80% of tangential speed and bounce with 0.15 normal restitution; very small residual motion stops. Keep the bike at its last safe position and deflect its heading before the next swept step. Arena racing collisions retain their previous lethal behavior.
- [x] Show remaining cycle health outside the arena. Initialize full health on escape/new match.
## Low-speed cycle steering — September 25
- [x] Blend low-speed kinematic steering (yaw rate = speed × tan(steering angle) / wheelbase) into the existing progressive cornering controller between 3 and 12 m/s. Low-speed input reaches full steering in 0.18 s; faster riding retains speed-dependent hold buildup. No pivoting at rest.
- [x] Derive lean from actual lateral acceleration: atan(speed × yaw rate / gravity). This allows small-radius slow turns with modest banking, rather than imposing a fixed lean per key press. A simplified steady-turn approximation; it does not simulate rider countersteering, tire deformation or balance.
- Research: [Georgia Tech / GDOT, curve analysis, pp. 38–40](https://rosap.ntl.bts.gov/view/dot/88895/dot_88895_DS1.pdf) relates speed, radius and equilibrium lean. [MSF Motorcycle Operator Manual](https://msf-usa.org/wp-content/uploads/2023/02/motorcycle-operator-manual.pdf) distinguishes slow tight-turn rider counterbalancing. Input-response/blend thresholds are game tuning choices, not values taken from these sources.
## Escape simulation handoff — September 25
- [x] On player escape, pause arena competitors and match progression, stop their engine loops, and continue the player’s road handling. Existing destruction effects finish and expired trails release their collision cells.
- [x] Resume carrier sensors, Recognizers, ground tanks, reinforcements, projectiles and hearing against the current cycle position/speed. Outside enemy damage feeds cycle health and the cycle explosion/spectator flow; no tank controller or beam/teleport objectives run. Allow enemy JEV requests and deferred solar-sailer loading again.
- [x] Return restart clears the handoff and pauses outside combat while the next arena match runs.
## Road reverse, orbit glances and wall clearance — September 25
- [x] S/K brakes forward travel to zero, then accelerates slowly in reverse up to 3 m/s; release stops reverse motion. W/I brakes reverse motion before driving forward. Small unpowered residual speeds settle to zero, with no stationary steering pivot.
- [x] J/L glances orbit around the cycle, keeping it in frame and the horizon level. Outside the arena the glance/return response is 4/s versus 28/s inside.
- [x] Road cycle maze collision sweeps the authored perimeter segments with the cycle radius instead of expanding triangulation half-planes, avoiding oversized acute-corner wedges. Contacts permit movement away from a slightly overlapping wall boundary.
## Looking into road turns — September 25
- [x] Outside the arena, bias camera position and look-ahead toward the intended bend as steering builds (up to 36 degrees), smoothing with a 4/s response. Ease back on release, fade anticipation at walking pace/rest, and retain a level horizon with the cycle visible. Deliberate J/L glances take priority; aerial view does not add turn anticipation.
## Road driving control revision — September 25
- [x] Outside only: W/S taps change held target speed by 5 m/s (18 km/h); key repeat does not add steps. X held brakes and clears the target to zero. A fresh X press at rest latches 3 m/s reverse. W/S leave reverse (W sets 5 m/s forward, S stops). I remains held turbo. Arena controls remain unchanged.
- [x] Smoothly approach target speed rather than applying full turbo whenever accelerating. Normal acceleration 8 m/s², turbo multiplier 1.6, normal/turbo ceilings 55/80 m/s. Roll-off drag increased; turbo release returns gradually toward the selected speed. These supersede the earlier road acceleration/maximum settings.
- [x] At road speed derive turning rate from developing lean, so the visible bike and path respond together. Preserve quick low-speed steering and progressive hold input.
- [x] V now uses Clu’s aerial height, trailing offset, transition blend and wheel zoom for cycles, replacing the separate 40 m overhead view.
## Patrol recovery after player destruction — September 25
- [x] Confirmed player destruction clears committed tactical attack/search routes, attack assignments, threat holds and hearing investigations. Recognizers finish any required stomp recovery and resume their normal patrol or escort behavior. Preserve visual confirmation and delayed, range-limited destruction reports.
- [x] Return from cycle spectating clears old destruction reports and allows enemies to detect the next player again.
- Validation: `node --test tests/tactical.test.js tests/cycle-player.test.js tests/simulation.test.js` — 83/83 passed. Covers stale tactical choices, road-cycle destruction/radio propagation, continued patrol simulation during spectating, and restart awareness. Node simulation checks only; no new browser or visual review.
## Direct road-cycle testing start — September 25
- [x] Temporary automatic cycle entry starts stationary, facing away from the arena, 20 m beyond its far outer wall. Road handling is active immediately; arena competitors stay paused. Return after death repeats this position with full health and turbo.
- [x] Gate this shortcut through `CYCLE_TESTING.startOutsideArena`; set it false to restore the arena testing start. `?cycleStart=0` still preserves normal maze-first entry.
- Validation: inline Node assertions using `GameSession` checked outside road mode, zero initial speed, maze-wall clearance, forward acceleration and repeatable Return restart. Browser driving-feel review remains pending.
## Cycle tire contact with the floor — September 25
- [x] Replace the fixed 8 cm cycle elevation with the actual supporting floor height and the repaired model's measured bottom. The arena floor is at +2 cm; outside ground is at −6 cm. Select by rendered position within the arena floor footprint so returning through a breach also restores the correct height.
- Validation: `node /tmp/tron-cycle-grounding.mjs` in headless Chrome on local macOS verified model bottoms at +0.02/−0.06/+0.02 m for inside/outside/inside positions, checked the running game's outside player height, and rendered an inspected low-angle capture at `/tmp/tron-cycle-grounding.png`. Lean dynamics are unchanged.
## Return to arena racing — September 26
- [x] Once the whole road cycle clears the inner wall, restore cardinal turns, normal arena speed, W/I turbo, S/K slow, lethal arena collision rules and trail emission. Clear road lean/steering/reverse state and queued turns. Join the nearest cardinal lane over the first short span without teleporting the bike; sweep that span before reserving its collision cells.
- [x] Start a separate trail section at reentry; preserve older trails without drawing a wall across the outside drive. Resume paused arena competitors and suspend the outside combat handoff. Repeated exits/reentries remain supported; reentry after a completed round reopens racing with a fresh round timer.
- Validation: `node --test tests/cycle-road.test.js tests/cycle-player.test.js tests/light-cycles.test.js tests/cycle-camera.test.js` — 46/46 passed. `node tests/cycle-reentry.mjs` — headless Chrome on local macOS, 1280×800, passed actual breach traversal, restored keyboard controls and trail-render checks with no page errors. Inspected `test-results/cycle-reentry.png`. Manual driving-feel review remains pending.
## Steadier player-cycle drone — September 26
- [x] Remove the repeated opening swell from the player cabin sample by narrowing the existing 1:36–1:38 source selection to 1:36.7–1:37.8. Apply a 0.2-second equal-power seam overlap and circular, stereo-linked 100 ms RMS leveling; the finished loop is 0.9 seconds. Match the previous integrated loudness (RMS approximately 0.118) and version its URL to refresh cached audio. Preserve speed-related pitch and the separate external-cycle loop.
- [x] Keep extraction reproducible through `python3 scripts/extract-cycle-sfx.py`; record processing and source/use constraints in `public/audio/cycle-source.json` and the sound catalog.
- Validation: `node tests/cycle-audio.mjs` passed in headless Chrome on local macOS. Decoded cabin-loop level spread is 0.507 dB across 100 ms windows; stereo loading, event counts, pause/resume and reset cleanup passed with no sample/page errors. This measures playback and level stability; listening approval remains pending.
## Remove recorded pitch wobble — September 26
**Rejected in listening review:** the user found the synthesized tone terrible and irritating. The September 26 rollback below supersedes this experiment.
- [x] The volume-only correction above did not satisfy listening review: the recording still changed pitch at constant speed. Replace the cabin recording loop with stationary harmonic resynthesis from its averaged stereo spectrum, implemented in `scripts/cycle_drone.py`. Use fixed 367.5 Hz harmonics and exact 120-sample waveform periods, retaining source spectral balance, stereo differences and RMS 0.118. This is a synthesized approximation of the film timbre, not an isolated original engine stem.
- [x] Loop whole waveform periods with no crossfade, and update the asset cache version. Speed-related playback pitch remains; player Doppler remains disabled.
- Validation: `node tests/cycle-audio.mjs` passed in headless Chrome on local macOS. Every decoded waveform period matches exactly; 120 playback updates at constant player speed keep pitch at 1× despite listener motion. Level spread is 0.018 dB; loading, event counts and pause/reset cleanup pass. Listening review of the revised timbre remains pending.
## Restore film cycle sound — September 26
- [x] Remove harmonic resynthesis and restore the preceding film-derived cabin loop, retaining its seam crossfade and volume smoothing. Refresh the audio URL cache version. Frequency steadiness remains unresolved; do not treat objective periodicity as proof of acceptable sound.
## Road tire traces and cycle dynamics window — September 26
- [x] Outside the arena, leave a faint 13 cm rear-tire trace that fades over eight seconds of simulation time. Sample the rendered contact path every 45 cm into a bounded 8,192-segment pool. Marks follow the supporting floor, stop during arena racing/death, do not bridge teleports, clear on restart, and never affect collision. Pause freezes their age; disposal releases geometry/material resources.
- [x] In development builds, Tab while controlling a cycle opens the Cycle dynamics window; Escape or Close returns to play. Pause while editing, preserve an existing pause, retain normal Tab navigation within the window, and isolate all game keyboard/mouse controls while it is open. Tank Tab retains the survey map.
- [x] Expose road speed/acceleration/braking/drag, steering buildup and release, wheelbase, low-speed blending, cornering and lean limits, reverse, and contact response with labeled units, sliders and numeric fields. Keep Earth gravity fixed. Validate positive denominators and related thresholds before changing settings. Speed hold compensates drag; drag adjustments are most apparent under turbo.
- [x] Own road settings per GameSession, retain them over restarts, and save valid changes locally in this browser. Provide Reset defaults and Copy settings. Arena dynamics remain separate. Defaults moved to `src/game/cycle-road.js`, with the existing simulation-module export retained for compatibility.
- Validation: `npm test` — 337/337 passed; `npm run build` passed (existing large-chunk advisory). `node tests/cycle-tuning.mjs` — headless Chrome on local macOS, 1440×1000, passed tire-trace rendering, dialog opening/closing, input isolation, pause restoration, settings changes, reload persistence and defaults reset. Inspected `test-results/cycle-tuning.png` and the close driving-camera `test-results/cycle-tire-traces.png`. Driver preference for trace intensity and tuned handling remains subject to play review.
## Tire trace attachment and transparency — September 26
- [x] Replace the guessed 1.05 m rear-wheel offset with the repaired model's measured rear contact (approximately 1.23 m behind the origin). Transform that contact with the displayed bike pose, after applying its yaw and lean. Keep a live final segment attached between the 45 cm history samples; this removes the changing gap at the tire.
- [x] Reduce peak opacity from 24% to 6%. Add derivative-based edge coverage with an expanded transparent envelope to reduce subpixel shimmer, and strengthen floor depth bias with a 15 mm surface offset. The live segment ages while stopped instead of refreshing forever; its buffers dispose with the history.
- Validation: `node --test tests/cycle-tuning.test.js tests/cycle-camera.test.js` — 11/11 passed, including live endpoint placement between samples and fade at rest. `node tests/cycle-tuning.mjs` passed in headless Chrome on local macOS at 1440×1000; inspected the updated close driving-camera capture `test-results/cycle-tire-traces.png`. User confirmation of flicker on their display remains pending.
## Road-bike performance and shorter enemy radio — September 26
- [x] Profile live road riding versus post-death spectating. Browser CPU samples identify enemy route planning and wall-intersection queries as the dominant live-bike work. The whole outside enemy world had inherited the arena's 120 Hz step; the bike's isolated motion/collision cost was very small.
- [x] Keep arena simulation at 120 Hz; restore the outside world's normal 60 Hz cadence. Road movement and swept collision retain their bounded 120 Hz internal substeps and shared render interpolation. Reentry restores the arena cadence.
- [x] Wall queries copy only endpoint coordinates, rather than whole unit/search-node objects. Compute the world-space query bounds once, and stop on the first wall hit for boolean ground-clearance and line-of-sight checks. Nearest-hit projectile queries retain their behavior.
- [x] At the user's additional request, halve enemy-to-enemy communication range from one maze width to half a maze width, including vertical separation. Use the shared radio-range configuration for aircraft, tanks, tactical allies, carrier reports and arrival synchronization; retain report delays and observation timestamps.
- Validation: `npm test` — 339/339 passed. `node tests/cycle-performance.mjs` profiles headless Chrome on local macOS at 1280×800 with layout seed 1982. Approximately five-second live-bike samples: simulation CPU **1,572 ms before → 177.5 ms after** the combined changes; median step **2.6 → 0.4 ms**, p95 **4.5 → 0.7 ms**. Post-death CPU **639 → 216 ms**. Both baseline and updated runs maintained roughly 60 fps locally; this measures CPU headroom, not an established user-device framerate gain. Final measurements ran without the test suite competing for CPU. JSON and CPU profiles are in `test-results/cycle-performance.json` and `test-results/cycle-{alive,dead}.cpuprofile`. User-browser confirmation remains pending.
## Stronger road brakes and arena pedal isolation — September 26
- [x] Double default road braking from 18 to 36 m/s² and downward held-speed response from 2 to 4 /s. Keep acceleration response separate. Expose braking response in Tab tuning. Upgrade previously saved default braking without replacing custom handling settings.
- [x] Route W/S/X directly through cycle controls. Inside the arena, S/X/K request the existing half-speed minimum and override turbo; road braking and reverse commands are empty. Update the control hint. Ordinary arena wall/trail collisions remain lethal.
- Validation: `npm test` — 342/342 passed, including twice-strength braking at low/high speeds, saved-default migration, and S/X arena survival. `node tests/cycle-reentry.mjs` passed in headless Chrome on local macOS at 1280×800: breach reentry, cardinal turn, turbo, S/X slowing and survival, and trail rendering. `npm run build` passed (existing large-chunk advisory). The originally reported S/X destruction was not reproduced; its specific cause remains unconfirmed.
## Closer aerial zoom — September 26
- [x] Halve V-mode's minimum zoom scale from 0.25 to 0.125, bringing the closest camera position twice as close to the vehicle. The shared wheel control applies to both tank and cycle aerial views.
- Validation: `node --test tests/cycle-camera.test.js` passed. In-browser framing at the new closest distance has not been reviewed.
## Local sighting response — September 26
- [x] Reduce shared enemy radio radius from half to one quarter of the active maze width (about 334 m in the default blueprint world). This halves the previous range and reduces its horizontal coverage area to one quarter.
- [x] Broadcast only firsthand sightings. Receiving a radio report still starts pursuit, but does not forward that report to another neighborhood. A recipient that independently spots Clu may broadcast its own sighting. Preserve report delays, timestamps and destruction notifications.
- Chase size remains emergent: multiple direct observers and tightly grouped units can still respond together. No fixed pursuer cap is imposed. User play review of encounter density remains pending.
- Validation: `npm test` — 343/343 passed, including 3D radio boundaries and a three-unit chain that stops at the firsthand observer's neighbors while allowing a new direct sighting to recruit locally. Updated the tank-radio fixture to place its receiver within the reduced range. `npm run build` passed with the existing large-chunk advisory. No browser encounter-density playtest performed.
## Gentler low-speed cycle steering — September 26
- [x] Reduce the road bike's low-speed steering limit from 0.65 to 0.45 radians. At full steering below the blend threshold, this widens the nominal turn radius from approximately 2.8 m to 4.3 m (about 36% less yaw rate at the same speed). Preserve input buildup and the high-speed steering branch; blend smoothly between 3 and 12 m/s.
- [x] Upgrade the previous saved steering default so existing browser tuning receives the change; retain other customized values. The limit remains adjustable in Tab's Cycle dynamics window.
- Validation: `node --test tests/cycle-road.test.js tests/cycle-tuning.test.js` passed, including the revised low-speed radius and continuity at both blend boundaries. Driving feel awaits user play review.
## Home game selection — September 27
- [x] Show Space Paranoids and Light Cycles on the home terminal using FilmTerminal, with a `>` beside the selected choice. Arrow keys select and Return starts; native buttons support mouse, touch, focus and keyboard activation. Update the displayed controls for the selected game.
- [x] Default to Space Paranoids and wait at the menu. Space Paranoids starts the normal Clu maze/pursuit. Light Cycles enters the arena as gold player 1, with two gold teammates, all three blue opponents, and no initial wall breach. Preserve this setup when restarting a match.
- [x] Keep the explicit `?cycleStart=1` road-testing shortcut separate from menu entry. Update road browser fixtures to request it explicitly. Ending terminals hide the menu.
- Validation: `npm test` — 344/344 passed. `node tests/home-menu.mjs` passed in headless Chrome on local macOS at 1280×800 with touch enabled: home waits, arrow selection, Return, mouse click, touch tap, normal maze start, six-bike intact arena and match restart. Inspected `test-results/home-menu.png` with the terminal font and selection marker. Touch verification uses browser emulation, not physical mobile hardware. `npm run build` passed with the existing large-chunk advisory.
## Plain ENCOM home terminal — September 27
- [x] Replace the home access request with `ENCOM SYSTEM`, `USER ACCESS GRANTED`, and `SELECT PROGRAM`. Remove controls, navigation help, objective copy and project-notes link from the home screen.
- [x] Match both choices to the terminal text's font size, line height and letter spacing. Remove boxes, padding and extra row spacing; retain the `>` selection indicator and keyboard/click/tap behavior.
- [x] Show the Clu access request only after selecting Space Paranoids. Keep it visible briefly even with reduced motion, then enter the game without a camera transition in that mode. Returning home restores the ENCOM greeting.
- Validation: `node tests/home-menu.mjs` passed in headless Chrome on local macOS, including matching computed typography, no button outline/border, removed help, the post-selection access message, keyboard/mouse/emulated-touch activation and both game starts. Inspected the updated 1280×800 home capture. `npm run build` passed with the existing large-chunk advisory.
## Uppercase terminal typography — September 27
- [x] Apply uppercase text presentation to the shared terminal content, including home choices, loading/access messages, victory and credits. Keep source strings and typing cadence unchanged.
- Validation: shared terminal selector reviewed; `git diff --check` passed.
## Program selection cursor — September 27
- [x] Replace the `>` marker with the terminal's blinking block cursor beside the selected program. Arrow-key selection moves it between entries. Hide the heading cursor on the home screen; retain access and ending cursors. Honor reduced-motion preference with a steady selection cursor.
- Validation: `node tests/home-menu.mjs` passed in headless Chrome on local macOS (reduced-motion mode), covering arrow selection, keyboard/mouse/emulated-touch activation and both game starts. `git diff --check` passed.
## Typed Clu access and shared game font — September 27
- [x] Space Paranoids prints its access message one glyph at a time with variable 45–145 ms keystroke spacing, extra space pauses and a longer carriage return. Retain the previous screen's top margin and opaque background. Hold the complete message for two seconds, then enter the Clu game. Inputs do not skip this sequence; simulation, enemy decisions and gameplay audio remain stopped during it.
- [x] Use FilmTerminal for all game HUD text and controls, including paused screens, while retaining existing text sizes. Keep Light Cycles' direct start and the victory printer's uniform cadence.
- Validation: `npm test` passed 344/344 before adding the new printer-specific check; `node --test tests/victory.test.js` then passed 3/3, including human timing, line-break pause and reset. `node tests/home-menu.mjs` passed in headless Chrome on local macOS: equal terminal top position, partial then complete text, at least two-second hold, frozen simulation time and computed FilmTerminal HUD font, plus keyboard/mouse/emulated-touch entry and intact six-bike arena restart.
- `npm run build` passed with the existing large-chunk advisory; `git diff --check` passed.
## Explicit uppercase program titles — September 27
- [x] Correct the mixed-case menu titles shown in Safari: store `SPACE PARANOIDS` and `LIGHT CYCLES` directly in the markup and explicitly apply uppercase styling to the buttons rather than relying on inherited terminal styling.
- Validation: confirmed both uppercase strings in `index.html`; `git diff --check` passed.
## Access cursor and controls visibility — September 27
- [x] Hide the program menu with `display:none` during access typing, preventing its selected cursor's explicit visibility from leaking through. Retain the single text cursor beside the access message.
- [x] Keep the HUD hidden until the typed access screen and two-second hold finish. Only running gameplay reveals the control hint. This also prevents explicitly visible HUD children from appearing over the terminal.
- [x] Apply FilmTerminal and uppercase directly throughout game/pause controls and both tuning panels, including native form controls.
- Controls validation: `node tests/home-menu.mjs` passed in headless Chrome on local macOS, including exactly one visible cursor during typing, hidden HUD/control elements until gameplay, and computed uppercase FilmTerminal control labels. `npm run build` and `git diff --check` passed.
## Clu cannon elevation limit — September 27
- [x] Cap manual and mouse elevation at 45 degrees above horizontal. Exclude assisted intercepts requiring more than 45 degrees, and clamp assisted-shot spread to the same ceiling. Targets within the allowed elevation retain normal lead assistance.
- Elevation validation: `npm test` passed 346/346; targeted gunner/mouse checks and production build passed. Regression coverage verifies manual 45-degree saturation, no overhead assisted lock, and retained assistance below the limit.
## Teleporter pads disabled — September 27
- [x] Disable pad creation in live simulation and presentation through `TELEPORTERS.enabled=false`. There are no active transfer volumes or rendered floor borders. Keep layout generation, transfer behavior and rendering implementation for possible future use.
- [x] Skip floor shader injection for an empty pad layout. Existing isolated teleport tests explicitly supply pads so the retained implementation remains covered.
- Teleporter removal validation: `npm test` passed 348/348, including pad-free normal runs and no floor shader modification for an empty layout. Retained teleport behavior passes with explicit test pads.
## Credits dismissal — September 27
- [x] Any fresh key press, mouse click or touch-generated click on victory or post-death credits returns to program selection. Consume the dismissal event so Return or a click cannot also start a game. Reset credit printing, music, input, simulation and ending state through the same path used by the automatic credits exit.
- Credits validation: headless Chrome menu checks passed for keyboard, click and emulated-touch dismissal, returning to the greeting without starting a game.
## Restore tank-camera opening — September 27
- [x] After typed access and its two-second hold, restore the original 1.1-second terminal dissolve and 5.5-second camera approach to the tank. Keep controls hidden until the approach completes. Reduced-motion mode retains the direct transition after the hold.
## Big-maze music footprint — September 27
- [x] Use each maze instance's own basis and floor dimensions when detecting exploration. The previous shared small-maze footprint excluded most of the larger central labyrinth. Preserve pursuit priority and the existing beam music progression.
- Validation: `node --test tests/music-selection.test.js` passed 7/7, including all four big-maze edge entries with outer mazes enabled and disabled, plus rejection just outside its boundary.
- Music browser validation: `node tests/maze-music.mjs` passed in headless Chrome at the large labyrinth edge, then through exploration, hidden approach, visible beam, entry, afterglow and pursuit playback transitions.
## Readable, incremental extended credits — September 27
- [x] Preserve the existing left margin and set the credit column's right margin to match, including the mobile breakpoint. Wrap long text within that column.
- [x] Append immutable glyphs and retain completed line nodes instead of replacing the entire credit text every frame. Reuse the victory printer's incremental rendering and line isolation. Clear nodes only when changing entries.
- [x] Display one authored credit at a time. Use `credits_display.txt` for 27 concise entries, all five lines or fewer (below the requested eight-line limit). Keep URLs and the full dependency inventory in the unchanged archival `credits_extended.txt`; omit them on screen. Retain a two-line Cloudflare credit.
- Validation: `node --test tests/victory.test.js` passed 5/5, covering entry boundaries, URL filtering, a brief Cloudflare mention, and line/length limits. `node tests/extended-credits.mjs` passed in headless Chrome on local macOS: complete credits progression, immutable retained glyphs with no removed nodes while typing, desktop/mobile equal margins and no horizontal overflow. The longest entry rendered within eight lines. Early/late mean append work measured about 0.018/0.017 ms per frame; this does not measure total GPU painting or establish performance on the user's device. Inspected `test-results/extended-credits-layout.png`.
- Final browser verification: `node tests/home-menu.mjs` passed with the restored terminal fade and changing camera coordinates before gameplay controls appear, plus keyboard/click/tap credits dismissal and pad-free game state. `npm run build` passed with the existing large-chunk advisory; `git diff --check` passed.
- Final simulation verification: `npm test` passed 351/351 when rerun without concurrent browser/build work. The earlier concurrent run failed one ground-tank patrol-distance assertion; no unrelated simulation code was changed for that retry.
## ENCOM loading copy — September 27
- [x] Replace `LOADING` with `ENCOM REMOTE ACCESS`, `DIALING HOST`, and `AWAITING CARRIER`. This is a text-only change; retain the existing terminal presentation and loading behavior.
- Validation: reviewed loading markup; `git diff --check` passed.
## Cycle lineups at their walls — September 27
- [x] Start gold and blue lineups at the nearest safe grid row to their respective arena walls, facing inward. Derive the row from the inner wall position, bike length and a named minimum clearance rather than a fixed interior position. Apply on initial entry and round restarts; preserve the explicit outside-road test spawn.
- Validation: `node --test tests/cycle-player.test.js tests/light-cycles.test.js tests/cycle-road.test.js` passed, covering round resets, arena movement/collisions and road transitions. Updated the collision fixture to assert displacement relative to its starting row.
## Faster Clu access typing — September 27
- [x] Halve Clu access keystroke delays, including space and line-break pauses, while retaining their natural variation. Preserve the two-second completed-text hold and subsequent camera opening.
- Validation: `node --test tests/victory.test.js` passed 5/5, including the revised typing delays and unchanged victory printing.
## Confirm Clu entry and start music with the opening — September 27
- [x] Replace the automatic two-second access-screen countdown with an indefinite wait after typing. A fresh key press, click or tap starts the original terminal dissolve/camera approach; input during typing is consumed. Keep simulation frozen while waiting and prevent the confirming input from firing the cannon.
- [x] Start music directly in that confirming user gesture and enable audio during the camera approach, rather than waiting until the approach ends. Do not restart music when the camera arrives. Preload the opening track during access typing, restoring it after terminal/credits music, and avoid reassigning an already-loaded initial track URL.
- Clu-entry validation: `node tests/home-menu.mjs` passed in headless Chrome, including waiting beyond the old countdown with frozen simulation, key/tap/click confirmation, music playback during the camera approach and no forced restart at camera arrival. Production build and whitespace checks passed.
## Clu aerial keyboard zoom — September 27
- [x] While Clu uses V-mode aerial view, hold I to zoom in and K to zoom out continuously. Share the wheel's 0.125–4 scale limits through named zoom configuration. Preserve other modes and cycle pedal controls. Switch the displayed I/K hint from aim to zoom in Clu aerial view.
- Validation: `node tests/home-menu.mjs` passed in headless Chrome, including I zooming in, K zooming out, updated aerial hint, and unchanged zoom when holding I outside V mode. `npm run build` and `git diff --check` passed.
## J + L turret recenter shortcut — September 27
- [x] Pressing J while holding L, or L while holding J, invokes the same center, level and lock action as F in the Clu game. Register both held keys so their steering inputs cancel during centering. Preserve cycle glance and free-camera controls. Update the control hint.
- Keep the chord latched until both keys are released, so staggered releases do not cancel centering; clearing input also clears the latch. Browser verification passed in both key orders. Targeted gunner/refactoring tests passed, including staggered release followed by normal manual aiming. Production build passed before the final input-clear refactor, followed by targeted tests and `git diff --check`.
## Beam completion returns to Clu victory — September 27
- [x] Completing the central labyrinth beam now wins the Clu game rather than entering Light Cycles. Normal layouts without a central labyrinth retain the all-beams win condition. Arena asset readiness cannot delay the victory.
- [x] Restrict cycle entry to explicit requests from the Light Cycles menu/testing path; preserve its gold-team lineup and independent restarts. Update cycle fixtures to enter explicitly.
- Validation: `npm test` passed 353/353, including exactly one victory event after central-beam completion, no arena handoff/wait, and continued explicit cycle entry.
## Charged cannon reserve and Recognizer behavior — September 27
- [x] Start Clu with all three stored shots available. Recharge each spent slot in five seconds (previously ten), with continuous HUD fill; firing still resets the next slot's timer and pause freezes recharge. Chrome `node tests/cannon-reserve.mjs` verifies full startup, real key consumption, progressive fill, paused fill, and all three natural recharges.
- [x] Show Recognizer searchlights only during search/investigation without a confirmed visible enemy. Patrol, pursuit and visible contact suppress even existing projector phases immediately; observation confirmation and information-limited aiming remain intact.
- [x] Limit sideways/backward desired flight speed to half forward speed using a hull-relative elliptical envelope. Preserve momentum, bounded acceleration, and normal turning toward travel; expose both speed ratios in development tuning. Intercept travel estimates account for directional speed. Wider crossings can escape slower lateral correction; near-head-on interception remains covered.
- [x] Beam-to-victory follow-up: Chrome `node tests/home-menu.mjs` and production build passed for direct victory without entering the cycle match.
- [x] Validation: `npm test` passes all 355 tests, including directional flight speed, near-head-on interception, searchlight suppression and reserve timing; `npm run build` passes (existing chunk-size warning). Recognizer flight feel still needs human play review.
## Continuous chase-camera zoom — September 27
- [x] Holding I/K zooms Clu's normal chase camera in/out continuously as well as the V aerial camera. Chase zoom scales distance and height around the tank anchor from 0.5× to 4×, retaining wall clipping and smoothing. Each camera retains its own zoom across mode switches; resetting the view restores chase zoom.
- [x] Preserve first-person gunner I/K elevation and cycle driving inputs; suppress chase zoom while paused or using free camera. Update the HUD hint for chase mode.
- [x] Validation: 27 camera/gunner tests pass; Chrome `node tests/home-menu.mjs` verifies held I/K in both chase and aerial views, independent zoom settings, and preserved gunner elevation. `npm run build` passes with the existing chunk-size warning.
## Installable Space Paranoids PWA — September 27
- [x] Add a relative manifest scoped to the deployed game path, standalone launch, dark startup colors, and project-authored T icons (192/512 PNG, maskable, 180-pixel Apple touch icon, editable SVG). Keep the existing desktop keyboard/mouse gameplay.
- [x] Generate a content-versioned production service worker and complete local game asset list during build. Cache 88 files / 48.8 MiB, including models and music, for offline starts after installation completes. Support audio byte ranges, exclude remote APIs and documentation, and scope cache cleanup to this game. Development does not register a worker.
- [x] Wait for old game windows to close before activating a new release; never force-refresh a running game.
- [x] Validation: production build passes. `node tests/pwa.mjs` passes in headless Chrome with a temporary persistent profile: no installability errors, correct subdirectory scope and PNG dimensions, offline Clu and light-cycle launches, ranged cached audio, and a waiting update while gameplay continues. Physical home-screen installation on iOS/Android remains untested.
## Faster victory terminal — September 27
- [x] Triple victory typing from 45 to 135 characters per second. Allow several characters per rendered frame so the speed is attainable at 60 Hz, with bounded catch-up after frame stalls. Clu access typing and credits retain their separate timing.
- [x] Validation: `node --test tests/victory.test.js` passes all five tests, including two seconds of 60 Hz typing and a simulated two-second frame stall.
## Matched terminal speed and model attribution — September 27
- [x] Supersede the 135-character/second victory setting: win text now shares the extended credits' 18 ms per character timing (about 56 characters/second).
- [x] Label each imported model artist's displayed credit explicitly as 3D MODEL or 3D MODELS. Remove adaptation/behavior wording from these pages so the credit clearly identifies the supplied artwork. Preserve full provenance in the archival credits.
- [x] Validation: all five `tests/victory.test.js` checks pass, including shared typing speed and the eight-line credit limit.
## Top-centered terminal text — September 28
- [x] Center the first line of the home, loading and Clu access text horizontally, with a shared top margin of 12vh bounded to 64–140px. Keep subsequent lines and the program menu left-aligned under the first line; fixed character-width offsets prevent the typing animation from moving the text block. Existing victory and credit layouts retain their margins.
- [x] Validation: Chrome `node tests/home-menu.mjs` passes. Inspected the 1280×800 menu capture: the first line is centered and the remaining text stays left-aligned below it.
## BIT program integration and terminal correction — September 28
- [x] Restore TRON home/loading/access text to its previous left-offset position and top margin. The top-centered request was intended for the separate Bit application; retain that layout only in the integrated Bit program.
- [x] Copy the Bit project into `public/bit`, preserving its MIT license, legacy renderer, models, animation and sounds. Add BIT as the third program, with keyboard, mouse and tap selection. Escape unloads Bit and returns to the home menu.
- [x] Remove obsolete copied code variants, the standalone server/TLS files, key-entry UI, duplicate font/logo assets and separate service worker. Reuse the host assets and PWA cache. Extend the shared budgeted Jev protocol with bounded yes/no/unsure questions; retain server-side credentials and quotas. The public worker requires its corresponding update when deploying.
- [x] Use the existing ENCOM logo for all PWA icon sizes, with dark square backing and mask-safe padding; generate directly from the shared SVG source.
- [x] Validation: all 357 simulation tests pass; Chrome `tests/bit.mjs` verifies keyboard/mouse/tap selection, rendered Bit, mocked yes/no/unsure responses and repeated Escape returns. `tests/home-menu.mjs` passes with restored terminal positioning. Production build and Worker dry-run pass; `tests/pwa.mjs` validates installability, icons, offline Bit entry/Escape, offline tank/cycle starts, audio ranges and waiting updates. Speech was mocked; live microphone/provider responses were not exercised.
## Bit activation and microphone permission — September 28
- [x] Add a blinking block cursor to the activation prompt. Query microphone permission silently on entry: granted permission launches Bit immediately; prompt/denied/unsupported permission queries show activation text and wait for a gesture. Keep the prompt hidden while checking to avoid a flash for returning users.
- [x] Activation requests microphone permission before constructing Bit; release the temporary permission-check stream before speech recognition starts. Denial keeps Bit unlaunched with a clear retry instruction. Escape remains available throughout.
- [x] Validation: Chrome `tests/bit.mjs` passes with mocked granted/prompt/denied/unsupported permission cases, deferred permission resolution, cursor visibility, track release, activation and Escape. Production build passes. No live microphone or paid provider calls were used.
## Center Bit's changing heading — September 28
- [x] Measure the current Bit instruction text instead of centering a fixed 12-character label. Recenter after heading changes, font loading and window resizing; keep question/answer lines at the same left edge.
- [x] Validation: Chrome `tests/bit.mjs` passes, including centering within one pixel at 1280px and 390px widths for activation, running and permission-denied headings, with aligned following lines.
## Bit exit hint — September 28
- [x] Display ESCAPE KEY TO EXIT using the same responsive font size, line height, spacing and glow as the top instructions. Fade both together over 0.5 seconds when the first accepted spoken question hides the instructions.
- [x] Validation: Chrome `tests/bit.mjs` passes, checking exact hint wording, equal computed font sizes and both elements fading to zero after simulated speech.
## Bit hint and answer timing — September 28
- [x] Hide the Escape hint until Bit launches, then fade it with the top instructions on the first accepted spoken question.
- [x] Hold YES, NO, unsure and unavailable response text for two seconds, then fade over 0.5 seconds. Cancel the previous fade when a new question starts so an old timer cannot hide THINKING or a newer response; clear the timer on exit.
- [x] Validation: production build and Chrome `tests/bit.mjs` pass. The browser check verifies hidden-before-start/visible-after-start hint behavior, each answer still opaque after 1.6 seconds and subsequently fading to zero, and the hint/instruction fade on simulated speech.
## Light Cycles arena opening — September 28
- [x] Cover the canvas with a black loading terminal while the arena loads, preventing the Clu tank scene from appearing when Light Cycles is selected.
- [x] Add a 4.5-second smooth camera descent from 600 meters above and 680 meters behind the arena to the player's cycle view. Hold the race for the first 65% of the move, then begin the countdown/race as the camera approaches the cycle inside the arena. Suppress driving/view inputs and HUD during the move; Escape pauses and resumes the camera.
- [x] Reduced-motion preference skips the descent. Explicit road-test entry keeps its existing immediate camera behavior.
- [x] Validation: 28 cycle camera/player tests pass; Chrome `tests/cycle-opening.mjs` verifies loading coverage, hidden tank mesh, descending camera, held race time, pause/resume, race handoff and reduced-motion bypass. Inspected the arena approach capture. Production build passes.
## Film-reference cycle entrance and sound — September 28
- [x] Replace the earlier overhead zoom with a shallow approach, a bank over the rear arena rim, and a low grid arrival based on the supplied `Enter Cycle Arena.m4v`. Travel takes 13 seconds; the complete entrance lasts 20 seconds to accommodate the dialogue/transport/startup cues and a smooth handoff. Wall crossing stays above the 50 m rim; the race releases at 92% of the entrance.
- [x] Extract “Prepare to transport to light cycle grid” and “We have transport” from the 5.1 center channel, plus stereo transport and cycle startup effects. Named cue times, local preloading, master mute, pause/resume offsets, reset/disposal, and suppression of duplicate materialization/launch cues keep playback tied to the entrance. Reduced-motion mode skips the cinematic and its timed audio.
- [x] Validation: 30 camera/player/audio lifecycle unit checks pass, including wall clearance, exact camera handoff, and mid-cue pause/resume. Chrome entrance checks and rendered approach/bank/grid captures pass. Dialogue boundaries were located using local transcription; effects were selected against the film frames, not isolated production stems. Subjective sound balance remains for user audition.
## Cycle entrance reference correction — September 28
- [x] Re-read the supplied film clip frame by frame and replace forward flight with a backward, maze-facing descent over the west wall. Keep camera viewing direction independent of travel: reveal the broad ledge, drop past the decorated inner wall with a restrained bank, then turn across the grid and ease into the player's view. The existing arena placement already presents this wall to the labyrinth; no world rotation is required. Preserve the 20-second entrance, audio cues, pause/resume and reduced-motion bypass.
- [x] Give the arena's horizontal crest surfaces the reference's muted blue-violet (`#343548`) while preserving the dark vertical faces and symbols. Apply in the presentation material so existing breach cuts and restores retain the treatment.
- Validation: on local macOS, 33 camera/player/audio/breach unit checks pass. Headless Chrome `node tests/cycle-opening.mjs` passes for loading coverage, descent, frozen race, pause/resume including mid-dialogue, audio loading, race handoff and reduced-motion bypass. Inspected rim, bank and grid captures against extracted film frames. The reconstruction preserves the existing authored maze and arena geometry; subjective motion and sound review remains with the user.
- Production build passes with the existing chunk-size advisory; `git diff --check` passes.
## Recreate only the first sixteen seconds — September 28
- [x] Supersede the previous entrance with a timed 00:00–00:16 camera study: retreat from the maze, descend along the wall while panning right and pitching down to 65 degrees, then accelerate across the floor and lift the view at the far staging area. Separate timed position, yaw and pitch tracks; remove aircraft roll and the invented blend toward the player during the film shot. Preserve the blue-violet wall crest.
- [x] Hold the race throughout the sixteen-second shot. At its end, cut back to the existing gameplay camera; later film shots and player/cycle formation are outside this pass. Existing audio cues remain tied to elapsed time; the later startup cue lies outside this shorter shot.
- Validation: 30 camera/player/audio unit checks pass, including wall clearance, steep downward pitch, rightward pan, accelerated traverse and the sixteen-second duration. Browser and rendered review results follow below.
- Headless Chrome `node tests/cycle-opening.mjs` passes on local macOS, including pause/resume, held countdown, audio loading and reduced-motion bypass. Inspected 3/7/10.5/14-second captures; the 10.5-second view is dominated by the downward-facing grid and the arrival faces the opposite wall. Production build passes with the existing chunk-size advisory; whitespace checks pass. Film-motion fidelity still needs the user's review.
## Cycle launch timing and continuous entrance — September 28
- [x] Fix accelerated race startup: the fixed-step loop accumulated the entire cinematic hold and replayed it after release. Discard accumulated time when simulation is held, including holds triggered within a step. Preserve the normal 38.4 m/s cycle speed and turbo tuning.
- [x] Remove the centered arena-loading wording; retain a plain black cover until assets are ready. Hide cycle control hints throughout the cinematic.
- [x] Keep the sixteen-second reference study, rightward/downward pan and fast floor traverse, but spread forward travel through the turn instead of slowing almost to a stop before accelerating. Regression checks require continuous motion through the turn as well as faster travel across the grid.
- Validation: 41 camera/player/audio/loop checks pass, including a sixteen-second hold followed by one second of real-time simulation and a hold triggered inside a simulation step. Browser and production results follow below.
- Headless Chrome `node tests/cycle-opening.mjs` passes on local macOS, including normal simulation time versus wall-clock time after launch, hidden entrance hints, loading text removal, pause/resume and reduced-motion bypass. Inspected updated turn and downward-grid captures. Production build passes with the existing chunk-size advisory; `git diff --check` passes. Subjective motion matching remains for user review.
## Wall clearance, overhead patrol and Recognizer face tints — September 28
- [x] Correct the entrance's assumed wall height from 50 to the imported model's actual 60 meters. Raise the crossing, retain clearance over the wall's full thickness, and descend only after entering the arena. Check the swept camera path against the source GLB triangles.
- [x] Move the arena patrol's initial position to the west ledge beneath the cinematic crossing, facing along the perimeter. Preserve autonomous patrol after the cinematic hold; the camera passes above its crown with clearance.
- [x] Add a per-Recognizer `tintColor` override and `createRecognizer(template, {tintColor})` presentation parameter. Default and arena-patrol colors have separate development-panel color pickers with black, green, blue and red presets. Arena patrol uses muted green; other units retain the original dark default. Only solid face materials change; trim and damage flashes remain independent, as do cloned vehicle materials. Tuning reset/export includes the colors.
- Validation: headless Chrome on local macOS checked 960 swept camera segments against the actual arena GLB with no intersections, all four tint colors, unchanged trim and independent clone colors. Inspected the green patrol crossing capture. Final tests/build results follow below.
- Final validation: 39 camera/player/patrol/audio tests pass, including full patrol traversal and camera-over-crown clearance. Chrome `tests/cycle-opening.mjs` also passes tint picker/preset/reset checks, verifies independent default and arena colors, and retains normal launch timing, pause/resume and reduced-motion coverage. Production build and whitespace checks pass (existing chunk-size advisory).
## Compact ENCOM E app icon — September 28
- [x] Replace the full ENCOM wordmark on the PWA icon with its E and wrapping curved outline. Adapt the existing SVG lettering and border into a square mark, preserving pale lavender strokes, dark backing and maskable padding. Keep the terminal's full wordmark intact.
- [x] Store the editable mark in `public/images/encom-app.svg`; regenerate `public/icons/icon.svg` and the 180/192/512 PNG sizes with `node scripts/generate-app-icons.mjs`. Inspected the rendered 512-pixel icon in Chrome on local macOS.
- Icon validation: production build and whitespace checks pass; verified PNG dimensions at 180, 192 and 512 pixels.
## Match Light Cycles and Clu HUD typography — September 28
- [x] Share one typography rule for Clu's control hints and Light Cycles' control/status text: 9 px, 1.8 line height and 0.6 px letter spacing. Replace Light Cycles' larger 11 px text. Both modes already share the same turbo indicator and FilmTerminal font.
- Validation: reviewed the shared CSS rule; production build and whitespace results follow below.
- Production build passes with the existing chunk-size advisory; `git diff --check` passes.
## Terminal-sized resume prompt — September 28
- [x] Replace the two-part STAND BY / ANY KEY TO RESUME pause text with only ANY KEY TO RESUME. Share the terminal's responsive font-size variable, typeface, line height and letter spacing. Keep the existing top-centered placement and resume behavior.
- Validation: reviewed the markup and shared responsive CSS; production build and whitespace checks pass (existing chunk-size advisory).
## Simplify initialization copy — September 28
- [x] Remove AWAITING CARRIER from the initial loading terminal and attach the existing animated three dots directly to DIALING HOST. Preserve ENCOM REMOTE ACCESS and the cursor.
- Validation: reviewed loading markup; production build and whitespace checks pass (existing chunk-size advisory).
## Moving arena entrance patrol — September 28
- [x] Advance the arena patrol at the fixed simulation timestep throughout the sixteen-second entrance while keeping cycle countdown, racers and tank combat held. Pause freezes the camera and patrol together; race release remains free of simulation catch-up.
- [x] Start the green Recognizer already traveling south along the west ledge, toward screen-left in the maze-facing shot. Offset its initial position by three seconds of cruise travel so the camera still crosses above it. Continue the normal perimeter patrol afterward.
- Validation: 40 camera/player/patrol tests pass, including moving-patrol crown clearance, patrol direction during the entrance, unchanged racer state and unchanged other enemies. Browser/build results follow below.
- Moving-patrol browser validation: Chrome entrance checks pass, including leftward patrol movement while racers remain held and normal timing after release. Production build passed before the subsequent flight/HUD changes.
## Forward cruise and slow Recognizer maneuvering — September 28
- [x] Cap desired sideways and backward travel at a named 3 m/s maneuver speed. Preserve forward cruise, bounded acceleration and momentum through turns; do not snap existing velocity to the new limit. Expose the maneuver-speed cap in development tuning.
- [x] At open-flight altitude, tactical navigation faces its travel direction. Retain wall-aligned hull orientation for low-speed corridor positioning. A wider lateral crossing can now escape; preserve head-on interception without restoring high-speed strafing.
- Validation: 133 camera/player/patrol/tactical/simulation/interception tests pass. Updated previous half-cruise strafe expectations to the requested slow-maneuver behavior, retaining acceleration, obstacle clearance, head-on interception and continuous turn checks.
## Solid carrier for Light Cycles — September 28
- [x] Show the carrier fully materialized for explicit cycle entry and cycle play, including the arena entrance. Preserve Clu's carrier materialization.
- Validation: Chrome entrance test confirms full solid opacity and hidden materialization panel during the opening; normal entry, moving patrol, pause/resume and race release still pass.
## Terminal-sized gameplay text — September 28
- [x] Supersede the previous 9 px hint-only match: all HUD text in Clu and Light Cycles now shares the terminal's responsive font-size variable. Include health/turbo/shot labels, hints, status/warnings, sight text, map text, pause prompt and development panels. Widen meter labels for the larger type.
- Browser layout and production verification results follow below.
- Final verification: headless Chrome `tests/home-menu.mjs` passes and confirms computed HUD/control font sizes match the terminal in both modes. Inspected desktop label/control captures; production build and whitespace checks pass with the existing chunk-size advisory. The earlier Chrome entrance run also verifies the solid carrier and moving patrol. Human flight-feel review remains pending.
## Detectable PWA icon revisions — September 28
- [x] Generate content-versioned icon filenames and update manifest, favicon and Apple touch icon URLs whenever artwork changes. Preserve the app's manifest identity, launch URL and scope. Keep stable icon copies for existing references. This fixes replacing icon bytes without changing the manifest's icon entries, which Chrome 144+ treats as unchanged.
- Deployment and installed-device status remain unconfirmed: local updates alone cannot update an installation from the public site. Chrome may offer Review app update for identity changes. Reference: https://developer.chrome.com/blog/improvements-to-web-app-updates .
## Automatic Clu entry after access typing — September 28
- [x] Remove the confirmation wait after REQUEST ACCESS TO CLU PROGRAM finishes typing. Automatically start the existing dissolve/camera approach and music; reduced-motion mode enters directly. Keep the initial menu selection gesture and consume input during typing.
- Update menu, offline PWA and cannon-reserve browser checks for automatic entry. Verification results follow below.
## Exterior-to-aerial Clu zoom — September 28
- [x] Make normal-view I/K zoom continuously from the existing exterior tank camera up to the same high aerial framing available in V mode. The closest setting is exactly the normal exterior distance and height; remove the previous closer-than-normal endpoint. Blend the look target down toward the tank as the camera rises instead of scaling a shallow chase angle. Preserve V as an aerial shortcut, gunner elevation, and cycle pedal controls.
- Verification results follow below.
- Automatic-entry and icon validation: Chrome home-menu checks pass, including no confirmation gesture and uninterrupted opening music. Production PWA checks pass with content-versioned icon URLs, correct dimensions, offline automatic Clu entry and waiting updates. Installed-device icon acceptance is unverified; installation URL/platform was requested but not supplied.
- Zoom follow-up: V-mode zoom also reaches the normal exterior endpoint. Share the effective aerial blend with fog and floor presentation so zoomed-out terrain remains visible. Camera endpoint and gunner/cycle controls are covered by unit tests.
## Assisted Recognizer targeting marker — September 28
- [x] Display a red downward V above the crown of the Recognizer selected by Clu's upward assisted cannon solution. Use the same narrow-cone, obstruction and lead constraints as firing. Project the marker into screen space; hide it off-screen, behind walls, during manual gunner view, cycle play, pause/opening or after target loss/destruction.
- [x] Use a small SVG graphic with no input handlers or GPU allocations. Reuse one projection vector; keep simulation targeting unchanged.
- Verification results follow below.
- Validation: 34 camera/gunner/marker tests pass, including visible assisted acquisition and rejection of blocked targets. Chrome `tests/assist-marker.mjs` passes acquisition, lost alignment, manual gunner and destroyed-target cases; inspected the red V capture. Reserved extra HUD space beside the wider meters. Production build and whitespace checks pass (existing chunk-size advisory).
## One-second completed Clu access hold — September 28
- [x] Hold the fully typed Clu access terminal for one second before its existing dissolve/camera approach. Continue automatically; no extra input is required, and input cannot shorten the hold. Apply the hold before reduced-motion entry too; reset its timer on a new session or return to program selection.
- Installed-icon diagnosis: the local Chrome app is `~/Applications/Chrome Apps.localized/Space Paranoids.app`, launching `http://localhost:5173/`. Its app.icns already contains the new E artwork; the Dock's ENCOM TERMINAL entry points to this same bundle. Restarted Dock to refresh its displayed icon. Use localhost when opening this installed app's origin; 127.0.0.1 is a separate origin.
- Verification results follow below.
## Subtler targeting chevron — September 28
- [x] Compress the targeting V from 18 to 12 CSS pixels tall while retaining its 30-pixel width. Set overall marker opacity to 0.5, including glow; let the SVG stretch vertically so its width stays unchanged.
- Validation: reviewed SVG sizing and CSS opacity; production build and whitespace checks pass. Chrome home-menu checks also pass with the one-second completed-access hold and automatic transition.
## Smaller marker with acquisition flashes — September 28
- [x] Halve the targeting V to 15 × 6 CSS pixels, retaining 0.5 steady opacity. A newly acquired Recognizer lock flashes three times at a 400 ms period, then holds steady. Losing the lock cancels the animation; acquiring a different target or reacquiring after loss starts a fresh sequence. Ordinary updates to the same lock do not restart it.
- Use the browser animation timeline with named timing values; no timers or recurring callbacks are added.
- Validation: Chrome `tests/assist-marker.mjs` passes exact 15 × 6 sizing, three on/off flashes, steady 0.5 opacity without retriggering, fresh flashes after reacquisition, and disappearance on manual gunner/destruction. Production build and whitespace checks pass with the existing chunk-size advisory.
## Automatic Bit microphone request — September 28
- [x] Selecting Bit now requests microphone access on entry without an extra activation key/click. Show REQUESTING MICROPHONE ACCESS while the browser prompt is pending; launch after approval and stop the temporary permission-check stream. Already-granted permission still launches directly.
- [x] On denial, remain unlaunched and offer an explicit retry after microphone permission is enabled. Preserve Escape during pending permission and avoid launching after leaving. The browser's own permission prompt remains authoritative.
- Update integration tests and README to describe automatic activation. Browser requests and speech remain mocked during verification.
- Validation: Chrome `tests/bit.mjs` passes automatic entry, microphone approval/denial, temporary stream release, heading alignment, answers and Escape return. Production build and whitespace checks pass with the existing chunk-size advisory. No real microphone recording or paid provider requests were used.
## Arena opening carrier and faster target flashes — September 28
- [x] Stage the carrier beyond the maze on the right of the 16-second cycle entrance, using the supplied original-film still. Preserve model scale, expose the open bow on the right, and move along its heading. Keep this cinematic placement scoped to the opening; ordinary world transit resumes at the gameplay cut. Extend the cycle camera far plane to retain the distant ship.
- [x] Double the targeting V blink rate: three 200 ms flashes (600 ms total), then steady at 0.5 opacity. Size and reacquisition behavior stay unchanged.
- Validation: Chrome `tests/assist-marker.mjs` passes the exact 200 ms period, three flashes and steady state. Chrome `tests/cycle-opening.mjs` passes entrance, wall clearance, moving patrol, pause and gameplay handoff; inspected opening and carrier-angle captures. Full film-motion fidelity remains subject to visual review.
## Gold-team formation and active Clu approach — September 28
- [x] After the 16-second cycle camera move, hold its exact endpoint for a 2.75-second materialization of the three gold cycles, then cut immediately to the player's cycle view. Keep racers frozen throughout; skip the second countdown reveal. Use the actual gold starting row on the north side with 14.4-meter spacing, facing into the arena, and team-colored reveal lines. Reduced-motion entry keeps its existing shortcut.
- [x] Use I/K to zoom in/out consistently inside and outside the arena, from normal cycle follow framing to the aerial range. Reserve T for turbo in both modes; retain W turbo and S/X slow in the arena, and W/S speed control plus X brake/reverse outside. Update control help and meter labels. This supersedes the earlier I/K speed aliases.
- [x] Enable keyboard driving, turret and firing controls during Clu's camera approach, plus mouse firing. Start the approach at normal maximum forward speed with the same persistent cruise flag as Shift+W. Camera-mode selections can leave the approach immediately; terminal typing and its one-second hold still complete automatically before controls activate.
- Validation: 33 camera/player simulation checks pass. Chrome entrance checks pass the stationary three-cycle formation, paused race, immediate handoff and I/K zoom. Further browser and build results follow.
- Final validation: all 365 `npm test` checks pass. Chrome `tests/home-menu.mjs` verifies maximum-speed cruise, steering, turret movement and firing during Clu's approach; `tests/cycle-player.mjs` verifies the north-side start, turning, turbo and restart. Target marker and entrance browser checks pass. Production build and whitespace checks pass (existing chunk-size advisory). Chrome headless on macOS; rendered carrier-angle, entrance and formation captures inspected. Human driving/sound and exact film fidelity still require review.
- Commit review: newly staged imported Bit sources/assets retain upstream whitespace, which `git diff --cached --check` flags. The earlier whitespace check covered tracked edits; these vendor-format warnings do not affect the passing build/browser checks.
## Close angled cycle formation — September 28
- [x] Ease the final two seconds of the arena approach into a low, close view from the formation's right: 8 m right, 5 m forward, 1.6 m high, with a 38-degree vertical field of view. Hold that framing during materialization, then retain the immediate player-camera cut.
- [x] Tighten the actual gold starting row to 4.8 m spacing so all three cycles compose a close group. Accelerate materialization by 50% (1.5× playback rate), reducing its duration from 2.75 to 1.833 seconds.
- Validation: 34 camera/player tests pass; Chrome entrance check passes swept wall clearance, stationary three-cycle formation, paused race, camera handoff and zoom controls. Inspected the angled formation capture. Production build and whitespace checks pass; the existing chunk-size advisory remains. Chrome headless on macOS.
## Progressive arena speed changes — September 28
- [x] Slow the arena's speed response from 8/s to 2/s: turbo acceleration, S/X deceleration, and return to cruise now complete 95% of the requested speed change in about 1.5 seconds. Preserve top speeds, half-speed slowing, boost charge timings, and bounded collision substeps. Road riding already uses acceleration/braking limits.
- Update movement checks to measure fractional cell progress during the gentler acceleration, and verify that both turbo and S remain partway through their speed changes after short key holds. Correct the road tuning help to show T for turbo.
- Validation: 43 player, arena-race and road-dynamics tests pass. Chrome `tests/cycle-player.mjs` confirms gradual turbo/S response with real key events, plus charge, release, pause and restart. Its turning fixture clears the neighboring teammate's trail to isolate controls from the tighter formation's collision. Production build and whitespace checks pass (existing chunk-size advisory). Chrome headless on macOS; final driving feel remains for hands-on review.
## Clu camera clearance and continuous zoom return — September 28
- [x] Apply camera collision constraints after approach/zoom/gunner positioning, including transitions that previously overwrote or bypassed the follow-camera wall check. Sweep between rendered positions against padded wall prisms, including roofs; slide along a safe axis when a direct move crosses a wall. Keep aerial ascent possible over intervening walls.
- [x] Replace minimum-fraction camera clipping with a 5 cm contact margin and 1.2 m follow-camera clearance (20 cm in gunner mode). A long camera boom must not force the camera beyond a nearby wall. Free/reference cameras remain explicit inspection views.
- [x] Keep encounter pitch tracking active throughout I/K zoom and blend overhead Recognizer framing continuously near the follow-camera end. Reaching minimum zoom no longer restores stale follow pitch or abruptly enables encounter framing.
- Added deterministic wall-corner, roof, approach, aerial, gunner and minimum-zoom regression tests. Chrome home-menu checks pass active Clu entry and zoom controls; final wall/browser validation follows.
## Two target flashes and less precise auto aim — September 28
- [x] Flash the target V twice at the existing 200 ms period, then hold at 0.5 opacity until lock is lost.
- [x] Increase locked auto-aim shot scatter to ±2.4 degrees horizontally and ±1.2 degrees vertically around the predicted intercept. Preserve seeded shot variation, existing lead/acquisition, level-shot behavior and exact manual gunner aim. Tests check the wider bounds and reproducibility.
- Final validation: all 372 `npm test` checks pass. The long-range firing test now requires both hits and misses across deterministic assisted shots instead of assuming one shot always hits. Chrome `tests/assist-marker.mjs` verifies two flashes; `tests/camera-walls.mjs` records 389 clear rendered frames through actual maze-wall zoom/turn/gunner transitions. Production build and whitespace checks pass, with the existing chunk-size advisory. Chrome headless on macOS; final camera and shot feel still require human review.
## Recover camera following after wall contact — September 28
- [x] Fix the wall-avoidance dead end: axis-only moves toward the desired camera could leave it pinned while Clu kept driving. Detect stalled progress and enter a swept recovery path above the wall, then track the moving tank and return to normal follow as soon as the tank is visible. Recovery moves at a bounded 100 m/s and resets with the camera.
- Add a moving-tank regression: start the camera stranded across a wall, drive Clu continuously at 22 m/s, and require clear camera sweeps, bounded separation and return to normal follow height. Extend the real-maze Chrome check to drive while zooming back in and verify the camera keeps up.
- Validation: 40 camera/gunner tests pass, including recovery while Clu keeps driving. Chrome real-maze check passes 483 rendered frames of clear sweeps, driving during zoom return, bounded camera/tank separation and gunner transitions; inspected its final follow-view capture. The browser fixture now requires a clear 90 m driving lane so a tank-wall collision cannot invalidate the follow check. Production build and whitespace checks pass (existing chunk-size advisory). Chrome headless on macOS; driving feel remains for user review.
## Arena cycle camera clearance and front-facing patrol pass — September 28
- [x] Apply final camera constraints and frame-to-frame sweeps to cycle follow, side glances, aerial zoom and arena entrance views. Include the arena's four 60 m wall prisms as well as maze walls, with independent collision geometry and the existing recovery path if blocked.
- [x] Keep breach passages usable by subtracting openings derived from the authored breach profile. Test intact walls, roofs, breach passage, all four starting-wall sides, moving side glances and return from zoom.
- [x] Start the arena patrol 24 m farther back along its existing route. Preserve its forward motion and green tint; the entrance camera now passes in front of the Recognizer instead of directly over its center, exposing its front.
- Validation: 44 arena/cycle/camera/player tests pass, including padded wall clearance, roof crossing rejection, breach passage and front-facing patrol visibility. Chrome `tests/cycle-opening.mjs` passes entrance, patrol motion, formation, pause, handoff and zoom checks; inspected the rim flyby capture. Production build and whitespace checks pass (existing chunk-size advisory). Chrome headless on macOS; final game feel remains for hands-on review.
## Keep the wall-constrained camera out of the cycle — September 28
- [x] Raise the cycle camera's collision anchor from bike-body height to 7 m, while retaining the normal unobstructed 4 m follow height. When the bike fits closer to the arena wall than the camera's padded volume, inset the anchor onto the arena side. Wall correction can shorten the horizontal boom without pulling the camera down into the cycle.
- [x] As the boom shortens, blend the look point from forward driving focus toward the cycle itself, producing an overhead view with the bike still visible. Preserve the authored formation shot.
- Regression tests park the cycle against all four walls and a corner, checking camera height, wall clearance and on-screen bike position through side glances. Browser reproduction uses the actual gold model at the closest legal wall placement.
- Validation: 24 arena/cycle/camera tests pass. Chrome `tests/arena-camera.mjs` verifies clearance above the actual gold model during normal follow and both side glances; inspected the close-wall capture showing the complete cycle from above. Production build and whitespace checks pass (existing chunk-size advisory). Chrome headless on macOS; final driving feel remains for hands-on review.
## Formation materialization pacing — September 28
- [x] Shorten the gold formation's wireframe sweep from 1.2 to 0.933 seconds and lengthen its solid fade from 0.233 to 0.5 seconds. Retain the initial 0.4-second opening and total 1.833-second hold before the player-camera cut. Give formation timing its own configuration while retaining other materialization defaults.
- Validation: 13 cycle-camera tests and Chrome `tests/cycle-opening.mjs` pass, including the formation hold and race handoff. Production build and whitespace checks pass (existing chunk-size advisory). Chrome headless on macOS; pacing remains for user review.
## Restore gunner sight typography — September 28
- [x] Restore the original monospace zoom label and sight-green color by exempting it from the global terminal color-font override. Keep its original 30 px SVG font size.
- Validation: Chrome headless on macOS reports monospace, 30 px, and matching green text/fill (RGB 86, 214, 51) while playing.
## JEV driving with player-controlled firing — September 28
- [x] Shift-U toggles Clu autoplay with automatic firing disabled. JEV retains driving and turret aiming; Space and mouse firing remain player-owned. Shift-U switches full autoplay into manual-fire mode or turns manual-fire mode off. U retains its existing autoplay toggle.
- [x] Show MANUAL FIRE in the autoplay HUD and describe Shift-U in its tooltip. Keep the firing restriction through pilot replanning and reset; disabling/re-enabling ordinary autoplay restores automatic firing.
- Validation: 26 autoplay/input tests pass, including an aligned enemy that full autoplay fires at while manual-fire autoplay preserves movement/aiming but emits neither firing channel. Production build and whitespace checks pass (existing chunk-size advisory).
- Chrome `tests/autoplay.mjs` passes Shift-U activation, full-to-manual mode switching, manual Space firing while driving, and disabling, plus existing autoplay lifecycle checks. Provider responses were mocked; no paid requests. Updated an existing text assertion to accept the uppercase HUD and waited for its render before checking the mode label. Chrome headless on macOS.
## Soundtrack credits — September 28
- [x] Add on-screen and archival music credits for Wendy Carlos (composer; gameplay excerpts from We've Got Company and Tower Music - Let Us Pray) and Journey (Only Solutions, ending/credits). Record artist/album source links in the archival credits.
- Validation: Chrome headless on macOS `tests/extended-credits.mjs` passes the complete tribute-to-signoff sequence and eight-line page layout checks. Whitespace checks pass.
## Cycle glance horizon at every zoom — September 28
- [x] Keep the J/L orbit angle when blending into the I/K elevated view. During a glance, ease downward pitch to at most one quarter of the vertical field of view, leaving the horizon within frame. Release returns to the chosen zoom framing. Apply the look adjustment after collision correction without moving the camera through walls or into the cycle.
- Validation: 25 camera tests pass across arena/road, both glance directions and zoom scales 1–128, including release recovery and tight-wall clearance. A deliberate glance now prioritizes the horizon over keeping the bike centered; ordinary close-wall follow retains its overhead bike framing. Production build passes (existing chunk-size advisory).
- Chrome headless on macOS `tests/arena-camera.mjs` passes model clearance and real K/J zoomed-horizon checks; inspected the rendered horizon capture. The preview server stopped during the first capture attempt and was restarted before the successful run. Whitespace checks pass.
## Consolidated validation — September 28
- All 379 `npm test` checks pass before committing the camera, driving, targeting, manual-fire autoplay, credits and presentation changes. Updated the credits page-count bound for the two requested soundtrack pages and explicitly assert both music credits. Prior targeted Chrome checks and production build results are recorded above.
## Trim the first second of cycle entry — September 28
- [x] Begin the authored cycle entrance at 00:01, leaving 15 seconds of camera travel before formation. Keep camera, carrier and audio on the same authored clock; advance the arena patrol through the skipped second using bounded simulation steps. Preserve formation timing and reduced-motion entry.
- Validation: 14 cycle-camera tests, Chrome `tests/cycle-opening.mjs` (including the 1-second starting offset, patrol, audio, pause and handoff), production build and whitespace checks pass. Chrome headless on macOS; existing build chunk-size advisory remains.
## Shared BIT JEV relay configuration — September 29
- [x] Confirm BIT uses the same /api/jev/decision relay, upstream JEV endpoint, model configuration and budget as Clu. Fix development config so BIT receives VITE_JEV_API_BASE just like the main client; production already embeds that shared setting. The config response contains only the public base URL. Light-cycle race opponents currently use their local planner.
- Validation: 17 local/public relay tests pass, including BIT upstream/response compatibility, custom-base configuration and credential isolation. Chrome `tests/bit.mjs` passes replies, menu/exit and microphone-permission paths with mocked API responses; no paid requests. Production build and whitespace checks pass (existing chunk-size advisory). Chrome headless on macOS.
## Live BIT relay and character context — September 29
- [x] Deploy the Worker separately from the website: the stale live protocol rejected BIT questions with HTTP 400. After deployment the same request returned HTTP 200.
- [x] Supply explicit BIT identity facts and interpret second-person questions as addressed to the TRON character. Reserve UNSURE for unknown or non-binary questions instead of uncertainty about roleplay identity.
- Validation: 17 relay tests and Worker dry run pass. Deployed Worker version 0fa10ec9-be17-4d31-8e14-30ac45073287; live Are you a bit? returned m0 (YES), confidence 1. This was a real provider request.
## Smooth cycle formation handoff — September 29
- [x] Replace the post-materialization camera cut with a 1.2-second eased move into player follow. Track the launching cycle, blend orientation and field of view, and lift the path over the bikes while retaining final wall constraints. Pause holds transition time; reset clears it.
- Validation: 19 cycle/arena camera tests pass, including first-frame position/orientation/FOV continuity, bounded motion, pause and completion. Production build and whitespace checks pass (existing chunk-size advisory).
- Chrome `tests/cycle-opening.mjs` passes the entrance, held formation, handoff and zoom controls. Inspected the midpoint capture after changing the aim blend to follow a moving look point with world-up orientation. Chrome headless on macOS; final motion feel remains for user review.
## Confirm Escape before returning home — September 29
- [x] Escape pauses Clu, Light Cycles and BIT and opens a shared modal with Continue Playing and Return Home. Continue receives initial focus; Escape cancels. Block gameplay keys behind the modal, preserve pause during tuning close, and reset game state on confirmed return. BIT suspends audio, listening, requests and rendering while the panel is open. Include the shared script in the offline cache through the existing BIT asset inventory.
- Validation: Chrome tests/exit-confirm.mjs passes all three games, frozen simulation, input blocking, microphone pause, cancellation/resume and home navigation. tests/bit.mjs passes reply and microphone lifecycle checks with confirmed exit. Mocked provider requests; Chrome headless on macOS.
## Keep the cycle forward view near walls — September 29
- [x] When wall clipping would shorten the follow boom below 7 m, choose a clear 10 m lateral offset along the wall. Prefer the side nearest the previous camera, retain wall sweeps, and reduce look-ahead during this side view to keep the bike visible. Apply the same destination during the materialization handoff.
- Validation: all 382 npm test checks pass. Tight-wall tests now require viewing distance and shallow downward pitch on all four sides and a corner. Chrome tests/arena-camera.mjs passes and its inspected capture shows the complete cycle with the forward arena visible. Production build passes (existing chunk-size advisory); final driving feel remains for user review.
## Wall-connected starting trails and direct launch cut — September 29
- [x] Seed each active arena cycle trail from the nearest inner arena wall at launch. Fill the intervening grid collision cells and preserve the wall endpoint as the first segment grows. Initial render trimming attaches the trail to the rear axle without losing the wall connection.
- [x] Restore the fixed formation shot through materialization and direct cut to player follow at launch; remove the 1.2-second transition and its lift/field-of-view blend. Preserve the newer near-wall side view.
- Validation: 382 full-suite checks passed, followed by 24 focused tests including the new starting-trail regression and direct-cut camera check. Production build and whitespace checks pass (existing chunk-size advisory).
- Chrome tests/cycle-opening.mjs passes the held formation, launch handoff, audio and zoom checks; inspected its post-launch follow capture. Chrome headless on macOS.
## Arena palette, carrier framing and launch hold — September 29
- [x] Darken the green Recognizer face preset to #1c3825 (used by the arena patrol). Brighten arena purple symbols to #61249b with #9c6bbe outlines.
- [x] Set the entrance carrier 800 m back along its heading while retaining its altitude, orientation and forward speed. Inspect its first frame at 1250×1000 to ensure the entire vessel fits.
- [x] Hold the fixed formation shot for one second after materialization as the cycles launch. Release race simulation while retaining the shot, then cut directly to follow. The inspected late-hold capture shows gold trails with the bikes out of view.
- Validation: all 383 npm test checks and production build pass (existing chunk-size advisory). Chrome entrance checks cover fixed camera during launch, moving race, patrol tint, pause, audio and handoff. Whitespace checks pass.
## Follow surviving cycles after death — September 29
- [x] Show the requested top-of-screen death message: CYCLE DETROYED - ARROW KEYS TO FOLLOW - ENTER TO RESTART. Replace automatic free-flight spectator entry with a surviving-cycle follow camera. Left/Right wrap through live cycles, skip destroyed cycles, and leave player identity unchanged. Select another survivor if the followed cycle dies; Enter retains match restart.
- [x] Latest launch-hold tuning is 0.6 seconds, shortened in two requested 0.2-second steps.
- Validation: 383 full-suite checks pass, followed by 16 camera tests including selected-survivor tracking. Chrome tests/cycle-follow-spectator.mjs passes message, both arrow directions, wraparound, target death and restart; inspected the rendered message. Build and whitespace checks pass (existing chunk-size advisory). Chrome headless on macOS.
## Finite light-cycle trails — September 29
- [x] Cap each cycle's trail at the arena's inner perimeter: 8 × 409.06 m = 3272.48 m. Advance the oldest endpoint as new wall appears, discard exhausted segments, preserve active-segment references, and release expired collision cells. Retain stripe placement as the tail recedes. Account for rear-wheel attachment, fractional movement and discontinuous arena reentry.
- Validation: all 386 npm tests pass on the final run. The first full run had a crossing-Recognizer shooting failure; that test passed in the 69-test focused rerun and final full run. Production build passes with the existing chunk-size advisory. Chrome headless on macOS: tests/cycle-trail-limit.mjs verifies rendered length against the cap, reports no page errors, and produces an inspected aerial capture. Unit checks cover corner trimming, reclaimed cells, other cycles' ownership, active-segment remapping, fractional advance, death and reentry.
## Cycle death overview — September 29
- [x] After player-cycle death, smoothly pull up over three seconds to a fixed view above the nearest arena wall, aimed at the center. Rise clear of the wall before translating outward. Fit the full arena with an inset margin based on window aspect ratio. Left/Right still select surviving cycles to follow; Enter restarts.
- Validation: 21 cycle/arena camera tests pass, including smooth initial motion, vertical wall clearance, full-arena framing at three aspect ratios, center aim and reset. Chrome headless on macOS: cycle-follow-spectator.mjs passes overview entry, survivor selection, wraparound, target death and restart; inspected cycle-death-overview.png. Production build passes with the existing chunk-size advisory.
## Lower cycle death perch — September 29
- [x] Replace the high, whole-arena overview with a fixed perch 6 m above the 60 m arena wall, at its inner edge. Retain the three-second pullback, completing the vertical rise before moving across the wall. Smoothly point at a surviving cycle and select another if it dies; use the arena center when none remain. Arrow keys still enter normal survivor-follow views.
- Validation: 21 camera checks pass for bounded height, clearance during ascent, a fixed perch while tracking a moving survivor, replacement targets and reset. Chrome spectator lifecycle checks pass; inspected the lower perch capture. The original whole-arena framing requirement is superseded by this lower tracking view.
## Shared cycle turbo and rechargeable arena brakes — September 29
- [x] Give autonomous cycles use of their own turbo reserves: boost on clear arena straights, and brake when the lane ahead closes. Road agents can also use their existing turbo on clear routes. All arena cycles share the same gradual speed response and resource rules.
- [x] Add a per-cycle arena brake reserve: five seconds of braking, sixty seconds to recharge fully when released, partial-charge reuse, and no repeated pulses when holding an empty brake. Brake requests take priority over turbo. Reset restores both reserves. Show S/X brake charge beneath the turbo meter using the terminal text scale. Road speed-hold controls retain their existing braking behavior.
- Validation: all 389 npm tests pass, including autonomous turbo/brake use, empty-brake hold, release/recharge, partial reuse and reset. Chrome headless spectator lifecycle checks pass; inspected the additional brake HUD in the rendered capture.
## Spectator resource meters — September 29
- [x] Bind cycle HUD meters to the watched cycle: selected survivor in follow mode, or the survivor tracked from the wall perch. Turbo/brake fills and active states, road health and speed now use that same bike; ordinary play still uses the player bike.
- Validation: Chrome headless on macOS, cycle-follow-spectator.mjs passes with distinct turbo/brake reserves per bike. Checks cover wall-perch meters, both arrow directions, wraparound, active-state changes, automatic replacement after target death and restart. Whitespace checks pass.
## Partial-charge Clu turbo — September 29
- [x] Allow Clu to trigger turbo whenever some reserve is available. Convert the recharged fraction into boost duration (25% gives 2.5 seconds), spend that available reserve and restart its recharge timer. Prevent stacking during an active boost; retain the existing ten-second full boost, sixty-second recharge timing, teleport/transfer restrictions and destruction reset.
- Validation: 89 simulation, autoplay and ground-tank checks pass. Added coverage for empty charge, partial recharge, proportional duration, immediate reuse of newly available charge and tiny partial reserves. Existing full-duration, braking and destruction checks also pass.
## Clu autoplay temporarily disabled — September 29
- [x] Disable player autoplay through the application feature flag and an unavailable Autoplay instance. U, Shift-U and button activation cannot enable driving, aiming or firing assistance. Hide the autoplay button and Clu JEV statistics; retain the implementation for later reactivation. Enemy AI and the other programs retain their existing behavior.
- [x] Track the three newly supplied film-reference clips with Git LFS, including the 194 MiB clip that exceeds normal GitHub file limits.
- Validation: all 391 npm tests pass. Chrome headless on macOS: tests/autoplay.mjs verifies hidden/disabled UI, U and Shift-U, synthetic button activation, no player API requests and no page errors.
## Clu keyboard zoom restricted to aerial mode — September 29
- [x] I/K no longer changes Clu's normal following-camera distance. Only V aerial mode accepts keyboard zoom for Clu; hide the zoom hint outside that mode. Gunner aiming and light-cycle keyboard zoom retain their existing controls.
- Validation: extend the Chrome autoplay-disabled check to exercise I and K in normal view, zoom out/in in V mode, and verify zoom remains unchanged after returning to normal view.
## Home-screen credits — September 30
- [x] Add CREDITS after BIT in the program menu, with the same keyboard selection and click behavior. Reuse the existing standard and extended credit sequence with terminal music, omitting the detached-from-Clu header. Any key or click returns to program selection.
- Validation: Chrome headless on macOS, tests/home-credits.mjs passes keyboard and mouse entry, absence of the header, progression from standard to extended credits, and return/reentry. Inspected home-menu and credits captures; whitespace checks pass.
## Clu menu naming — September 30
- [x] Rename the home menu's SPACE PARANOIDS option to CLU. Replace the old masthead branding and home-link accessible name with ENCOM TERMINAL; the browser title and PWA names already use ENCOM TERMINAL.
## Clu startup cruise cancellation — September 30
- [x] Remove the separate startup-throttle override and use the same cruise latch as Shift-W. S/Down cancels cruise instead of temporarily overriding it, so releasing the brake does not resume forward acceleration. Plain W releases cruise; Shift-W enables it again.
- Validation: 48 simulation/input checks pass. Chrome headless tests/cruise.mjs passes startup cruise, both brake keys, no forward reacceleration after release, Shift-W reenable and plain-W cancellation. Whitespace checks pass.
## Quiet JEV fallback and scalable meters — September 30
- [x] Restore JEV as the browser default, superseding older stored AI-mode preferences once (preference version 4). Keep manual Clu autoplay disabled. Log local/classic AI selection to the console instead of showing a persistent HUD warning. On JEV failure, show JEV unavailable for four seconds with a fading final portion; retain local fallback and suppress repeat notices during a continuous outage.
- [x] Stack vehicle meters at the top left in a shared grid with font-relative widths, bar heights and row gaps. Wrapped labels grow their rows; position controls beside the stack on desktop and below on narrow screens.
- Validation: Chrome headless tests/system-warnings.mjs passes default migration, simulated outage/fade, console-only local selection and non-overlapping meters in Clu and light cycles at enlarged font sizes. Inspected rendered captures. The crossing-target shooting regression now uses a fixed seed and asserts damage, since authored spread does not guarantee a lethal hit location.
- Final verification: all 391 npm tests and production build pass; existing bundle-size advisory only.
## Smaller vehicle meter labels — September 30
- [x] Reduce Clu and light-cycle meter-label text to 80% of the terminal font size. Retain the existing meter widths, spacing and other HUD text sizes.
## Restore meter position — September 30
- [x] Return both games' vehicle meters to the top right, preserving the stacked spacing and 20% smaller labels. Reserve space for them on the right of the control hints.
## Consistent smaller game text — September 30
- [x] Use the meter-label size (80% of terminal text) for Clu and light-cycle HUDs, controls, notices, pause/exit prompts, camera help and tuning panels. Keep terminal/home/credits text at its existing size. Preserve the gunner typeface and colors. Meter and hint spacing share the game text scale.
## C controls pause — September 30
- [x] Replace C's free-camera shortcut with a controls panel that pauses Clu or light cycles. Show controls appropriate to Clu, arena cycles or outside-arena driving. Any fresh keypress, tap or click dismisses the panel and resumes; consume that event so it cannot fire, steer or open the exit panel. Update the in-game control hints.
- Validation: Chrome headless tests/controls-help.mjs passes frozen simulation and resume via Space, Escape, C, mouse and touch in both games, with no leaked firing or exit prompt. Inspected the controls panel capture; whitespace checks pass.
## Brief controls hint and reliable V transitions — September 30
- [x] Replace automatic full control lists with a centered C / CONTROLS prompt for the first five seconds of active play. Keep loading/death/result messages and the C pause panel. Count from gameplay after the cycle introduction and exclude paused time.
- [x] Release a stale follow-camera wall-recovery path when an aerial movement has a clear swept path. Ignore repeated V keydown events, and restore aerial scale when enabling V after zooming down to the follow-view limit.
- Validation: all 29 camera checks pass, including a regression with an occluded tank, stale recovery and clear aerial movement. Browser controls coverage includes the timed hint and V key repeat behavior.
## Grouped controls panels — September 30
- [x] Remove the M mute shortcut and its control hint. Group Clu controls into Driving, Weapons, Camera and Game, and cycle controls into Driving, Camera, After Death and Game. Remove the final any-key/tap/click instruction while retaining that dismissal behavior.
## Recover stale offline installations — September 30
- [x] Add an uncached update.html entry that checks for a new worker, explicitly activates the downloaded update and returns to the game. This works even when an older worker serves cached entry HTML and its old registration code.
- [x] Future clients activate waiting updates from terminal/home screens and reload after controller replacement. Preserve running games and credits until they return home; keep ordinary offline caching.
- Validation: Chrome tests/pwa-update.mjs reproduces old cache-first HTML and a worker without update messaging, then verifies migration through update.html into the current playable game. Production build and whitespace checks pass.
- PWA lifecycle verification also passes: offline Clu/cycles/BIT, audio ranges, installability, waiting during play, and applying the waiting update after returning home.
## Credits navigation — September 30
- [x] Space skips directly to the next credit page, including extended credits, resetting the current typing/fade state. Only Escape, Return/Enter, click or tap returns to the home screen; other keys are ignored. Ignore repeated keydown events.
- Validation: Chrome tests/home-credits.mjs passes Space progression into extended credits, ignored unrelated keys, Escape/Return/numpad Enter exits and click/tap exits. Whitespace checks pass.
## Teammate turbo discipline — September 30
- [x] Teammates conserve turbo at launch and on uncontested straights. Use it with a clear lane when a live opposing cycle is nearby, in a neighboring lane and traveling in the same direction; release after establishing a lead. Use separate engage/release lead thresholds to avoid rapid toggling. Apply the same policy to teammate road driving; preserve player controls and opponent behavior.
- Validation: all 49 cycle/player/road tests pass, including launch conservation, enemy use, a nearby rival, release after pulling ahead, and no boost for oncoming or destroyed rivals. Whitespace checks pass.
## Terminal-style exit choices — September 30
- [x] Use stacked, borderless choices in all three games' Escape confirmation panels, matching the home menu's blinking block cursor. Default to Continue Playing on every opening. Arrow keys, Tab and pointer focus move the selection; Enter/Space or click activates it, and Escape resumes. Respect reduced-motion preferences. Remove the panel fill and border so only the text and cursor appear over the dimmed game.
- Validation: Chrome headless on macOS, `node tests/exit-confirm.mjs` passes Clu, cycles and BIT pause/resume, cursor selection and blink timing, keyboard navigation/confirmation and click return home. Inspected the Clu panel capture; syntax and whitespace checks pass.
## JEV light-cycle maneuver selection — September 30
- [x] Include autonomous arena cycles from both teams in the existing shared JEV scheduler and transport, excluding the human-controlled cycle. Rotate requests among eligible bikes with a four-second per-bike replan interval and existing global quotas, cancellation and fallback.
- [x] Supply bounded, unobstructed cycle observations and clear corridor destinations in arena-local meters. A cycle-specific shared relay prompt selects a strategic destination; local steering rechecks cells and available space each crossing. Goals expire after six seconds; reject late, uncertain, dead/escaped or previous-round replies. Keep local turbo/brake rules, including teammate conservation. Outside-arena road driving retains its local controller.
- Validation: all 398 `npm test` checks pass. Chrome headless on macOS, `node tests/cycle-jev.mjs` verifies mocked API requests, accepted arena decisions, human exclusion and paused scheduling. Unit checks cover round changes, visibility, stale replies, local fallback and blocked-cell overrides. Live-provider tactical quality remains unverified. Public deployment must include the updated Worker prompt alongside the client.
## Social sharing preview — October 1
- [x] Add static Open Graph and large-image Twitter Card metadata for the public TRON URL, with ENCOM TERMINAL title, game description and image alternative text. Use the user-supplied arena screenshot, converted without cropping to a 1080×720 JPEG for sharing compatibility (`public/images/encom-social-preview.jpg`). Source: user attachment `encom-terminal-v0-uxv6f49c6ssh1.png.webp`; supplied for this site's preview, no broader license asserted.
- Validation: production build passes; built HTML contains both sharing-image tags and the large-image card type, and the built JPEG matches the source asset with verified 1080×720 dimensions. Platform-specific preview rendering remains controlled by each social service.
## Encrypted repository credential — October 1
- [x] Initialize git-crypt and encrypt `credentials/**` before adding the JEV key. Allow only `credentials/Typesafe.txt` through Git's ignore rules. Export an owner-readable recovery key outside the repository; document unlocking and secure backup. Keep Cloudflare production secrets independent.
- [x] Ignore git-crypt ciphertext when loading a locked development checkout, retaining local fallback rather than sending encrypted bytes as an API credential. Preserve development-server file denial.
- Validation: staged credential has the git-crypt header and contains no plaintext key; a separate temporary repository successfully unlocks the staged blob with the exported key. A locked server fixture reports JEV unconfigured. All 11 JEV tests and the production build pass. Prior history scan found no copy of this JEV key.
## Meshy shuttle reconstruction trials — October 2
- [x] Add a server-side Meshy generation/download script using ignored local credentials, persistent task IDs, reference hashes and explicit submissions. Download GLBs and previews locally; preserve the original and remeshed outputs for comparison.
- [x] Try original four-view film input (carrier contamination), focused two-view input (incorrect closed shell), and tight detachment-only input (fragmented geometry). Three completed jobs used 105 credits total. Keep the existing default shuttle unchanged; comparison links are available in the model studio.
- [ ] Produce a reference-faithful replacement shuttle. None of these Meshy outputs meets the supplied film references. Further generation needs clearer, isolated views or a different reconstruction approach.
- Validation: macOS Chrome headless loads and exports the four-view and detachment-only models, including the original detachment mesh; six rendered angles captured and the reference-angle capture inspected for the detachment failure. Credentials remain ignored; no signed URLs or keys recorded in task manifests.
## Shuttle smoothing and symmetry — October 2
- [x] Use the two-view Meshy shuttle's user-accepted silhouette for cleanup. Smooth welded geometry, planarize broad panels, and mirror one quadrant across width and height. Use a neutral surface material to remove baked texture noise. Make this the default standalone shuttle preview and export a separate GLB, retaining raw trials.
- Validation: Chrome on macOS captures six views and checks exact X/Y mirror correspondence for every mesh vertex plus GLB export. No extra Meshy credits used; small inferred surface details remain provisional.
## Shuttle hard-surface correction — October 2
- [x] Replace the rejected smoothing pass with explicit flat orthogonal panels. Quarter-circle front corners are the only curved or non-right-angle regions. Preserve width/height symmetry and straight detail strips; restore a clean green inset. Use this as the default studio model, preserving earlier comparisons.
- Validation: geometry invariant test passes for X/Y world-space mirror correspondence and no angled faces outside front corners. Chrome renders six views and exports the 1,532-triangle GLB. Visual proportions still require user review.
## Preserve shuttle structural details — October 2
- [x] Supersede the oversimplified planar reconstruction with the detailed two-view Meshy source. Disable global smoothing and retain its material maps, ribs and recess geometry. Restrict planarization to tightly grouped near-axis-aligned surfaces and keep X/Y symmetry. Export a separate detailed GLB and make it the studio default.
- Validation: Chrome six-view captures, symmetry checks and GLB export pass (38,008 triangles). Reference/top views inspected. Exact hard-surface reconstruction remains unresolved; do not claim the simplified panel model satisfies the reference.
## Provider-balance-controlled public JEV usage — October 2
- [x] At the user's request, set the public relay's shared daily request/input quotas and per-IP daily/minute quotas to explicit `unlimited`. Preserve request size validation, timeout, short pacing and concurrent-request controls; no cumulative site usage cap remains. Existing usage counters do not block requests under the new configuration. TypeSafe account balance and provider limits govern total usage.
- Validation: all seven Worker tests pass, including requests succeeding with counters far above the former caps. Invalid settings still fail; explicitly configured numeric limits retain their semantics.
## CLU variation, sound, shadows and victory cadence — October 2
- Normal CLU starts now choose a fresh run seed so maze patrol tank/Recognizer placements vary. Opening pursuit Recognizers and carrier escorts retain their formation; explicit `runSeed` and reference runs remain repeatable.
- Bullet surface-hit tones now use HRTF positioning at the impact point, with inverse distance attenuation (18 m reference distance). Mono source tones become directional stereo at the listener.
- Maze shadows on vehicles use a 2048-pixel tile and four-tap bilinear depth-comparison filtering instead of unfiltered 1024-pixel tiles. Exact floor/wall shadow geometry is unchanged; atlas memory increases fourfold.
- Victory typing retains the extended-credit 18 ms character interval, allowing 250 ms of catch-up so low frame rates no longer throttle it to two characters per frame. Human access typing retains its single-character limit. Text source: `docs/victory.txt`.
- Validation: `npm test` passed 402 tests, including seeded patrol placement/formation preservation and victory cadence at 5–30 fps. Desktop Chrome: `node tests/surface-audio.mjs` verified left/right channel energy and distance attenuation; `node tests/tank-wall-shadows.mjs` produced inspected before/after tank captures without shader errors; `node tests/wall-shadow-edges.mjs` retained zero incorrect pixels in the exact wall fixture. Audible balance and the precise screenshot location still need user play review.
## Extended-credit model previews and horizontal camera recovery — October 2
- Extended model credits now show a locally rendered tank, Recognizer, carrier, Solar Sailer, grid cloud, light cycle, or authored shuttle beside the text. The transparent preview requires at least 260 pixels of free width and 220 pixels of height; narrow windows keep text only. Model fade-in begins with printing, and fade-out follows the credit's opacity. Space, return-home, resize and reset dispose obsolete model resources; the preview shares the existing frame loop and respects reduced motion.
- CLU's normal follow camera now tries a swept horizontal detour around wall corners before rising. A bounded local path search covers 64 meters in each horizontal direction, with a 512-node budget; if it finds no route it retains the roof escape. Manual aerial, gunner and cycle cameras keep their existing behavior. Camera orientation continues tracking CLU throughout recovery.
- Desktop Chrome: `node tests/credit-models.mjs` passed seven model loads, fade synchronization, narrow-window hiding, reset during loading, disposal, and actual home-menu credit navigation. Inspected model and in-game captures in `test-results/credit-model-*.png`. `node tests/camera-walls.mjs` passed 483 rendered frames without wall crossings. `node --test tests/camera-collision.test.js` passed 10 tests including constant-height two-corner recovery, following a moving tank, and upward fallback with no bounded horizontal route. Victory printer tests also passed.
## Light-cycle wall connection — October 2
- Rounded the upper edge of the 1.2 m bike-to-wall connection into a quarter-ellipse, dropping toward the rear tire as in the user's CycleWallCurve reference. Sixteen short sections resolve the curve; the tire cutout, floor contact, and long wall sheets remain intact.
- Space is an additional cycle turbo key in arena and road modes; control help and meter labels include it. Braking still overrides turbo.
- Validation: 15 light-cycle/tuning tests passed. Chrome `node tests/cycle-wall-curve.mjs` rendered the 16-section connection without shader errors; inspected `test-results/cycle-wall-curve.png`. The older `tests/light-cycles.mjs` timed out in its obsolete C/Home/Space entry sequence before reaching its rendering fixture; its match check was not claimed as passed.
## Session-persistent arena damage and overlapping CLU opening — October 2
- Arena breaches survive cycle-round retries and full game/home resets in the same GameSession. A new app session (page reload) starts with intact walls. Both visible fracture geometry and collision openings retain the damage; debug starting breaches are deduplicated.
- Removed the opening camera's first-15-percent hold. Its zoom now begins with the terminal dissolve while preserving the one-second post-typing hold and 5.5-second approach.
- Validation: arena/session regression covers repeated resets, collision passage and a fresh session. Chrome `node tests/arena-breaches.mjs` confirmed the mesh opening remains on the next round, adjacent wall geometry remains solid, and an explicitly fresh empty arena restores the wall. Camera regression verifies descent in the first 5% of the opening. Updated the former Space-no-turbo assertion for the newly requested Space binding.
## CLU shot accuracy — October 2
- Bullet spread now applies only to acquired auto-aim targets. Unassisted exterior shots and manual gunner/mouse aim stay precise. Removed unused unassisted spread settings; assisted spread values are unchanged.
- Validation: 22 gunner/mouse-raycast tests passed, including exact unassisted headings over multiple shot seeds and retained assisted-shot variation.
## BIT startup audio cache — October 2
- Preload and decode both YES/NO recordings as BIT initializes, including while microphone permission is pending. Start listening only after startup loading settles; successful first replies use the same AudioBuffers as later replies. Removed redundant HTMLAudioElement loads and clear pending load entries so failed downloads can retry. Bluetooth microphone/output handoff timing is preserved.
- Chrome `node tests/bit-audio-cache.mjs` held both cold sample requests, verified BIT did not begin listening early, then checked first and repeated playback started synchronously from the decoded cache without any further fetches. Each WAV was requested once.
## Destructible light-cycle trails — October 2
- Hitting another cycle's arena trail immediately removes 1.6 m of wall to either side along its path. The older portion gets an independent normal hold/flash/lower lifetime; the newer portion remains attached to its living owner. Gaps span turns, repeated impacts retain existing death deadlines, and erased segments release their occupied cells without erasing the newer wall or reconnecting it across a gap.
- Per-segment destruction drives both rendering and collision expiry. Surviving cycle segment references remain valid after splitting/removal.
- Validation: `npm test` passed 410 tests. `node tests/cycle-trail-damage.mjs` rendered initial gap, half-lowered older section and surviving newer wall without browser/shader errors; inspected the half-lowered capture. Regression covers impact through the simultaneous crossing path, gap dimensions, old/new collision lifetimes, corner cuts and repeated hits.
## Trail retirement at the arena crack entrance — October 3
- A cycle moving outward into an open crack stops emitting at the inner entrance, rather than waiting to reach the far side of the wall. Its existing trail starts the standard hold/flash/lower lifecycle without killing the cycle. Travel through the wall no longer leaves extra trail or occupied trail cells. Reentry enables a fresh trail.
- Validation: 47 arena-breach, cycle-road, cycle-player and trail-damage tests passed, including an entrance test that keeps the rider alive, confirms no added trail through the wall, and verifies delayed normal expiry.
- Browser `node tests/cycle-reentry.mjs` reached its braking check but failed its existing near-instant deceleration expectation (`speedMultiplier < .51`); the full browser scenario is not recorded as passing. The targeted simulation lifecycle checks above passed.
## Cycle follow zoom horizon — October 3
- [x] Keep I/K cycle follow zoom shallow enough to include the horizon and bike together. Pull the camera farther back as it rises, and cap downward pitch after wall avoidance. Preserve the V aerial perspective and existing side-glance framing.
- [x] Validation: 22 camera unit tests passed, including zoom transitions in the arena and outside it. Chrome `node tests/arena-camera.mjs` passed wall clearance, forward zoom horizon and side-glance checks; the screenshot contained a black 3D scene despite passing camera-state assertions, so rendered appearance and human driving review remain unverified.
## Large-maze tank density and update terminal — October 3
- [x] Triple large labyrinth ground patrols from 8 to 24, distributed three per original broad spawn sector. Retain collision clearance, 30 m spacing, connected-corridor checks and seeded placement; smaller mazes and carrier escorts retain their counts.
- [x] Style the standalone update page with the local FilmTerminal font, home-terminal colors, placement, glow, ENCOM logo and blinking return cursor. Relative asset URLs support the deployed subdirectory; narrow screens wrap text and reduced-motion disables blinking.
- [x] Validation: 25 enemy-start/ground-tank/outer-maze tests passed. Chrome desktop capture inspected; font loading, background, logo and mobile overflow checks passed using `/tmp/tron-update-preview.mjs`. Gameplay performance with the increased tank count remains subject to human review.
## Double-turret boss tank — October 3
- [x] Add two bosses alongside the 24 regular large-maze patrols, starting on opposite sides of the central light beam with seeded, separated, connected spawn checks and information-limited AI. Use 12 health versus the regular 3; Clu shots deal 3 damage to boss armor, so four hits destroy it. Cap movement at 80% of normal enemy speed. Idle patrol destinations remain within the central courtyard, 100–230 m from the beam; bosses return there after combat pursuit.
- [x] Adapt the supplied SpringSociety double-turret GLB without changing its proportions: black armor, cool highlights, painted red contour lines, ground silhouette, articulated upper assembly, recoil and two muzzle flashes. Fire a simultaneous pair from measured barrel tips on the existing enemy cadence, with clearance checks for both shots and rotating boss hit regions.
- [x] Record embedded CC BY 4.0 provenance in model documentation and extended credits; add the styled model to its credit preview.
- [x] Validation: 29 focused tests passed. Full suite initially passed 417/418; the sole failure expected the old eight-tank roster. Updated it for 24 regular tanks and reran all six blueprint tests successfully. Chrome `node tests/boss-tank.mjs` passed startup/roster and rotated muzzle alignment checks and captured the styled adapter. The initial full-entry browser wait timed out under software rendering; corrected smoke coverage checks CLU entry plus the isolated model. Human combat balance and hardware performance review remain pending.
- [x] Courtyard follow-up: 14 boss/spawn/blueprint tests passed, including two distinct starts, 20 seconds of active court patrols, and movement confinement. Updated browser roster check for two bosses.
## Separate shuttle and maze credits — October 4
- [x] Split local shuttle modeling and maze/level work into separate extended-credit pages, preserving the note that blueprint artists are unidentified.
- [x] Show a live, disposable 3D miniature of the central labyrinth beside the maze credit, built from the actual wall outlines and triangles. Use an elevated view to reveal the courtyard and corridors; preserve the existing synchronized fades, narrow-window hiding and resource cleanup. The shuttle retains its own model preview.
- [x] Visually inspected the Chrome maze credit capture. All nine isolated browser preview/fade/lifecycle checks passed; the later home-screen credit navigation check timed out waiting for the tank page (`tests/credit-models.mjs:36`). Production build passed.
## BIT credit preview — October 4
- [x] Add the original neutral BIT OBJ beside ControllingTransmission's credit, with its lavender flat-shaded appearance, using the existing live preview and synchronized fade. Reuse the locally supplied MIT-licensed model; no new asset or attribution source.
- [x] Chrome `node tests/credit-models.mjs --previews-only` passed all ten model previews, synchronized fades, narrow-screen hiding, pending-load cancellation and disposal. Inspected the BIT screenshot. This isolated run excludes the previously recorded full-game credit-navigation timeout.
## Arena wall rider-view fallback — October 4
- [x] Enter a forward rider-height POV within 16 m of an arena wall, returning to chase only beyond 24 m to prevent repeated switching. Smooth camera position and direction; hide the followed cycle's exterior during the transition so the camera cannot look through its own mesh. J/L glances keep the horizon level.
- [x] Preserve manual I/K and V views. Clear the enclosure roof before zooming outward and return over the interior before lowering from an exterior aerial position. Do not apply the close-wall fallback to the film entrance or escaped road cycles.
- [x] Validation: 24 camera tests passed, covering four walls, corners, moving/glancing cycles, zoom handoff, reset, and recovery from an embedded camera. Inspected Chrome rider-view capture at `test-results/arena-camera-close-wall.png`. Human driving review remains pending.
## Above-cycle wall fallback — October 4 correction
- [x] Replace the close-wall POV with a camera 3 m above the floor and up to 2.5 m behind the cycle, clamped inward at the arena boundary. Keep the bike visible and look slightly down/forward while retaining the horizon. Preserve the existing entry/exit thresholds and wall/zoom recovery.
- [x] All 24 camera tests passed, including wall and corner clearance, zoom transitions and embedded-camera recovery.
## Smooth cycle spectator selection — October 4
- [x] Preserve follow/aerial zoom when arrow keys select a surviving cycle. Replace the camera snap and inter-cycle translation jump with a 1.2-second eased position/look transition toward the moving target; rapid selections restart from the displayed view. Retain wall constraints and close-wall framing.
- [x] Validation: 25 cycle/arena camera tests passed, including rapid reversal, moving target, unchanged zoom and settled framing. Chrome `node tests/cycle-follow-spectator.mjs` passed arrow selection/wraparound, zoom preservation, survivor meters, automatic target replacement and restart.
## Spectator rotation smoothing — October 4
- [x] Interpolate the displayed camera quaternion toward the destination framing over the same eased 1.2-second switch. Keep orientation independent of interpolated look-at points, which can cross the camera and cause a sudden flip. Rapid selections begin at the current displayed orientation.
- [x] All 26 camera tests passed, including bounded frame-to-frame rotation through an opposite-heading switch, eased first frame and rapid target reversal.
## Cycle crash camera hold — October 4
- [x] Hold the displayed camera position and orientation for three seconds after the player's cycle dies, before the existing overview zoom. Hold a followed survivor's crash view for the same duration before automatically selecting another living cycle and using the smooth spectator transition.
- [x] Pause freezes the hold timer; arrow-key selection can skip the hold. Reset clears per-cycle hold history so subsequent games behave consistently.
- [x] Validation: all 27 cycle/arena camera tests passed, including player death, followed survivor death, frozen position/orientation, pause and manual override.
## Four central-beam bosses — October 4
- [x] Increase the light-beam court guard from two boss tanks to four. Seed one start in each angular quarter of the courtyard, preserving minimum spacing, connected-floor checks, existing courtyard patrol behavior, health, paired fire and slower movement.
- [x] Eight boss/spawn tests passed, including all four distinct starting sectors, spacing and 20 seconds of active courtyard patrols.
## Credit preview framing and darker vehicle details — October 4
- [x] Darken the Recognizer's credit-preview faces while preserving its red trim. Fit each model to the available space beside the complete credit text, with outer margins and clearance above the logo; enlarge the carrier and solar sailer previews.
- [x] Ten credit previews passed Chrome/SwiftShader rendering, projected framing bounds, synchronized fade, narrow-window hiding, reset during loading and disposal checks (`node tests/credit-models.mjs --previews-only`). Inspected Recognizer, carrier and solar sailer captures.
- [x] Dim the boss tank's red edge material from `#a32619` to `#76190f` in the shared model adapter.
## Credit vehicle surface fidelity — October 4
- [x] Apply the game's existing cycle surface and wheel-hub repairs to the credit preview instead of displaying the uncorrected export.
- [x] Load the carrier through its game adapter, preserving its hull treatment, panel seams and lights; reduce preview illumination to 15% for the darker in-game appearance. Other previews retain their lighting.
## Shared reference-based Solar Sailer appearance — October 5
- [x] Use the game Solar Sailer adapter in credits so both views share materials and details. Match `docs/references/images/Solar Sailor/SolarSailerRef.jpg` with dark blue hull surfaces, translucent blue-gray sails, pale illuminated rigging, colored navigation lamps and a white radial beam-coupling star. Replace the amber transit beam with a warm-white core and cool halo.
- [x] Preserve each material's base opacity during transit fades so the sail fabric stays translucent at full visibility. Preview framing excludes the hidden source guide beam.
- [x] Validation: two transit/beam timing tests and ten Chrome credit-preview checks passed. Chrome Solar Sailer fixture verified repeated-pass resource stability, reset, beam endpoints/fades, sail opacity, beam-coupling presence and resource disposal. Inspected game and credit renders; final film fidelity remains subject to visual review.
## Solar Sailer full-sail mode and credit beam — October 5
- [x] Add a translucent cruise state and a bright opaque full-sail state based on `Sailor Full.png`. After twelve seconds of cruise, fill the sails over three seconds while smoothly accelerating to four times cruise speed. Integrate the acceleration for continuous position; compute beam fade timing from the shorter transit duration.
- [x] Credits use the same sail transition and include short white beam segments on both sides, with a cool halo and the existing coupling star. Fit the complete ship and beam inside the preview margins.
- [x] Three simulation checks cover cruise speed, full-sail speed, continuous transition distance, exact transit endpoint, repetition and beam fades.
## Hide unused shuttle credit — October 5
- [x] Remove the unused carrier shuttle from the displayed and extended credits and remove its preview mapping. Preserve the model files for possible later use; the separate maze credit remains.
## Screenshot shortcut — October 5
- [x] Change screenshot capture from B/F9 to Shift+B and update the control hint. Preserve modifier exclusions and repeat protection.
## Preserve rapid light-cycle input — October 5
- [x] Replace the single pending keyboard turn with an ordered queue. Preserve two rapid taps delivered before the next simulation step, with a combined two-turn limit across keyboard and race queues to avoid a delayed backlog. Clear queued turns on reset.
- [x] Cycle player and application refactoring tests passed, including rapid opposing taps, queue order/capacity and reset.
- Remaining latency: arena turns are committed only at 4.8-meter cell boundaries (up to 125 ms at 38.4 m/s cruise, 250 ms at half-speed braking), plus frame scheduling. Removing this quantization requires a separate continuous-turn/collision design; this change fixes dropped taps, not the grid delay.
## Immediate arena turns and deliberate AI reactions — October 5
- [x] Remove grid-boundary turn scheduling for all arena cycles. Store continuous positions and apply player turns on the next 120 Hz simulation step; the 4.8-meter occupancy grid remains only a route-planning approximation. Preserve exact fractional trail corners.
- [x] Sweep cycle discs against actual visible trail segments and their end caps; check moving cycle bodies simultaneously. Preserve trail damage, length limits, crash decay, wall breaches and road transitions. Road riders use exact trail geometry once the arena has continuous trails.
- [x] Limit autonomous steering, turbo and brake decisions to one every 0.2 seconds. The player has no AI reaction delay.
- [x] Slow arena turbo acceleration response from 2 to 1.4 per second (about 2.14 seconds to 95% of the requested speed change); retain top speed and brake response. Reduce road turbo acceleration multiplier from 1.6 to 1.3.
- [x] Validation: all 433 simulation tests passed. Additional continuous-turn/road/breach/trail checks passed after road collision integration. Chrome 154 with SwiftShader verified a real keyboard turn at x=0.37, z=0.0966667 grid units: the emitted trail corner exactly matched the position at keydown; inspected rendered capture and no page errors. This verifies removal of grid waiting, not total input-to-display latency on users' hardware.
## CLU killer follow shot — October 5
- [x] Record the attacker ID on fatal enemy cannon hits and Recognizer stomps. If that enemy survives, hold the game view for four seconds before the existing terminal fade. Unknown/dead killers and cycle endings keep their previous timing.
- [x] Ease camera position, orientation and field of view into CLU's normal follow offset around the living killer over two seconds, pointing at the enemy. Follow aircraft altitude; retain wall sweeps and lateral recovery. Pause freezes the shot, reset clears it, and losing the killer holds the last view.
- [x] Unit validation covers fatal-shot/stomp attribution, missing/dead killers, cycle exclusion, camera easing, moving targets, aircraft altitude, pause/reset and wall-safe motion.
## Remove premature cycle match timeout — October 5
- [x] Remove the legacy two-minute match limit and its survivor-count winner selection. Matches continue while both teams have survivors and resolve only on team elimination. Rendering instance capacity is a separate setting, not a gameplay deadline.
- [x] Nineteen cycle/race checks passed, including a regression across the former 120-second boundary and at 300/1800 seconds with unequal survivor counts; eliminating the final opponent still awards the round exactly once.
- [x] Final validation: `npm test` passed all 440 tests and `npm run build` passed. `node tests/clu-killer-view.mjs` passed in headless Chrome on this Mac: the killer remained framed during the four-second hold, the terminal appeared after the existing fade (measured 4.9 seconds), and no page errors occurred. Inspected `test-results/clu-killer-follow.png`. Earlier forced-SwiftShader runs timed out during the typed opening; the normal-renderer run completed.
## Cycle match result orbit — October 5
- [x] Smoothly blend position, rotation and field of view over 2.5 seconds into the closest normal cycle camera distance (10 meters behind, 4 meters high), then continue a slow 0.16 radian/second orbit. Keep the orbit inside arena walls and preserve collision sweeps during the transition.
- [x] Select the surviving player first, then the followed surviving winner, then another winner. Ties use the last crashed cycle's final position. Display GOLD/BLUE TEAM WINS or TIED MATCH! in the upper middle; Enter restarts using the existing match flow.
- [x] Unit checks cover target priority, tie selection, smooth position/orientation, orbit distance, pause and reset.
- [x] Validation: all 442 tests passed before the additional corner-orbit regression, and all three result-camera tests then passed. Production build passed. Headless Chrome on this Mac verified gold, blue and tied banners, a rendered close orbit, and Enter clearing the result on restart, with no page errors; inspected `test-results/cycle-result-0.png`. An initial synthetic crash omitted its coordinates and caused an audio error; the corrected browser fixture passed.
## Darker blue cycle credit preview — October 5
- [x] Give the credit model a deeper blue body and accent color, with preview lighting reduced to 75%. These settings belong to the credits preview adapter.
- [x] Chrome preview checks passed for all nine models, fades, responsive hiding and disposal; visually inspected the darker cycle capture (`test-results/credit-model-cycle.png`).
## Speed-responsive cycle follow camera — October 5
- [x] Ease the following distance in proportion to actual cycle speed: 10 meters at arena cruise, 7.8 at full turbo and 10.8 at half-speed braking. Bound the distance scale to 0.78–1.16 with a 2.5/second response. Road riding uses actual speed relative to its configured cruise limit; spectators use their followed bike's speed.
- [x] Retain existing zoom, horizon framing, wall avoidance, launch shots and result orbit. Camera state resets with the rig. Regression checks cover smooth acceleration/braking response, road speeds and spectator selection; the zoom comparison uses equal relative cruise speeds.
- [x] Final verification: all 445 tests and the production build passed. Chrome measured cruise/turbo/braking camera distances of 10.000/7.800/10.800 meters, with no page errors; inspected the turbo capture. Credit preview and match-result browser checks also passed for this release.
## Stable light-wall connection — October 5
- [x] Fix the curved wall end jitter introduced by continuous arena motion: the bike was interpolated between simulation steps while the emitter used the latest un-interpolated trail endpoint. Carry the visible bike's path lag into presentation-only head trimming, keeping the curved boundary fixed at the rear axle without modifying collision trails.
- [x] Regression checks cover all four headings, braking/cruise/turbo step distances, irregular render fractions and unchanged simulation state. The browser curve fixture also checks rear-axle alignment through interpolated turbo frames.
- [x] All 446 tests and the production build passed. Chrome's rendered curve check passed with no browser errors; inspected `test-results/cycle-wall-curve.png` and verified fixed rear-axle alignment.
## Remove embedded cycle lettering — October 5
- [x] Remove the 29 separately named signature/date meshes from each loaded gold and blue bike before sizing and surface repair. Apply the same filter to the credits preview. Keep source GLBs and author attribution intact.
- [x] Production build, Chrome curve preview and all nine credit-preview checks passed. Inspected the cleaned bike capture; no browser errors reported.
## Random player cycle slot — October 5
- [x] Draw uniformly from the three live gold cycles when entering a player match and on every round reset. Select before outside-arena placement so road starts also use the chosen bike. New browser games receive fresh run seeds; explicit runSeed URLs preserve reproducible comparisons.
- [x] Update player-control fixtures to follow the assigned ID, keeping autonomous teammates distinct from the player. A 60-match seeded regression reaches all three gold slots reproducibly; Chrome verified Enter restarts across 15 matches selected slots 0, 1 and 2 without page errors.
- [x] All 447 simulation tests and the production build passed.
## Longer CLU ending shot — October 5
- [x] Double the killer-camera hold from four to eight seconds and its smooth transition from two to four seconds. Retain the existing terminal fade after the shot.
- [x] All 447 tests and the production build passed. Chrome verified the game view still showing at 6.8 seconds after death and the terminal appearing after the hold/fade (8.85 seconds measured from the captured death frame), with no page errors.
## Level CLU shots against tanks — October 5
- [x] Keep assisted tank-target aim at the muzzle's height, including its intercept calculation. Apply vertical shot spread only to recognizer elevation assistance; preserve horizontal assisted spread and exact manually aimed turret shots.
- [x] Add repeated-shot checks against a ground tank in both follow and turret views, asserting zero vertical projectile velocity. Existing recognizer spread and manual-elevation checks remain active.
- [x] All 448 tests and the production build passed.
## Game-lit cycle credit preview — October 5
- [x] Share the game's cool hemisphere/key/fill lights and ACES exposure with the light-cycle credit shot. Share the bike's roughness, metalness and reduced ambient response so the preview has the same directional highlights and dark shaded surfaces. Retain the requested darker blue preview colors.
- [x] Other credit models retain their existing lighting and tone mapping; switch the lighting group with the active credit.
- [x] Production build passed. Chrome rendered all nine credit models and passed fade/resizing/disposal checks; inspected the updated cycle credit capture. The in-game cycle camera/rendering browser check also passed without page errors.
## Shallow maze wall damage — October 5
- [x] Tank bullet impacts cut angular, recessed cavities into upright maze slab faces. Nearby repeated hits enlarge/deepen the same cavity; faceted dark interiors give depth, crossed seam lines are clipped, and a small burst of solid fragments flies outward and falls. Damage lasts for the current game and resets with the view/new game.
- [x] Keep solid collision and navigation prisms unchanged. Protect wall edges, floor contacts and roofs; cap cavities at 96, simultaneous fragments at 84, and fragment lifetime at 2.8 seconds. This is shallow slab damage, not passable breaches or general destruction of decorative ledges.
- [x] Reuse immutable wall data and cached caster bins. Only damaged faces need new projected shadow polygons; reset restores the original mesh/shadow resources. On this Mac, a three-hit CPU check measured wall rebuilds at 18.1/5.4/4.6 ms and shadow updates at 10.8/3.2/3.1 ms (before optimization, full shadow rebuilds were about 98 ms). These are local CPU measurements, not a hardware-wide performance guarantee.
- [x] All 451 tests passed, including recessed geometry, repeated impacts, edge/debris bounds, pause, expiry and reset. Chrome verified real Space-key shots growing one cavity, fragment expiry and new-game cleanup; inspected impact and settled captures. Existing maze-shadow browser checks passed, including unchanged overlapping-shadow coverage. JEV 503 responses are deliberately stubbed in the browser fixture and excluded from renderer errors.
- [x] Production build passed.
## Blockier wall damage and releasable CLU turbo — October 6
- [x] Replace radial crater outlines with uneven rectangular breaks and individually chipped corners. Offset and vary the recessed back face; eject block-shaped fragments.
- [x] Releasing the last held forward key (W or Up) stops CLU's active turbo, returns unused charge to the reserve, and uses the existing smooth deceleration to the normal speed limit. Cruise-throttle selection remains intact.
- [x] Targeted damage/turbo simulation checks passed, including charge preservation and smooth return to cruise.
- [x] All 452 tests and the production build passed. Chrome verified repeated real shots, the blockier rendered recess, fragment expiry/reset, and W release cancelling turbo with reserve remaining; inspected the updated settled capture.
## Slightly tighter recognizer auto aim — October 6
- [x] Reduce CLU's recognizer auto-aim spread by 20% on both axes (maximum yaw ±1.92°, pitch ±0.96°). Keep tank-target horizontal spread and exact manual aiming unchanged.
- [x] All 31 cannon/gunner tests and the production build passed.
## Jagged hallway-wall fractures — October 6
- [x] Follow the user's oblique hallway reference with staggered angular ledges and concave notches. Vary recessed vertex depths, darken the back faces, and brighten exposed fracture shelves. Initial depth is 1.05 m, bounded by cavity radius and the existing maximum.
- [x] Triangulate concave back faces and clip decorative seams at every polygon crossing. Preserve solid gameplay collision and bounded, resettable damage resources.
- [x] Four geometry/lifecycle tests passed, including a raycast sweep checking that concave breaks remain sealed. Chrome verified real shots, growth, debris expiry/reset and turbo release; inspected the game capture and a close oblique geometry render. Production build passed.
## Geometric prism wall cuts — October 6
- [x] Refine damage from the second supplied film reference: overlapping rotated squares and elongated hexagons cut straight into the wall, with independently chosen flat depths. Replace the continuous stepped/tapered outline with three bounded convex prism cuts per impact cluster.
- [x] Subtract deeper cuts from shallower floors and expose only surviving side-wall intervals. Skip disjoint polygon subdivisions; preserve radius/depth limits, collision, shadow refresh, seam clipping and reset lifecycle.
- [x] Four damage tests passed, including raycasts across overlapping cuts verifying the deepest floor is exposed without unintended holes. Chrome real-shot/growth/expiry/reset checks passed. Inspected the close oblique render; production build passed.
- [x] Release verification: all 453 simulation/geometry tests passed before deploying the accumulated wall-damage, turbo-release and recognizer-spread changes.
## Variable wall-damage size — October 6
- [x] Assign each new damage cluster a reproducible uniform 1×–3× footprint scale (2× average), retained for subsequent growth and its maximum radius. Keep the existing depth limit and face-edge/neighbor clearance, which can constrain the footprint near boundaries.
- [x] Five damage tests passed, including sampled range/mean and stable growth scaling; production build passed. No browser opened.
## Gold-team result text — October 6
- [x] Show GOLD TEAM WINS in gold (#ffd45c); clear the winner class for blue wins, ties and other game states. Production build passed.
## Arcade victory palette cycling — October 6
- [x] Cycle winner text through five discrete warm gold/orange/coral colors for gold and cool cyan/blue/violet colors for blue, repeating every 1.5 seconds with a matching subtle glow. Ties retain their neutral appearance. Reduced-motion preferences use steady team colors.
## Restore steady victory text — October 6
- [x] Revert victory palette cycling and its glow at the user's request. Gold wins retain steady gold (#ffd45c); blue wins and ties return to the original blue-gray text.
## Smoother cycle self-shadows — October 6
- [x] Increase each bike's shadow tile from 512 to 1024 pixels and use the existing 36-tap Gaussian edge filter instead of four-tap bilinear filtering. Keep shadow strength and depth bias unchanged; expose the cycle-specific settings in CYCLE_SELF_SHADOW.
- [x] Headless Chrome rendered the actual gold model with game lighting before/after the change, with no WebGL/shader errors. Inspected close-up captures: fine jagged bands along the curved body and wheel covers are substantially smoother. Added a reproducible isolated model-render check. Broader hardware performance remains unmeasured; the atlas and filtering now cost more GPU memory/work.
## Reversible rear-wheel refinement — October 6
- [x] Preserve byte-identical copies of the gold, blue and red source GLBs and the pre-refinement surface adapter in docs/models/archive/pre-wheel-refinement. Original runtime GLBs also remain unchanged.
- [x] First experiment replaces only the separate rear tire ring with a smooth revolved surface using its measured axial/radial profile. Keep original hubs, spokes, front wheel, body, materials and proportions. Apply the same adapter in game and credits; CYCLE_REAR_WHEEL.enabled and the loader's refineWheels option allow direct comparison/reversion.
- [x] Inspect same-camera original/refined Chrome renders: rear ring circumference and opening are smoother. Keep the scope at one component for user review before extending to other parts.
## Smooth inner wheel liners — October 6
- [x] Replace the quantized outer band of the dark front/rear wheel inserts with smooth 128-segment open cylindrical liners. Preserve the inset faces, spokes and hubs; align their outer boundary to the measured circular opening. Apply in both game and credits, leaving archived originals untouched.
- [x] Headless Chrome checked both team models, confirmed overall bounds stay within 1 cm of originals, and rendered without shader errors. Inspected the close-up to retain the visible spokes and openings.
## Four-team cycle matches and team reactions — October 6
- [x] First player match per app session remains gold versus blue, three bikes each. Each subsequent restart or arena entry draws 2–4 teams and 1–3 bikes per team from the seeded RNG. Gold remains the player team with a random occupied slot. Returning home preserves the match counter; a page reload starts the sequence over.
- [x] Add red on the west wall and green on the east wall, facing inward. Gold/blue retain their opposite starting walls. All four sides start connected to their nearest wall; equal rosters cover all nine size combinations.
- [x] Expand models, trails, live tire traces and explosions to twelve bikes/four teams. Recolor cloned materials on roster changes, hide unused bike slots, recognize every surviving team in victory detection, and name red/green winners correctly. Keep the six-tile high-resolution shadow budget by selecting the nearest visible bikes.
- [x] AI reaction intervals: gold/blue 0.20 s, red 0.14 s, green 0.30 s. Player inputs remain immediate.
- [x] All 458 tests passed, including roster variation, four-sided trail starts, session continuity, red/green wins and reaction timing. Headless Chrome verified all four-team roster sizes and new winner labels without WebGL errors. Inspected an isolated four-color model render; production build passed. Browser not opened interactively.
## Straight rear spoke and smooth silver liner — October 6
- [x] Replace the remaining quantized rear silver spoke with a tapered straight-edged mesh and smooth circular liner. Give its side faces a small clearance over the retained dark inset to avoid the inset masking its edge. Preserve archived source models and overall bike bounds.
- [x] Headless Chrome validated both team-source adapters and rendered the new spoke without shader errors; inspected the close-up.
## Immediate home-menu access tone — October 6
- [x] Schedule CLU/cycle access feedback directly after audio unlock, before simulation and view reset, instead of at the end of startup. Permit the access oscillator to queue during context resume; ordinary terminal typing still waits for a running context. Request interactive audio latency. Hardware/Bluetooth output latency remains outside this scheduling fix.
## Stable side panels and black reflective wheel rims — October 6
- [x] Pin the authored flat side-panel vertices during surface repair so smoothing cannot pull the shared diagonal below the window out of plane. The diagonal line also appeared without self-shadowing; it is no longer visible in the corrected close-up.
- [x] Separate the front wheel rim faces from the team-painted shell and derive smooth normals from welded geometry. Give both rims independent near-black standard materials (roughness 0.24, metalness 0.45), preserving their finish in game/credits lighting and team recoloring. Highlights are produced by scene lights; source GLBs and archived copies remain unchanged.
## Remove overlapping gray cycle-panel faces — October 6
- [x] Source inspection found partially overlapping coplanar triangles in the gray side plates, including overlaps between FrontColor and _LightGray_1. Keep those plates planar during smoothing and subtract already-covered areas across their meshes; retain unique surface area and interpolate original attributes. Apply the repair in game and credits without modifying source GLBs.
- [x] Geometry regression confirms single ray intersections in overlapping and unique regions, unchanged union area and repeat-repair stability. Headless Chrome rendered both source-team adapters without WebGL errors; inspected the repaired close-up. Production build passed.
## Broader cycle surface overlap repair — October 6
- [x] Extend coplanar face subtraction beyond the gray side plates to all opaque authored cycle meshes, preserving unique coverage, winding, interpolated attributes, and separate parallel layers. Compare transformed planes so mesh placement is respected.
- [x] Split the black rear wheel liner into end bands around the silver liner instead of rendering both at the same radius over the same span. Retain the black reflective outer rim finish and original GLB backups.
- [x] Validate partial-overlap coverage and rotated/separated layers with geometry tests. Headless Chrome renders the game-lit model from four angles without WebGL errors; captures are in `test-results/cycle-surface-{front,left,rear}.png` and `cycle-self-shadow-smooth.png`. User play review of remaining shimmer is still needed; this does not claim to remove every shadow/silhouette aliasing artifact.
## Correct wheel finish placement — October 6
- [x] Restore team paint on outer wheel shells in the game and credits. Apply the black reflective finish only to inner wheel inserts and inward-facing rim surfaces, replacing light-colored inner liner highlights while preserving silver hubs and spokes. Reflections now come from scene lighting.
- [x] Check gold/blue outer-wheel material preservation and render four views in headless Chrome. Original source GLBs remain unchanged.
## Seal rear cycle body seams — October 7
- [x] Rebuild both narrow rear body returns between the original upper and lower boundary rows, preserving the colored shell outline and preventing the underbody showing through mismatched triangles. Keep those boundary vertices fixed during smoothing; use the same repair in game and credits for both source-team models.
- [x] Correct winding and normals on planar gray plates on both sides. A shadow-disabled render isolated the rear diagonal from shadow aliasing; reversed sliver faces were the cause. The corrected multi-angle close-ups no longer show that diagonal. Preserve separate curved parts, wheel colors and source GLBs.
- [x] Add regression checks for outward gray plate winding, both rear returns, and sampled surface coverage along the repaired strips. Inspect headless Chrome game-lit captures from front, rear and both sides. Remaining quantization on other curved source details is not claimed eliminated.
## Remove hub-to-tire highlight strips — October 7
- [x] User-marked gray radial strips are fake reflections, not wheel spokes. Remove the front highlight faces and rear spoke replacement in both game and credits. Add smooth black wheel faces with a small clearance over the authored inserts so the removed strips cannot reveal holes or underlying paint. Preserve silver hubs and team-colored outer shells.
- [x] Headless Chrome model checks and four-angle captures confirm the gray radial strips are gone; original GLBs remain preserved.
## Polished black inner wheel faces — October 7
- [x] Give smooth inner wheel faces and liners a physical black finish with roughness 0.08, metalness 0.5 and a smooth clearcoat (roughness 0.04). Preserve team-painted outer tires and silver hubs. Highlights are evaluated from scene lighting, without fixed gray highlight strips or added reflection graphics.
- [x] Verify physical rim material values on both source models and render from four angles in headless Chrome without shader errors.
## Unsupported mobile terminal — October 7
- [x] Phones and tablets show “MOBILE TERMINALS NOT SUPPORTED” in the existing terminal style. Detect mobile browser identities, including iPads using desktop identity, rather than small window width or touchscreen presence alone.
- [x] Stop before loading/constructing the game or registering its offline cache on unsupported devices. Keep the message readable in portrait and landscape; desktop startup and menus remain available.
- [x] Validation: `node tests/mobile-terminal.mjs` passed in headless Chrome with iPhone 13, iPad Pro 11 and Pixel 7 emulation; none loaded game code or GLBs. A 600px desktop viewport retained the normal menu. Production build passed.
## Prepare Light Cycles before the menu — October 7
- [x] Move arena download, GLB decoding, cycle surface repair and arena attachment into the initial loading terminal before enabling program selection. Previously this work began on Return, even when the asset bytes were cached. Reuse the existing arena promise across game starts.
- [x] Preserve the mobile early exit and loading-error handling. This shifts preparation into initial loading rather than claiming to eliminate its CPU cost.
## Preload all program resources — October 7
- [x] Prepare and decode CLU/cycle sound samples before the menu; warm all selected soundtrack files and the Solar Sailer alongside arena preparation. Audio playback still resumes from the user's gesture.
- [x] Fetch BIT's document, local scripts, four OBJ models, yes/no WAV samples, font and logo into the browser HTTP cache without running BIT, opening its microphone or sending a JEV request. BIT still initializes its own page and audio context when selected.
- [x] Headless Chrome confirms arena, audio/BIT resources and Solar Sailer finish before the home menu, and cycle selection causes no GLB downloads. Desktop initial loading carries the preparation cost; mobile still exits before loading the game.
## Smooth front cycle wheel shell — October 7
- [x] Preserve the current surface/seam adapters in `docs/models/archive/pre-front-wheel-refinement/` before changing the front wheel. Source GLBs remain untouched; the new front wheel adapter has an explicit enable switch.
- [x] Replace the front wheel's faceted colored shell with a symmetric revolved surface derived from its measured axial/radial profile, using 128 radial segments and 64 profile segments. Retain the body attachment geometry, team paint and polished black inner surfaces; use the same adapter in game and credits.
- [x] Headless Chrome checks both source-team models, original versus refined bounds (within 1 cm), preserved rear seam coverage and rim materials, and renders front/rear/side views plus `test-results/cycle-wheel-wireframe.png`. The new wheel has regular curved topology; window-border geometry remains a separate follow-up.
## Home selection fade — October 7
- [x] Fade unselected menu text and logo for 825 ms, then immediately fade the selected label over the opening scene together with the dark background for 1100 ms. Both phases are scheduled before synchronous game setup so setup cannot postpone the selected-label animation. BIT preserves the original fade deadline across navigation. Home selections replace the intermediate access typing screen; non-home restart paths remain intact. BIT carries its label across document navigation. Reduced-motion users skip the fades.
- [x] Headless desktop Chrome: `node tests/home-transition.mjs` passed for CLU, Light Cycles, Credits and BIT; checks animation ordering/durations and destination. `npm run build` passed. No deployment performed.
## Distance-sensitive arena turn tracking — October 7
- [x] Ease cycle heading independently of camera translation, zoom and deliberate glances. Keep the existing quick tracking within 12 m; progressively ease distant turns toward a 1.2/second heading response at 140 m. Use actual camera distance so wall-shortened views respond quickly. Applies to player and spectator follow views; road cycling, openings and survivor-switch transitions retain their existing behavior.
- [x] `node --test tests/cycle-camera.test.js tests/camera-collision.test.js` passed, including near/middle/far turn comparisons and eventual heading convergence for player and spectator views. Visual driving feel remains subject to user review.
## CLU turret contact and wall ramming — October 7
- [x] Add measured pivot-to-muzzle collision probes and bounded sweeps for hull movement, hull yaw and turret rotation. Stop the barrel at solid maze faces while retaining safe tangential sliding and withdrawal. Collision geometry remains separate from the model.
- [x] Emit the existing chipped-wall/debris effect for hull or turret impacts above 30 m/s normal contact speed, with a 0.6 s per-tank damage cooldown. A muzzle impact above 40 m/s and sufficiently aligned into the face embeds the barrel 0.45 m and locks the tank/turret. Hold reverse for 8 s to extract gradually; show a HUD recovery hint. Restart, destruction and teleport clear the jam.
- [x] Damage remains a shallow visual cavity; this does not create traversable maze breaches. Thresholds, probe spacing, embedding and recovery values live in `TURRET_CONTACT`.
- [x] Headless desktop Chrome: `node tests/turret-wall.mjs` passed a real maze-wall ram, damage creation, stationary lodged state, HUD notice and reverse extraction. Captures: `test-results/turret-lodged.png`, `test-results/turret-freed.png`. Inspected the lodged render. Focused simulation checks cover slow contact, rotation sweeps, damage cooldown, hard lodging, extraction, reset and hull damage.
## Stable carrier shadows and darker maze shading — October 7
- [x] Supply explicit flat wall receiver normals, including rebuilt damage geometry, instead of screen derivatives. Treat near-parallel light angles consistently and keep the receiver varying distinct from the shader's local normal variable. This avoids alternating triangular moving-shadow patches on grazing maze faces.
- [x] Suppress static shadow casting between coplanar neighboring wall faces with different slab IDs, accounting for float32 rounding; reject degenerate light-parallel caster triangles.
- [x] Increase maze shadow attenuation from 40% to 60%, and carrier attenuation from 50% to 65%. Retain filtered carrier edges, static geometric shadow unions, and readable ground grid lines.
- [x] Headless Chrome: carrier shadow edge/ray comparison, moving-shadow occlusion, grazing-angle receiver and maze grid tests passed. Grazing fixtures at 0 and 7000 m have zero spurious dark pixels; light-facing fixtures still receive the carrier shadow. Damage-geometry tests passed. Original screenshot location remains subject to user confirmation.
## CLU hull corner collision — October 7
- [x] Measure the imported non-turret mesh: 6.5 m width and 9.373 m length. Replace CLU's circular wall contact with an oriented rectangular footprint enclosing those bounds with a small clearance. Sweep translation and hull rotation, retain tangent sliding/reverse, and route hard hull contacts through the wall-damage mechanic. Enemy hull behavior remains unchanged.
- [x] `node tests/tank-hull.mjs` confirms every measured hull vertex is enclosed. Unit checks cover previously missed corners, multiple headings, large turbo displacements, rotation contact and sliding. `npm test`: all 473 checks passed.
## Combined carrier/maze shadows and menu cursor — October 7
- [x] Compose carrier shadows with the exact static maze wall/floor polygons using minimum light visibility. Apply a compensating multiplier on the static overlays instead of multiplying two shadow attenuations or cutting the carrier coverage with the coarse maze depth atlas on wall faces. Preserve receiver-plane filtering and vehicle occlusion.
- [x] Headless Chrome: `tests/carrier-shadow-union.mjs` covers 18,635 static-shadow pixels under a carrier shadow with zero incorrect overlap pixels. Carrier edge/ray comparisons, maze/grid rendering and all four menu transitions pass without shader errors. Original gameplay location remains subject to user confirmation.
- [x] Fade the selection cursor in the first 825 ms stage with the other menu text. Retain its layout space but carry only the selected program name into the 1100 ms scene fade, including BIT navigation.
## Immediate mobile unsupported terminal — October 8
- [x] Detect mobile devices in the initial HTML and show a static, inline-styled unsupported-terminal message immediately. Desktop-only styles load through the desktop bootstrap; the confirmation script is deferred. The warning uses a fallback font immediately and does not wait for JavaScript modules, CSS downloads or the terminal font.
- [x] Headless Chrome device profiles: iPhone, iPad and Android show the warning without loading game models; a narrow desktop still reaches the menu. The warning also remains visible with every external script, stylesheet and font request blocked. Production HTML retains the inline detector/styles and has no render-blocking game stylesheet.
## CLU cannon sound — October 8
- [x] Use the supplied `docs/references/sounds/clue fire.mp3` for player cannon fire at its original pitch and existing shot volume. Decode it during the normal startup preload, reuse the buffer for subsequent shots, and include the hashed asset in the PWA cache. Enemy cannons retain their existing sample; the old sample remains a fallback if the new file cannot decode.
- Validation: headless Chrome decoded the stereo clip (0.532 seconds after MP3 padding), and two player shots reused that buffer without audio-load errors. `npm run build` passed and emitted the hashed MP3 into the offline cache. Listening balance remains subject to play review.
## Restore terminal/game crossfade — October 8
- [x] Hold the shared selected-label/background fade until the first visible game frame has rendered. Simulation reset and shader preparation can no longer consume the fade while the canvas is hidden, which previously produced a black interval and sudden scene appearance. Preserve the 825 ms other-text fade and full 1.1-second scene dissolve. CLU and cycles use scene readiness; credits and BIT retain their document transitions.
- Validation: `node tests/home-transition.mjs` passed for all four menu choices, including a partially faded curtain over a visible game canvas for CLU and cycles. Inspected `test-results/clu-terminal-crossfade.png`: CLU label remains visible over the emerging maze.
## Menu confirmation blinks and arena launch clearance — October 8
- [x] Blink the selected home-menu label off/on twice for 77 ms per phase near the end of its 1.1-second dissolve. Background fading remains continuous; game blinks share the first-frame readiness gate. BIT carries the blink frames through its document transition, and reduced-motion mode skips them.
- [x] Place each cycle rear and initial light-wall endpoint exactly two cycle lengths (7.2 m) inside its starting arena wall, for all team counts and directions. Remove start-position grid rounding and update the materialization camera to the same formation location.
- Validation: all 474 simulation tests passed, including all nine team/roster arrangements and updated entrance-camera endpoint checks.
- Browser validation: `tests/home-transition.mjs` passed all four programs and the exact on/off/on/off/on blink sequence; `tests/cycle-opening.mjs` passed the full entrance, materialization, fixed launch shot, pause/resume, and camera handoff. Inspected the updated formation capture. Production build passed.
## Overlapping menu/game fade and visible confirmation — October 9
- [x] Begin preparing CLU/cycles immediately after selection, preserve copies of the home text above the scene, and start the background dissolve alongside the unselected-text fade after the first rendered frame. Keep the selected label on an independent layer so its blinks are not dimmed by a parent opacity.
- [x] Use three off/on blinks at a 30% faster rate (99/1.3 ms per phase), followed by a 140 ms final text fade. BIT carries the separate background and label timelines across document navigation; reduced-motion mode skips the animation.
- Validation: `node tests/home-transition.mjs` passed for CLU, cycles, credits, and BIT, checking the three on/off sequences and simultaneous partial background/text opacity over the visible game canvas. Inspected `test-results/clu-terminal-crossfade.png`. `npm run build` and `git diff --check` passed.
## Immediate menu feedback — October 9
- [x] Start the unselected-text and cursor fades on selection, independently of scene readiness, with a linear onset. Allow the browser to submit these animations before synchronous game setup. When the game frame is ready, consume any elapsed first-phase time rather than replaying the text fade; preserve three bright final label blinks and the background dissolve.
- Validation: the browser regression checks text opacity falls while scene readiness is deliberately withheld, then exercises CLU, cycles, credits, and BIT transitions.
## Synchronized immediate menu reveal — October 9
- [x] Reveal the existing rendered game canvas beneath the home screen and fade its black covering layer with the same 825 ms linear timing as the unselected text. These two animations start immediately; only the selected label waits for game readiness before its final three blinks. BIT navigation starts after the background fade completes.
- [x] Record improving outside-arena cycle driving dynamics in `TODO.md`.
## CLU opening camera during menu dissolve — October 9
- [x] Start CLU's prepared opening before the first dissolve frame, reuse the fresh home-screen simulation instead of rebuilding it on Return, and warm the opening's rendering during loading. Generate fresh random patrol seeds during initial loading and return to the menu; explicit comparison seeds remain reproducible.
- Validation: `node tests/home-transition.mjs` passed in headless desktop Chrome at 1280×800 for CLU, light cycles, credits, and BIT. The regression now records camera positions during partial background opacity and verifies that CLU's opening camera moves while the game fades in, in addition to checking the three selected-label blinks.
## Light-cycle menu scene isolation — October 9
- [x] Keep the background cover opaque when selecting light cycles until the first arena frame has rendered, then dissolve it and the unselected text together over 825 ms. Reuse the fresh home simulation and begin cycle setup without an extra frame delay. The selected label retains its three blinks. This prevents the home screen's CLU preview from appearing before the arena while keeping the scene reveal synchronized with the text.
- Validation: headless Chrome regression covers delayed scene readiness, checks matching text/background opacity across multiple arena reveal frames, and checks that every revealed frame during light-cycle entry belongs to the cycle game; CLU's moving-camera fade and the credits/BIT transitions also pass.
## MCP conversation terminal — October 9
- [x] Add MCP to the home menu, with a separate conversation page showing the supplied film image. Animate mouth/jaw and attached wireframe during speech, respect reduced motion, and provide typed input alongside speech recognition and browser TTS.
- [x] Add bounded conversational history and a server-only, OpenAI-compatible chat relay for Vite and the production Worker. Preserve origin checks and Worker budget checks. Leave provider configuration explicit; JEV cannot generate free-form dialogue.
- [ ] Configure the user's chosen chat provider and verify a real microphone → model → spoken reply session. The current browser checks mock these external interfaces; voice character and animation fidelity remain subject to user review.
- Validation: `node --test tests/mcp.test.js tests/worker.test.js` passed (10 tests). `node tests/mcp.mjs` passed the mocked speech/chat loop, history, stop, exit and unavailable-service recovery. Inspected the 1280×800 headless Chrome speaking capture against the supplied still and video frames. No deployment performed.
## MCP voice cloning and deferred model cache — October 9
- [x] Extract a reproducible MCP-only reference from the user's film clip and generate new dialogue using pinned Chatterbox weights. Add a saved audition and live voice test independent of chat-provider configuration.
- [x] Begin the model download only when entering MCP. Run inference in a WebGPU worker and cache model files atomically in bounded records in IndexedDB, separate from game/PWA updates. Stop/exit cancels worker activity and playback. Drive jaw animation from synthesized audio amplitude.
- IndexedDB validation: `node tests/mcp-voice.mjs --local-models` passed in headless Chrome on macOS with WebGPU. Stored 14 files (1,526,346,349 bytes), generated and played new speech, then reloaded successfully with no model-weight downloads (two optional missing-file probes remain). A local fixture supplied the pinned weights to isolate persistence from remote transfer failures. Relay unit tests passed 10 tests; production build passed.
- Validation: native CPU inference generated a 5.68-second sample in 12.36 seconds; local Whisper transcription recovered the intended sentence. Initial headless Chrome WebGPU generation took 6.61 seconds to playback and completed successfully. Clone quality still needs the user's listening judgment; full dialogue still needs a configured chat provider.
## MCP chat provider — October 10
- [x] Select Gemini 3.8 Flash as the default MCP conversation provider, using Google's OpenAI-compatible endpoint in both local and Worker relays. Keep the API key server-only; local status stays unconfigured until a key is supplied. Explicit endpoint/model overrides remain supported.
- [ ] Supply the Gemini API key and verify a live conversation.
- Gemini credential storage: `credentials/Gemini.txt` uses the existing git-crypt filter and an explicit ignore exception. Local MCP reads the unlocked file; public checkouts with encrypted content remain unconfigured. Production uses the Cloudflare `MCP_API_KEY` secret, never the static bundle. The authorized local key is installed. A real Gemini reply through the local relay returned HTTP 200 in 2.73 seconds; production secret upload remains pending.
## Light cycle match roster — October 10
- [x] Show a top-center row of top-down renders of the repaired game cycle model, facing upward and grouped by team, using each team's color; retain eliminated cycles at 20% opacity.
- [x] Rebuild the roster for each match and keep the controls/death prompt below it. Hide the roster outside cycle play and during the opening camera sequence.
- [x] Chrome (headless, macOS ARM64): `node tests/cycle-roster.mjs` passed for a 12-cycle/four-team match, elimination dimming, six-cycle restart, and hiding. Inspected the captured icon layout.
## MCP loading and voice comparison — October 10
- [x] Display a combined download meter and ETA with the active component name. Distinguish transfer completion from GPU initialization and voice conditioning. Keep the saved audition and typed chat usable while loading.
- [x] Preserve a static face and startup error message if the renderer/module fails. Restart the local Vite server with the MCP relay. Chrome UI tests passed; the user's original blank-screen report was not reproduced.
- [x] Cache large voice files as separate, bounded IndexedDB records in an atomic transaction. Avoid unreadable large Blob entries and IndexedDB's record-size limit. Pin model architecture and quantization metadata to remove optional generic-model 404 probes. Host the inference runtime locally.
- [x] `node tests/mcp-voice.mjs --local-models`: Chrome/WebGPU generated speech in 6.27 seconds after initialization and reloaded all 13 cached files (1,526,293,292 bytes) with no model downloads. `node tests/mcp.mjs` passed loading, interaction, cache and startup fallback checks. MCP download/relay/Worker unit tests passed 14 tests.
- [x] Produce a same-dialogue audition at `/mcp-voice-comparison.html`: original film excerpt, dry clone, measured pitch/reverb match, and a deeper alternative. Measurements guide the candidates; final perceptual matching remains subject to listening review.
## Cycle roster circles — October 10
- Replace the rendered cycle thumbnails with filled team-colored circles. Eliminated cycles keep a full-color outline and have a transparent interior. Connect adjacent teammates with matching lines spanning only the gaps; no line crosses a hollow circle or joins different teams.
- Move the controls/death prompt up beneath the shorter roster. Keep the existing model thumbnails available for future use.
## Adaptive antialiasing and stable quality — October 10
- [x] Query supported HDR/depth-stencil MSAA sample counts, retain SMAA, and adapt MSAA plus render pixel ratio toward a default 60 FPS target. Bound resolution to 2× and six million pixels. Development controls expose target FPS and adaptation enablement.
- [x] Observe sustained frame pacing, exclude inactive/opening frames, and allow a settling period after changes. Increase only after 24 seconds of stable windows. A failed level lowers a session-long ceiling, preventing repeated up/down cycling; page reload starts a fresh evaluation.
- [x] Five controller tests passed for capability limits, isolated stalls, inactive frames, upgrades, backoff, no retry of rejected levels, and the quality floor. Chrome renderer test switched from 4× MSAA at 1× resolution to SMAA at 0.85×, retained 27 textures, and reported no JS errors. Performance targets remain hardware/workload dependent; no claim of 60 FPS on every device.
## MCP cache clarity and transient chat recovery — October 10
- Distinguish downloading, saving each IndexedDB file, and GPU initialization. A cache-only launch explicitly reports no download. Hiding the page preserves pending voice loading; Stop/exit still cancels it. GPU initialization remains necessary after reopening.
- Retry temporary provider errors/timeouts at most twice, with 0.5/1-second backoff and a 12-second per-attempt deadline inside the existing 45-second overall deadline. Authentication, model-access and quota errors do not retry. Explicit cancellation remains immediate.
- MCP navigation keeps the home preview covered rather than flashing CLU before the separate page loads. Voice status now says “INITIALIZING VOICE FROM SAVED FILES” and distinguishes speech-engine startup from transfer. Chrome home-transition and MCP interface checks passed.
- Gemini's compatible endpoint continued to time out from the deployed relay despite successful direct tests. Added native generateContent support while preserving custom compatible providers. Native request/response mapping, thought exclusion, bounded retries and cancellation are covered by tests.
## MCP cloned voice only — October 11
- [x] Remove browser/native text-to-speech entirely from MCP. If the clone is loading, unavailable, or fails generation, preserve the reply as text and show an explicit cloned-voice message. Keep the saved cloned audition.
- [x] Chrome `node tests/mcp.mjs` verifies failed voice loading and generation never call browser speech synthesis; conversation history, Stop, exit, and cache/loading UI still pass.
## MCP input waits for cloned voice — October 11
- Disable the message field, Send and microphone until cloned voice initialization succeeds. Enforce the same readiness guard on submitted text and recognition callbacks. A failed/cancelled load leaves conversation input disabled and exposes the voice retry control.
- Resume microphone listening only after voice readiness; unlock Web Audio during keyboard/pointer interaction before asynchronous chat and inference finish. No queued replies or alternate TTS voice.
- Chrome `node tests/mcp.mjs` passed: input disabled during loading, enabled on ready, cloned-generation errors remain explicit, and browser speech synthesis is never called.
## MCP connection refresh and simplified terminal — October 11
- Reproduced an immediate local Gemini 503 while identical direct requests and a four-turn deployed conversation succeeded. Restarted the stale October 10 Vite process; four consecutive local and four deployed replies then succeeded (about 1–4 seconds each). The local relay now reads the credential file for each request, so key replacement takes effect without restarting. Gemini 3.8 Flash remains the model; no fallback model added.
- Remove the top heading/home button and fill the top/width with the cropped MCP face. Put MIC, STOP, LLM and TTS indicators directly beneath it; keep progress/errors with those controls, outside the dialogue. Return submits the input without a Send button. Conversation contains only the latest user and MCP messages; Escape still opens home confirmation.
- Verification: 11 MCP/cache unit tests, Chrome `node tests/mcp.mjs` (voice gating, Return submission, history, Stop/exit), inspected 1280×800 rendered capture, and production build passed. Live multi-turn probes verify chat only; cloned speech generation was covered by the earlier dedicated voice test, not repeated here.
## MCP warm voice session, live effects and film terminal — October 11
- [x] Instrument startup phases and cumulative IndexedDB read time/bytes. Real Chrome/WebGPU with local model fixtures measured cached manifest checks at 2 ms, runtime read 31 ms, tokenizer 2 ms, model loading/GPU sessions 53.20 s, reference conditioning 408 ms. A fresh fixture load measured model/GPU setup 50.27 s. Cache-read measurements overlap the model phase; they are not an additional phase. Cached reload used zero model downloads. Full reload still pays initialization cost.
- [x] Keep MCP in a retained same-origin frame when opened from the home menu. Leaving stops audio/microphone/chat, pauses the face render, and hides the frame; returning reuses the initialized worker. Suspend the underlying game render while MCP is visible. Pending inference finishes silently before accepting another request, instead of destroying the model. Remove STOP; Escape confirmation returns home. Direct standalone MCP page navigation/full reload still tears down its worker.
- [x] Check cache metadata without opening/prefetching large model streams during the manifest pass.
- [x] Fix live speech bypassing the approved audition treatment: lower live playback pitch to 0.92× and apply a 0.9 s room tail/early reflections at 1.25 wet gain. This lightweight pitch shift also slows speech by 8%; it is not the audition's duration-preserving offline processor. The preprocessed sample bypasses effects, and the face follows adjusted playback timing. Listening comparison remains for the user.
- [x] Create a local original MCP dot-matrix font from geometric glyphs, inspired by the supplied film terminal frame. Make MCP text uppercase with one font/size and only normal/dim blue text, including disabled controls, placeholders and the shared exit dialog. Other games keep their fonts.
- Validation: real Chrome voice generation/playback and cached reload passed (`node tests/mcp-voice.mjs --local-models`, first speech ready in 4.48 s). Twelve MCP/cache unit checks passed. Browser lifecycle, typography, retained-worker, reverb-tail and Escape checks recorded in `tests/mcp.mjs`; inspected its 1280×800 font capture. Production build passed with existing asset/chunk advisories.
## MCP short-sentence speech queue — October 11
- Ask Gemini for one or two short, complete sentences (32 words total maximum; first sentence at most eight words). Keep the full reply visible while speaking it sentence by sentence.
- Split speech at sentence boundaries, with a 120-character/16-word fallback for long sentences. Generate only one sentence ahead while playing the current sentence; preserve playback order, cloned-voice effects, and the existing reverb tail.
- Escape discards buffered audio and lets in-flight generation settle silently before unlocking input. Handle speculative-generation errors immediately to avoid unhandled promise rejections, then report them after current audio finishes.
- Validation: 16 MCP/chat/cache/speech unit checks passed, covering sentence boundaries, bounded prefetch, ordered playback, cancellation/draining, and prefetch failure. No measured real-model latency improvement claimed from these deterministic checks.
## Analytic light-cycle rendering experiment — October 12
- [x] Reduce the experiment's ambient intensity to 25% and fill to 40% of the original values, preserving the key light and highlights in both comparison views.
- [x] Add isolated `raycast-cycle.html`, using Three.js/WebGL fragment shaders with closed-form ellipsoid intersections, half-space clipping and a convex chassis. This is direct ray casting, not distance-field marching. Keep all production game models/renderers unchanged.
- [x] Match nominal length (3.6 m), width (1.014 m), height (1.52 m), camera and direct-light shader with a toggle to the existing repaired cycle mesh. The analytic model is an authored shape study, not a finished film-accurate replacement. Its panel contours and mechanical details still need work.
- [x] Provide gold/blue, orbit/zoom, fixed 720p/1080p internal resolution, one/four primary rays per pixel and an optional hard shadow ray. Both views use the analytic vehicle as the shadow proxy; raster geometry receives hardware antialiasing while analytic edges use four ray samples by default. There are no reflected rays, ray-marching steps, texture maps, bloom, or full-game simulation in this benchmark.
- [x] Browser test: `node tests/raycast-cycle.mjs`, headless Chrome on macOS, hardware reported `ANGLE Metal Renderer: Apple M2 Max`. After warmup, eight-second measurements at each setting sustained ~60 FPS (display-paced), p95 frame intervals 16.7–16.8 ms, with no page/shader errors. Inspected matched-camera analytic/current-model captures and a shadow capture. Production build passed with existing chunk/font/script advisories.
| View | Internal resolution | Primary samples | Shadows | Median GPU ms |
| --- | --- | --- | --- | --- |
| Analytic cycle | 1280×720 | 4 | Off | 1.81 |
| Analytic cycle | 1920×1080 | 4 | Off | 3.67 |
| Current mesh + procedural grid | 1280×720 | 4 grid samples | Off | 0.85 |
| Current mesh + procedural grid | 1920×1080 | 4 grid samples | Off | 1.25 |
| Analytic cycle | 1920×1080 | 4 | On | 6.31 |
- Limits: one vehicle, static benchmark camera, simple lights and no arena. Results do not establish full-race performance or performance on weaker integrated GPUs. Camera movement is available for manual evaluation. The current mesh remains more detailed and closer to the film; the experiment establishes smooth analytic surfaces and a measured performance baseline, not replacement readiness.
## POV-Ray cycle CSG port — October 12
- [x] Replace the experiment's simplified shape with Carl N. Hoff's 54-primitive model from Jason Cox's MIT-licensed `lightcycle-povray` repository. Preserve the upstream source and license in `docs/models/povray-lightcycle/`; retain the existing production mesh comparison and leave the game unchanged.
- [x] Transcribe CSG unions, intersections and differences over transformed spheres, cylinders/cones, boxes, planes, linear prisms and tori. Evaluate sorted ray-boundary events on the GPU; torus roots use quartic stationary-point brackets. Normalize source +X-forward coordinates to -Z and 3.6 m length without stretching proportions. This is a geometry port, not a browser port of POV-Ray itself.
- [x] Separate the inexpensive floor pass from the CSG pass; scissor the latter to conservative projected model/shadow bounds. Compile shadow-free variants and prune candidates behind the nearest part. Shadows remain optional and off by default.
- [x] Match the production arena's 4.32 m grid spacing, 0.08 m full line width, antialiasing, distance fade and navy background. Remove floor specular lighting entirely. Per subsequent user feedback, use brighter `#cccccc` grid lines in the experiment instead of production `#888888`.
- [x] Correct initial bounds that clipped the bulging window sides. Give the glass priority within 0.03 source units (0.21 mm at runtime) of the gold roof to resolve the source's nearly coincident 389.721/389.722-radius surfaces in GPU float precision. Keep window gloss unchanged.
- [x] Verification: `node tests/raycast-cycle.mjs` passed in headless Chrome / ANGLE Metal / Apple M2 Max with no page or shader errors. Inspected gold front, both sides, three-quarter, shadow and blue captures. Latest eight-second runs: 720p/four samples ~60 FPS; 1080p/four samples ~47 FPS; 1080p/shadows ~12 FPS. Earlier optimized runs reached ~60 FPS at 1080p without shadows, so sustained 1080p/60 is not established (other live browser workloads may contend for the GPU). Optional shadows remain too costly for gameplay. The earlier table above describes the simpler initial model, not this detailed port.
- [x] Production build passed with existing script/font/chunk warnings. No deployment requested or performed.
## Optional mixed cycle renderer — October 12
- [x] Add an explicit runtime switch (bottom-left button or Shift-R during the cycle game) between normal meshes and POV-Ray CSG cycles. POV-Ray is now the default when entering the cycle game; the CSG module and shaders are dynamically imported at that point (not during normal CLU play or the home screen). `?cycleRenderer=normal` explicitly opts out. Geometry and materials are disposed on return to normal; race/simulation state is untouched.
- [x] Render each CSG bike through a conservative proxy box with true surface depth, linear lighting and game fog into the existing scene/composer. Arena, maze, trails, other vehicles, bloom and antialiasing retain their normal rendering. This is a mixed renderer, not full-scene ray casting. Gold/blue/red/green colors and interpolated position/rotation/lean follow the existing race view.
- [x] Preserve wireframe materialization using the original model until solid. Preserve floor shadows using original cycle shadow casters (approximate silhouettes); expensive exact CSG shadows remain excluded from gameplay. Screenshot rendering also selects the correct bike representation.
- [x] Isolate normal/CSG adaptive-AA controllers so CSG performance does not permanently lower normal-mode quality. Cancel stale asynchronous switches and clean up on application disposal.
- [x] `node tests/cycle-renderer.mjs`: headless Chrome/Apple M2 Max, 1280×720; validated no CSG requests before selection, in-arena and outside-arena rendering, reversible switching, no race reset, asynchronous cancellation, keyboard/button controls, and unchanged normal geometry/draw-call counts after switching back. Inspected paired captures and live arena capture; no shader/page errors. Short mixed-mode sample p50 16.7 ms / p95 33.4 ms is a smoke check, not a sustained full-match performance guarantee.
## Default POV-Ray cycles and slow-device fallback — October 12
- [x] Automatically enable POV-Ray cycles on entry to the cycle game, retaining the button and Shift-R switch. Keep normal-only operation available through `?cycleRenderer=normal`.
- [x] After four seconds of active-play warmup, measure consecutive two-second frame-time windows. Three slow windows trigger a one-way fallback to normal (below 65% of target FPS, bounded to a 20–40 FPS threshold; 39 FPS for the usual 60 FPS target). Exclude pauses, hidden/unfocused windows, loading, opening camera work, and non-racing phases. Do not automatically retry after falling back; Shift-R allows an explicit manual retry.
- [x] Show `NORMAL (PERFORMANCE)` on the existing switch after automatic fallback. Preserve normal mode's independent AA settings and release CSG geometry/materials.
- [x] `node --test tests/cycle-renderer-guard.test.js`: four tests cover sustained slow play, short hitches, inactive periods/warmup, one-way fallback and manual retry. Browser coverage extends the renderer-switch test to default activation and injected sustained slow frames, verifying it stays on normal afterwards.
## Closer turbo cycle camera — October 12
- [x] Increase the speed-driven camera approach so full turbo settles at a 5.5 m horizontal follow distance (previously 7.8 m), approximately 30% closer. Preserve normal cruising, braking pullback and exponential easing.
- [x] Only invoke the short-boom lateral wall escape when a wall actually clips the requested camera segment. This prevents the intentionally closer turbo framing from triggering a sideways jump.
- [x] `node --test tests/cycle-camera.test.js tests/arena-camera.test.js tests/cycle-renderer-guard.test.js`: 35 checks passed, including smooth speed framing, wall clearance, horizon visibility and fallback behavior.
## Cycle self-shadowing and status controls — October 12
- [x] Cast a secondary ray toward the key light on lit CSG cycle surfaces. Transform the light into each bike's local coordinates so self-shadowing follows rotated bikes correctly; keep original-mesh floor shadow proxies and the existing performance fallback.
- [x] Handle Shift-R before opening/arena-wait and inspection input gates. Hide the renderer control in ray-cast mode; show normal mode (including performance fallback) for four seconds after switching.
- [x] Stop hiding system warnings during cycle play. Keep JEV failures visible as `JEV UNAVAILABLE / LOCAL AI ACTIVE` until recovery; retain the brief CLU notice.
- Validation: Chrome headless on the local Mac, `node tests/cycle-renderer.mjs` passed shader compilation, renderer switching/disposal, visible simulated JEV failure, notice expiry and performance fallback. Inspected `test-results/cycle-renderer-playing.png`. Nine cycle-JEV/renderer-guard unit checks passed. A live local `/api/jev/decision` cycle request returned a valid choice with confidence 1; this verifies the local relay, not production availability. Self-shadowing increases GPU cost; broad client performance testing remains outstanding.
## Cycle AI reaction profiles and collision lookahead — October 12
- [x] Each rider owns a `reactionTiming` profile: mean, standard deviation, minimum and maximum seconds. Draw a seeded truncated Gaussian for each reaction (default deviation 40 ms, bounds 70–450 ms; gold/blue mean 200 ms, red 140 ms, green 300 ms). Human input is unchanged.
- [x] Before scoring destinations, sweep the rider's next reaction travel against continuous trails, arena walls and projected moving bodies. Unsafe directions receive priority penalties over JEV goals and interception incentives. Reserve choices use the heading selected by the turn, instead of the old heading.
- [x] Preserve reaction limits: safety checks occur on decision ticks and account for the sampled delay plus 100 ms margin; no instantaneous emergency turn bypass.
- Validation: `npm test` passed all 486 checks, including fractional turbo obstacle avoidance, Gaussian distribution/bounds/reproducibility, team profile defaults and existing simulation invariants. This addresses immediate collision blunders; competitive tactics and future opponent turns remain imperfect and need play review.
## Credit attribution, previews and launch framing — October 12
- [x] Credit Carl N. Hoff's CSG cycle and Jason Cox's source repository in displayed and archival credits. Identify the custom project ray-casting kernel and Three.js integration accurately; ship the upstream MIT notice with the website.
- [x] Render the Hoff credit with the same CSG mesh/shader factory as gameplay, including self-shadowing, preserving fade/disposal behavior. Keep Daniel Preti credited for the arena and original fallback meshes.
- [x] Correct maze provenance per the user: traced from a TRON (1982) frame showing Flynn entering the electronic world, not an unidentified blueprint.
- [x] Keep the home transition covered when selecting credits so the underlying CLU scene is never revealed.
- [x] Credit blocks hold after typing for 240 words/minute plus 0.75 seconds, with a three-second minimum. Space still advances immediately.
- [x] Lower the formation shot from 1.6 m to 0.55 m with a slightly raised gaze, preserving the right-side three-quarter view, smooth arrival and launch hold. Widen the vertical field of view on narrower windows to preserve horizontal framing.
- Research: Carl Hoff discussed a POV-Ray tank model in [his 2005 Tron-Sector thread](https://tron-sector.com/forums/default.aspx?a=top&id=308827&pg=9). No verified downloadable tank source or reuse terms located; no tank asset imported.
- Validation: nine credit previews rendered in headless Chrome with fade/framing/disposal checks, including the CSG shader preview; capture inspected. All 25 camera unit checks passed; `tests/home-transition.mjs` and `tests/cycle-opening.mjs` passed in headless Chrome. Inspected the lower formation capture. `npm run build` passed. Fixed the development tuning reset to exclude the new antialiasing checkbox from slider indexing.
## Cycle wall elimination rewards — October 12
- [x] An opposing cycle destroyed by a living cycle's trail fully restores the owner's turbo and brake reserves. An allied cycle crashing into the trail instead empties the owner's reserves, animating both meters downward; self collisions give no reward. Existing trail splitting remains unchanged.
- [x] Watched-cycle meters fill brightly over 0.65 seconds, glowing for 1.1 seconds. Switching watched cycles clears the effect.
- [x] W is the cycle turbo key and S is brake; remove T, Space and X aliases and update help. Outside the arena W accelerates/boosts; S brakes and selects reverse when pressed at a stop.
- Validation: headless Chrome recharge presentation check and capture passed on macOS. Production build passed. Full simulation suite includes opponent/friendly awards, swept collisions and removed-key checks.
## Arena artist credit preview — October 12
- [x] Show Daniel Preti's arena architecture and game-style grid beside his extended credit, using an elevated view, proportional fitting and synchronized fades. No race simulation is created.
- Validation: 15 focused collision/control tests passed. Headless Chrome verified reserve fill/drain and watched-cycle cleanup; all ten credit previews passed framing, fade and disposal checks. Arena render captured and inspected on macOS.
## Credit floor and BIT transition — October 12
- [x] Keep the CLU preview covered when departing the home screen for BIT; headless Chrome verified CLU, cycles, credits and BIT transitions.
- [x] Use the usual procedural floor in the arena credit with the game's exact 4.32 m spacing and line width. Allow subpixel lines to disappear at credit-preview scale rather than widening or coarsening the grid. Gameplay floor defaults are unchanged.
## MCP terminal introduction — October 12
- [x] Hide the face, status, input and old controls. Show the MCP-font text at the opening terminal position and the supplied Neon ENCOM Retro Interface Badge at the lower-right logo position.
- [x] Type the film's Dillinger access request with varied key timing and line pauses, request microphone access after typing finishes. Hidden face rendering is not initialized.
- [x] Extract the opening beep from the user-supplied Dilinger.m4v, 0.295–0.620 seconds. Stereo 48 kHz WAV, 250–1800 Hz filtering, +12 dB gain and short edge fades. Film audio belongs to its original rights holders; no redistribution license asserted. Play one second before typing; browser autoplay rejection logs a warning and still permits the text sequence.
- Validation: headless Chrome checked hidden elements, message placement, typed access text and Escape confirmation without loading the large TTS model.
- MCP visual refinement: deeper blue text (#2864cf), matching dim blue, glyph outlines and advances narrowed to 90% with unchanged height, no typing cursor, and the ENCOM panel at 70% of its original size with 60% brightness, equal 4vw bottom/right margins and a four-second fade.
- MCP background is near-black blue (#010207), including the iframe loading background and browser theme color.
## MCP hosted voice and conversation entry — October 12
- [x] Replace browser Chatterbox inference with server-proxied fal.ai Chatterbox using the existing MCP reference. Preserve pitch/reverb processing; no generic voice fallback. Local Vite reads credentials/Fal.txt; production requires the FAL_KEY Worker secret. Keys never enter browser bundles.
- [x] Delete retired MCP IndexedDB model storage and cached ONNX/runtime requests on entry; preserve unrelated browser data.
- [x] After access typing, request microphone access. Denial clears the terminal to MASTER CONTROL PROGRAM UNAVAILABLE. Approval plays WHAT CAN I DO FOR YOU, MR. DILINGER? through the cloned voice, then enables listening after playback finishes. Escape stops requests/audio.
- Live fal.ai smoke test generated a short sample successfully in 13.1 seconds; this is not yet low-latency conversational speech.
## Light cycle edge highlights — October 13
- [x] Add thin, muted blue-gray highlights to the ray-cast cycle's surface creases and grazing rims, guided by the supplied 1982 film stills. Emphasize dark hubs, windows and underbody details; keep painted-body highlights subdued. Shared by game and credits, with named color/strength/width controls in cycle-ray-shader.js. Screen-space normal derivatives add no additional intersection rays; normal mesh rendering remains unchanged.
- Validation: headless Chrome `node tests/raycast-cycle.mjs` and `node tests/cycle-renderer.mjs` passed without shader errors. Inspected study and game captures; switching, fallback and resource disposal checks passed. Hardware performance and motion shimmer still require human play review.
## Outline filtering and MCP entry follow-up — October 13
- [x] Replace discontinuous normal-derivative outlines in game/credits with an eight-tap filtered normal buffer. The cycle-only prepass adds one intersection pass; the rejected multiple-neighbor-ray approach was too expensive. Preserve normal-renderer isolation and dispose the buffer on renderer shutdown. The standalone study uses softened grazing highlights.
- [x] Start the supplied Wendy Carlos “Break In (For Strings, Flutes And Celesta - Bonus)” recording with the first access character, at 30% volume. Pause it for exit confirmation/return home and release it on page exit. Original soundtrack rights remain with their rights holders.
- [x] Request microphone permission before testing speech-recognition availability. Explain audio-input requirements and browser permission settings on failure; click or Return retries access. Unsupported speech-recognition browsers get a specific explanation.
- [x] Add https://github.com/stevedekorte/TRON to Steve DeKorte's on-screen credit. credits_display.txt drives the terminal; credits_extended.txt remains linked/copied as the detailed archive.
- Validation: headless Chrome MCP approval/denial/retry, first-character music timing, greeting-before-listening, game renderer switching/fallback and all ten model credits passed. Game and credit captures inspected. Local Vite needed a restart to install the new speech endpoints; voice-status now returns configured:true.
## MCP response latency — October 13
- [x] Use fal.ai Chatterbox Turbo with the same MCP audio reference and existing client pitch/reverb. Turbo uses temperature 0.7; standard-model exaggeration/cfg parameters are omitted according to its API schema. Short sentence generation and next-sentence prefetch remain enabled.
- Measured one identical seven-word sentence: standard 2.995 seconds, Turbo 1.421 seconds. A separate local Gemini request took 2.068 seconds. These are individual service checks, not guaranteed end-to-end response times; recognition finalization, audio download, cold starts and network load add latency. Turbo voice delivery still needs user listening review.
- Validation: all 12 focused MCP relay, validation and speech queue tests passed. Local server restarted to load the new endpoint.
## Arena grid wall alignment — October 13
- [x] Fit an even number of whole visual grid cells between the four inner arena walls. With the existing 409.06 m half-span, 190 cells span the floor at 4.3058947368 m each (previously 4.32 m). Center and boundary lines align without partial perimeter cells. Applies to gameplay and the shared arena credit floor; cycle movement remains continuous and unchanged.
- Validation: direct dimension check confirmed 95 cells per half-span with zero wall remainder. Headless Chrome credit-model rendering checked the shared floor.
## Ray-cast cycle materialization — October 13
- [x] Keep the CSG cycle active through the opening/countdown wire sweep and solid fade, eliminating the mesh-to-ray shape change. Reuse the existing reveal timing and team colors.
- [x] Draw antialiased construction curves directly on the analytic CSG surface at local section-plane intersections, with the filtered silhouette/crease pass. These are smooth surface cross-sections, not the old mesh's triangulation. Hidden rear surfaces stay suppressed. Named spacing, line width and intensity are in CYCLE_REZ.
- [x] Preserve alpha/depth behavior through the fade; the normal prepass temporarily writes depth and restores the color-pass state. Raster fallback retains its existing materialization.
- Validation: headless Chrome tests/cycle-ray-materialization.mjs captured sweep, wire, fade and solid stages; inspected wire/fade captures. Full cycle-opening and cycle-renderer checks passed (pause/resume, race handoff, switching, resource cleanup and fallback). Production build passed. Motion/fidelity remain subject to user review.
- [x] Refine formation into defining silhouettes/creases first, cyan surface sections second, then a pale, glowing body that settles to its normal material. Timing is shared in cycle-materialization.js; yellow-green contours and cyan glow follow the supplied 42:54–42:56 film references. No riders are shown.
- Release validation: 500 automated tests passed; production build and full opening browser check passed. Staged ray-cast captures passed without shader errors; user approved the appearance.
## Cycle shadow contrast and MCP music — October 13
- [x] Reduce ray-cast cycle ambient intensity from 0.25 to 0.03 and fill from 0.4 to 0.04. Fully occluded key light now contributes zero instead of 15%, producing near-black self shadows while retaining the key light and thin edge highlights. Shared by gameplay, credits and the analytic study.
- [x] Double MCP opening music playback volume from 0.3 to 0.6.
- Validation: headless Chrome materialization shader/lifecycle check passed; inspected shadow-side and lit-side captures. Production build passed for the shadow changes; music adjustment checked directly. These changes are local following the preceding deployment.
## Cycle rear window panels — October 13
- [x] Assign the rear prism-cut faces of both projecting CSG windows the same gray material as the underbody. Curved glazing remains dark and reflective; material selection follows the actual CSG boundary rather than camera direction or team color. Applies to the shared analytic model in gameplay, credits and the study.
- Validation: Chrome materialization shader/lifecycle check and production build passed. Inspected rear-view and flat-material diagnostic captures to verify the gray panel independently of the new dark shadows. Local change, not part of the preceding shadow/music deployment (which completed successfully).
## Moving wheel reflections and menu/audio timing — October 13
- [x] Add antialiased white spoke glints only on both black inner CSG wheel discs during motion. Cadence is 12 flashes/second at normal speed, scales with speed up to 24 at turbo, and fades out below 2 m/s. Simulation time drives phase, so rendering frame rate does not set the animation speed; stopped/formation/result cycles and static credits have no glints. Named values live in cycle-wheel-reflections.js. Raster fallback remains unchanged.
- [x] Cue MCP music before typing, with a one-second lead from playback starting. Retain cancellation, volume and exit handling.
- [x] Place Light Cycles first and select it by default; keyboard order follows the displayed buttons.
- Validation: all 502 automated tests passed. Headless Chrome checked wheel on/off/stopped captures, materialization shaders, menu order/default/wraparound/Return launch, and MCP music lead-in plus permission/greeting/reply lifecycle. Inspected wheel glint capture.
- [x] Thin materialization surface-section lines from 0.006 m to 0.003 m half-width, reduce contour sampling radius from 1.2 px to 0.65 px, and narrow/soften the surrounding halo. Solid-cycle edge highlights are unchanged. Chrome staged shader/render checks passed and the thinner grid capture was inspected.
## Live wheel animation and MCP response presentation — October 13
- [x] Fix moving wheel glints being reset every frame by interpolated cycle object replacement. Reset the presentation clock only by stable seed/round/cycle ID. Live-match regression now waits for phase progression across actual game frames, in addition to the isolated shader checks.
- [x] Shorten MCP typed access text to one line: REQUEST: ACCESS TO MASTER CONTROL PROGRAM. Hold it for 1.5 seconds before microphone permission/conversation startup. Reveal greeting and reply text at the beginning of cloned audio playback rather than while generation is pending.
- [x] Add a separate Wendy Carlos credit for Break In (For Strings, Flutes And Celesta - Bonus), used for the MCP introduction, in displayed and extended credits.
- Validation: all 502 automated tests passed; headless Chrome checked live wheel phase progression, the completed-intro pause, greeting/reply audio-start synchronization, and microphone allow/deny/retry paths.
## Processed MCP speech cache — October 13
- [x] First MCP spoken/displayed response is IDENTIFY USER AND ACCESS CODE.
- [x] Cache fully processed mono PCM in IndexedDB (tron-mcp-rendered-speech), including pitch slowdown and the reverb tail. Playback recognizes processed audio and bypasses the live effect graph, avoiding double processing. Concurrent requests for normalized identical text share one generation. Cache failure falls back to generation/playback.
- [x] Keys include normalized text plus MCP_VOICE_ID, processing revision and the complete effect settings. Opening with a changed version clears old audio. Bump MCP_VOICE_ID in speech-cache.js when changing the hosted model/reference voice; numeric effect settings invalidate automatically.
- Validation: Chrome verified one TTS request for concurrent/repeated text, byte-identical PCM after a new page reload, nonzero baked reverb tail, and fresh generation after version invalidation. MCP permission and ENTER QUERY synchronization checks passed. Live-game wheel regression was expanded to require advancing phases on every living cycle; all six cycles, including five AI, passed.
- [x] Initialize one live Web Audio output before the MCP introduction and route both background music and speech through it, eliminating late output-context creation/rerouting at first speech. Cached speech also avoids live effect-graph construction. Headless Chrome verified one live context, no music pause during greeting/reply, synchronized display and 1x playback for preprocessed audio. Device-specific audible continuity still needs user listening confirmation.
## Multiplayer countdown audio — October 14
- [x] Add a short 660 Hz beep for each received countdown second and a longer 990 Hz launch tone on the transition to racing. Follow the same rounded server timer as the HUD; repeated snapshots and backward timer corrections cannot repeat a beep, and missed seconds do not produce a catch-up burst. Joining an already-running match does not play a launch cue.
- [x] Route the synthesized tones through the existing master volume/mute, consume muted ticks without replaying them on unmute, and dispose nodes on exit/reset. Single-player remains unchanged.
- [x] Verification: all 10 multiplayer authority/audio unit checks pass, covering countdown synchronization, mute, late joins, envelope duration and node cleanup. Production build and Chrome multiplayer lifecycle checks pass; a Chrome audio probe observes eight 660 Hz ticks followed by exactly one 990 Hz launch tone. Subjective volume/timbre remains for user audition.
## Solo multiplayer restart — October 14
- [x] Return requests a fresh multiplayer round when exactly one human is connected, including after that player is eliminated. Show RETURN / NEW MATCH only in that case. Other connected humans, including spectators, prevent restart.
- [x] The server checks the sender, connected-human count and current round before accepting the request. Duplicate/stale round requests are ignored, and the normal new-round path restores seats, AI, trails and countdown.
- [x] Validation: multiplayer authority/audio unit tests cover sole-player restart, eliminated players, spectators blocking restart, unknown senders and duplicate/stale requests.
## Multiplayer spectator reserve stability — October 14
- [x] Preserve reserve-reward object identity across equivalent network snapshots. Previously, each decoded snapshot retriggered the HUD refill animation, making a followed AI cycle's turbo/brake meters repeatedly snap back. Number actual reward events so identical consecutive rewards still animate separately.
- [x] Validation: 13 multiplayer authority/audio/HUD checks pass, including 40 repeated snapshots with no new reward, a genuinely new reward with the same values, and completion of the HUD animation. Chrome's isolated-arena multiplayer test passes solo Return restart and rejection with other humans connected, plus normal two-client rendering. Full suite: 517 tests pass; production build passes.
## Multiplayer roster stripe refinement — October 14
- [x] Match each black/white human-player stripe to the roster circle's 2 px border thickness. Extend the stripes over the border to the circle's outer edge, with rounded clipping that keeps them inside the circle. Preserve team connectors and hollow eliminated icons.
- [x] Chrome roster check passes; inspected the rendered human stripes and eliminated circles.
## Solo multiplayer Return follow-up — October 14
- [x] Diagnose the unchanged localhost server: a sole-client WebSocket restart request was ignored by the old in-memory code. Restart the Vite server on port 5173 to load the new server-side handler.
- [x] An accepted solo restart immediately initializes racing and trails with zero countdown when the new round has no waiting queue. Support main Return and keypad Enter; retain authoritative rejection when another human is connected.
- [x] Multiplayer authority/audio unit checks pass, including immediate racing, trail initialization, stale/duplicate requests and queue protection. Chrome on the restarted port 5173 server verifies main Return and keypad Enter immediately restart a solo round while shared matches reject Return. Production build passes.
## Eliminated human roster stripes — October 14
- [x] Retain the black/white human stripes after elimination, over the transparent circle interior and team-color border. Chrome roster checks pass; visually inspected the hollow striped icon.
## Compact multiplayer snapshots — October 14
- [x] Introduce protocol 2 with whitelisted presentation fields, submillimeter coordinate quantization, field-level cycle/trail changes, roster updates only when changed, and crash updates only when changed. Keep collision and AI simulation at full precision on the server. Initial/new-round snapshots are complete; deltas identify their baseline and clients request a full resync on a mismatch.
- [x] Keep protocol 1 supported for cached clients during deployment. Share encoded messages among clients with the same baseline and preserve separate late-join baselines. Applied human turns and reserve-control changes trigger a snapshot immediately instead of waiting for the next 20 Hz update. Prediction clones only moving trail tips rather than every trail object each frame.
- [x] Reproducible five-second measurements (`node scripts/measure-multiplayer-bandwidth.mjs`, same snapshots, uncompressed JSON): six cycles/one human falls from 111,015 to 12,779 bytes/sec (88.5%); twelve cycles/twelve humans falls from 197,499 to 13,360 bytes/sec (93.2%). These measure bandwidth, not real-network latency or perceived smoothness.
- [x] Validation: full suite passes 520 tests; 17 focused multiplayer checks include a subsequently added mixed-version/late-join/resync regression. Chrome renders two compact clients alongside twelve legacy wire clients without errors; inspected normal-renderer capture. Production build and Worker dry run pass.
- [ ] UDP-style transport: WebTransport datagrams or unreliable WebRTC data channels need a compatible server/gateway. Cloudflare Workers' documented protocol interfaces do not provide a direct WebTransport/WebRTC server endpoint. Retain WebSockets for this optimization; no hosting migration or deployment is included. See the multiplayer document for sources and tradeoffs.
## Multiplayer arena breaches — October 14
- [x] Enable impact-created arena breaches in multiplayer, synchronize openings in full and compact snapshots, and restore intact walls each match. No opening is pre-created.
- [x] Cycles crossing the outer edge silently derez, stop counting as survivors, and enter the normal next-match queue. No explosion effect or explosion sound; trail cleanup remains active. Keep bounded AI enabled after breaches appear.
- [x] Validation: 27 focused multiplayer/breach/AI unit tests pass; Chrome arena breach geometry checks verify an opening and restored walls; production build passes. Local implementation only, not deployed.
## Cycle AI evaluation rewards — October 14
- [x] Add persistent, bounded confinement attribution, hollow roster assist dots, and offline rewards for direct kills, trap assists and creating confinement. Runtime remains the first gate; aggression and wins jointly determine fitness. 561 unit tests, Chrome roster checks, dense attribution benchmark, evolution smoke and production build pass. No live weights changed; implementation local only. See [cycle AI](cycle-ai/) for measured costs and conservative attribution limitations.
## First evolved cycle AI playtest — October 14
- [x] Run 128 training and 64 paired held-out matches with aggression rewards; preserve weights/results and check serial/browser performance. Candidate gets 5 versus 3 direct kills, 10 versus 9 trap assists and 5 versus 4 wins in the 32-match held-out comparison. Evidence remains weak across eight independent seeds.
- [x] Add a development-only `?cycleAITrial=1` route for playing the candidate in LIGHT CYCLES. Exact weights and rendering verified in Chrome; baseline and production unchanged. See [cycle AI](cycle-ai/) for the reproducible artifact, timing and limitations.
- [ ] Human play review and broader evaluation before promoting the candidate, including multiplayer wall-breach matches.
## MCP closing-sentence fade — October 14
- [x] Fade the MCP face during the END OF LINE sentence, completing before speech finishes rather than leaving it visible through the post-speech hold. Use the actual audio duration/pitch and exclude the rendered reverb tail. Keep complete playback, the one-second hold and the normal game transition. Clear the fade state on another announcement or panel resume.
- [x] Four speech queue unit checks and the Chrome game-launch test pass, including progressive fade, disappearance before the closing audio ends, no early launch, complete speech, post-speech hold and direct cycle entry. Local only.
## Evolved AI default for solo and MP — October 14
- [x] Following the user's playtest, select frozen `aggression-1-0` weights as the common solo/MP default. Keep the original baseline for offline comparisons and keep ongoing experiments from replacing gameplay weights automatically. Solo arena AI remains bounded after breaches.
- [x] Focused AI/authority policy-selection tests, two-client Chrome MP lifecycle coverage and Worker dry run pass. Production deployment remains outstanding.
## Cycle AI research and evolution plateau — October 14
- [x] Complete the extended evolution run: stop after three consecutive generations without a qualifying validation gain. Fresh evaluation does not improve on the playtested policy; retain `aggression-1-0`. Preserve the experiment and limitations on the [cycle AI page](cycle-ai/).
- [x] Review original Tron AI research and Armagetron source for territory evaluation, neural inputs, passive self-play failure modes and close-combat interception. Record findings and proposed experiments on the cycle AI page. No new neural controller or overtaking behavior is implemented by this research.
## Faster-win AI evaluation — October 14
- [x] Add a tunable maximum 0.5-point bonus to confirmed wins, declining linearly with elapsed time relative to the evaluation match limit. Losses and timeouts earn no speed bonus; existing kill/trap/confinement rewards remain. Record match limits and worker bonus contributions for reproducibility. Runtime remains the first gate; gameplay weights are unchanged.
- [x] Four fitness tests pass, covering fast/late wins, loss/timeout exclusion, bounded scoring, tuning and missing/invalid timing. Offline evolution smoke passes. New scoring needs a fresh training/evaluation comparison before any policy promotion; historical scores use the previous objective.
- [x] Run `node tools/ai/evolve-until-plateau.mjs --fast-wins`: 352 simulated matches; stop after three consecutive failed validation generations. Retain `aggression-1-0`, with no trial/default change. Fresh results: 5/32 wins for both retained policy and original baseline, fitness 0.5266 versus 0.5500, mean winning duration 147.5 versus 151.2 seconds. No demonstrated improvement; see [full results and limitations](cycle-ai/). Runtime p99 gate passes; no deployment.
---
Source: /fun/TRON/docs/assets/
---
title: Assets
subtitle: Model, sound, and dependency inventory
---
## Big labyrinth blueprint — September 23
`docs/references/images/mazes/Big labyrinth.png` is a user-supplied reference; original author and license have not been provided. Its blue wall silhouettes were traced locally into polygon contours, simplified at one image pixel, scaled to 2.4 m/pixel and extruded to 54 m. The runtime uses generated geometry, not the raster texture. Source, tracing script and an SVG comparison are retained in the repository.
The tank and Recognizers are user-supplied Sketchfab GLBs; Sark’s carrier is a user-supplied JIHS Collada model converted locally to GLB. All are served locally with the application. Maze geometry is generated locally; vehicle/cannon audio uses short edits from the user-supplied film clip, with synthesized fallback and impact effects. No purchase was made. See [Models](../models/index.html) for source credits and adapter details.
| Asset | Source / author | Format and size | Use / attribution | Status |
| --- | --- | --- | --- | --- |
| Tank | arabinowitz / Sketchfab | GLB, 1.82 MB | Embedded Standard license; creator listing additionally says editorial purposes only | Imported and active |
| Recognizer | Shriker1 / Sketchfab | GLB, 128 KB | Embedded Sketchfab Standard license | Imported and active |
| Sark’s carrier | JIHS / TurboSquid; supplied by user | GLB, 6.86 MB, seven embedded textures | Listing Standard license; source readme retained | Background straight-line transit |
| Solar Sailer | JIHS; user-supplied TurboSquid DAE/readme | Local GLB, approximately 14 MB, seven embedded textures | Readme credits Syd Mead, Jean Giraud (Moebius), and Peter Lloyd for original design; redistribution terms are not stated in the supplied readme | Periodic background transit; no deployment performed |
| Maze slabs and surrounding grid | Fixed branching topology, oblique convex prism geometry | JavaScript / generated buffers | Original project implementation | Active |
| Tank movement and Recognizer flight/approach | User-supplied TRON scene | Three stereo PCM WAV loops, about 0.76 MB combined | Film-derived excerpts; source/timecodes below | Active, awaiting auditory review |
| Cannon | User-supplied TRON scene | Stereo PCM WAV, 102 KB | Film-derived excerpt | Active |
| Impact, destruction, missing-file fallback | Project Web Audio synthesis | Short oscillator/noise envelopes | Original synthesized effects | Active |
| UI and favicon | Project HTML/CSS; inline favicon path | Local Interface Raster terminal font derived from VT323; system fonts elsewhere | Original project implementation | Active |
| Terminal font | Peter Hull / [VT323 in Google Fonts](https://github.com/google/fonts/tree/main/ofl/vt323) | Local `public/fonts/VT323-Regular.ttf` | SIL Open Font License 1.1; retained in `public/fonts/OFL-VT323.txt` | Active |
| Film reference stills | User-supplied screenshots from the linked 1982 sequence | 15 PNGs in `docs/references/images/` | Study references; not used as runtime art | Inspected |
| Three.js | [Three.js](https://github.com/mrdoob/three), installed 0.186.0 | npm dependency, lockfile pinned | MIT; upstream license remains in package | Active |
| colvmn | [Steve Dekorte / colvmn](https://github.com/stevedekorte/colvmn) | Local documentation engine checkout | MIT; upstream license retained | Active for docs only |
## Evaluated external candidates
| Candidate | Creator | Published details | Decision |
| --- | --- | --- | --- |
| [Tank — Tron (1982)](https://sketchfab.com/3d-models/tank-tron-1982-b730d118f54b4a868339a4ddf11e09a7) | arabinowitz | Listing describes Blender/FBX files, about 58k triangles, free download, and editorial use. The listing's generic “Free Standard” label should not be substituted for reviewing the actual terms. | User supplied the GLB; imported and browser-tested. |
| [TRON Immersive](https://www.behance.net/gallery/236722545/TRON-Immersive) | Steve Talkowski | Creator presents tank and Recognizer work with embedded viewers. | Reference/candidate only; no download permission or usable distributable asset established. |
No paid candidate was selected and no purchase was made. Both GLBs load with GLTFLoader and uniform normalization; the original files remain unmodified.
The TRON names and film designs identify the user's reference. This inventory does not claim ownership of the underlying film property or permission to redistribute film assets.
## Historical replacement model shortlist — September 12
The user judged the second procedural pass closer but still inaccurate and requested internet models. Inspected the four public preview images below and queried Sketchfab's official metadata API. All four are marked downloadable. At the time of this initial search, no source mesh had been acquired or imported: an unauthenticated request to the official tank download endpoint returned `Authentication credentials were not provided.` The user subsequently supplied the two active GLBs above. Preserve the source archive and included license when downloading.
**Recommended first: Shriker1's pair.** The tank preview has a much better broad hull, offset canopy, triangular cannon supports, collar and recessed muzzle. The Recognizer preview has the articulated-looking shoulder construction, substantial body depth and inward feet. Both are described by the creator as custom-made and game-ready. That description and the polygon counts make them promising for the browser; materials, movable parts and final fidelity still need inspection in the source files.
| Model | Creator | Triangles | Published terms / formats |
| --- | --- | --- | --- |
| [Tron 1982 Tank](https://sketchfab.com/3d-models/tron-1982-tank-99f4ae8ce97445e8909c02d7481d12d9) | Shriker1 | 9,648 | Free Standard per official metadata; source formats not verified |
| [Tron 1982 Recognizer](https://sketchfab.com/3d-models/tron-1982-recognizer-ecb52608275c40a28d7d9dda1aed54f3) | Shriker1 | 1,854 | Free Standard per official metadata; source formats not verified |
| [Tank — Tron (1982)](https://sketchfab.com/3d-models/tank-tron-1982-b730d118f54b4a868339a4ddf11e09a7) | arabinowitz | 57,976 | Blender/FBX; listing says editorial use only |
| [Recognizer — Tron (1982)](https://sketchfab.com/3d-models/recognizer-tron-1982-897ad9061ab34a9885b8e874fe8a17e0) | arabinowitz | 8,080 | Blender/FBX; listing says editorial use only |
Sketchfab labels all four Free Standard; the arabinowitz descriptions additionally restrict use to editorial purposes. The current license page could not be fetched, so these are recorded listing terms, not completed license clearance. Metadata snapshots and public promotional previews are retained in `candidates/` for comparison; they are not runtime assets.
### Shriker1 tank preview

### Shriker1 Recognizer preview

### arabinowitz alternatives


Also found [JIHS's TRON 1982 Tank on TurboSquid](https://www.turbosquid.com/3d-models/3d-tron-1982-tank-1811154), listed at $25, 49,152 polygons, with FBX/OBJ/glTF among the offered formats and an editorial-use restriction. It is a paid fallback; no purchase was made.
The simulation revision still uses the procedural vehicle models. Recognizers are uniformly scaled by 0.5 in the adapter; collision uses the same scale. Five instances and five independent spatial sound voices replace the single scripted craft. Maze roof/side geometry is generated from the same clipped convex polygons used by collision and visibility.
## Requested film audio pass
Source: https://www.youtube.com/watch?v=kKBuOtedf3E — “Tron (1982) - Clu infiltrates the Encom system,” uploaded by vson8, 199 seconds. Metadata was retrieved on September 13; both audio-only Opus and standard MP4 media downloads returned HTTP 403. No film audio was extracted or added. Existing synthesized effects remain active. Await a local copy before isolating tank movement, cannon shots and Recognizer motion.
## Local video sound extraction — September 13
The user supplied `docs/references/videos/1982 tron clu scene.mp4` (187.48 s, 44.1 kHz stereo AAC). This resolved the earlier YouTube download block. Selected cuts using exterior action frames and signal/spectrum inspection. Source audio is a mixed film soundtrack; these edits are not isolated production stems. No direct auditory assessment was available in this run, so speech/score contamination and subjective fidelity remain for user audition.
| Runtime sample | Local video in/out | Processing |
| --- | --- | --- |
| tank-drive.wav | Spectrum derived from 10.75–11.45 s | 5.94-second periodic engine bed reconstructed from the filtered film sample’s averaged spectrum. Randomized phases remove recurring source transients; original time-domain stereo is replaced by a compact emitter panned in-game. Rebuild with `scripts/smooth-tank-loop.py`. |
| recognizer-flight.wav | 124–126 s | 45 Hz high-pass, 6.5 kHz low-pass, 120 ms loop crossfade |
| recognizer-approach.wav | 126.20–128.40 s | Same filters/crossfade; mixed into pursuit and attack |
| cannon.wav | 121.38–121.76 s | 65 Hz high-pass, 12 kHz low-pass, 3 ms attack/70 ms release fades |
All retain two channels, receive DC removal and peak normalization to 0.72, and are stored in `public/audio/`. Rebuild with `python3 scripts/extract-sfx.py` (ffmpeg required). `public/audio/sources.json` records the input SHA-256, cuts and processing. Credits: TRON (1982) film soundtrack, supplied by the user for this local study; no redistribution license has been established. The full reference video is excluded from the static build.
[Sound audition](http://127.0.0.1:5173/audio.html) provides individual loops and a moving stereo Recognizer using the game's HRTF emitter. Each craft has two world-space emitters 4.8 m apart, independent loop offsets, distance attenuation, wall low-pass/attenuation and bounded Doppler playback-rate shifts. The listener follows Clu with camera orientation, rather than moving hundreds of meters away with the aerial camera.
Tank revision: the user identified dialogue in the original 70.80–73.20 s cut. Replaced it with a short opening texture from 10.75–11.45 s (0.58 s after crossfade), chosen using the opening frames and spectrum. Listening confirmation remains with the user. Recognizer and cannon WAV hashes are unchanged.
Cannon revision: shortened the film cut to 0.38 s around the first firing onset to reduce neighboring rapid-fire content, widened the passband, and removed random playback pitch. Inspected frames at 120.8–123.2 s and measured waveform onset at 121.38 s. This is still mixed film audio; listening approval is pending. Terminal clicks and access beep are synthesized approximations, not extracted movie sounds.
Camera-listener revision: all spatial listening now uses the camera, including its height and movement. The tank has an 18 m reference-distance stereo emitter, so zooming away attenuates its sound. This supersedes the earlier Clu-anchored listener description. The opening tracks Clu's live position while it starts at full speed; releasing W ends the initial throttle.
## Opening music
User-supplied `music/Tron/03 We've Got Company.mp3` plays once from the beginning when a run starts, including the opening zoom. It pauses with standby, resumes from its current position, follows the sound mute/master volume, and restarts on R. A single streaming media element feeds the audio mix at 55% gain. Vite packages this selected track; the documentation copy excludes the rest of the supplied album. Source: user's local TRON film soundtrack collection; no new redistribution license established.
Terminal keys now use four stereo excerpts from the first two seconds of the supplied local video, starting at 0.885, 1.395, 0.995 and 1.585 seconds. The latest revision trims each to 60 ms, lowers the low-pass cutoff to 6.5 kHz, and adds an early decay with a smooth 30 ms release to suppress the harsh trailing transient reported by the user. Samples alternate without immediate repeats; synthesized ticks remain a loading/failure fallback. The access beep remains synthesized. Audio context resume and music play are both requested directly during Return's input event, before any asynchronous yield.
## Carrier rumble
`scripts/extract-carrier-sfx.py` reproducibly extracts 116.2–120.2 seconds of the user-supplied carrier/solar-sailer video. `public/audio/carrier-rumble.wav` is stereo PCM, 44.1 kHz, 3.6 seconds after a 0.4-second loop crossfade, DC removal, 35–900 Hz filtering and peak normalization to 0.65. Provenance/hash and processing are in `public/audio/carrier-source.json`. This is a mixed film soundtrack excerpt, not an isolated vehicle stem. Auditory approval remains pending; the assistant has not listened to the sample. In-game it uses stereo HRTF placement, camera distance attenuation, distant low-pass filtering and Doppler following the carrier's world position. The master mute/standby gate applies.
September 14: eight ground enemies reuse arabinowitz's supplied tank GLB, normalized geometry, surface materials and cannon sample. Each clone has independent turret/recoil transforms and red insignia. Source assets are unchanged; no additional models were downloaded.
### September 14 Recognizer sound revisions
Re-cut `recognizer-flight.wav` from exactly 2:04–2:06 of the local `1982 tron clu scene.mp4`; stereo PCM with the existing 120 ms loop join (1.88-second playback). Added `recognizer-explosion.wav` from 3:06–3:07, a one-second stereo effect with short edge fades, 45 Hz high-pass and 12 kHz low-pass. Recognizer destruction plays it through two world-space HRTF emitters; tank destruction retains its existing sound. Both excerpts are available on `/audio.html`. These remain mixed film soundtrack excerpts, not isolated sound-effect stems.
Correction: the 3:06–3:07 selection contained terminal typing. `recognizer-explosion.wav` now uses video time 1:47.84–1:48.69 (0.85 seconds), aligned with the visible explosion before the cockpit cut. Recognizer excerpts are now extracted from the user-supplied M4A. Full decoded PCM comparison found that M4A equals the video audio after trimming its first 2112 frames (47.89 ms); the extraction script compensates for this offset and records source/seek per sample. The game and audition page request version 2 of the explosion to bypass the previous cached audio.
The supplied `Recognizer Explosion.m4a` edit matches video time 113.207 seconds (normalized waveform correlation 0.997). `scripts/clean-recognizer-explosion.py` uses surrounding soundtrack context and shared-stereo harmonic/percussive masks to suppress sustained tonal ridges. It generates original/balanced/strong audition WAVs under `public/audio/studies` and a provenance manifest. Requires NumPy/SciPy (installed only in a temporary environment). This is approximate suppression: overlapping cannon/explosion energy cannot be reliably separated by this method, and stronger masking loses transient detail. Listening comparison is pending.
## Sound resource library
[Open the sound library](../sounds/index.html) for downloaded Syna-Max recreation previews, existing film edits, cleanup candidates, verified stock-effect catalog IDs and vendor metadata exports. The shelf preserves inactive resources, author/source links and licensing notes for this personal-use project. `docs/sounds/catalog.json` is the editable inventory; `scripts/build-sound-library.mjs` regenerates its page during the docs build. Public MP3 previews are clearly distinguished from original downloads and unacquired commercial-library audio.
### MCP closing dialog
`docs/references/dialog/END OF LINE.mp3` is the user-supplied MCP “END OF LINE” film dialog (1.28-second MP3). It plays once as the terminal starts typing the final sign-off. Imported directly through Vite and decoded into Web Audio; no edits to the source recording. Source: Disney's 1982 TRON, supplied by the user for this fan tribute.
### Embedded terminal raster font
`public/fonts/InterfaceRaster-Regular.ttf` is a modified VT323 font, renamed **Interface Raster**. Each of 582 glyphs retains its original monochrome outline and adds COLRv0 layers: bright pixels plus 15-unit bands at 68% brightness within each 80-unit source pixel row. The two embedded palettes match terminal blue and sign-off red. This replaces the screen-space scanline mask, keeping the dim bands aligned through typing, resizing, and sentence fades. The CSS cursor receives a matching dim-band treatment.
Based on Peter Hull's VT323 under SIL OFL 1.1; original notices remain in the font and `public/fonts/OFL-VT323.txt`. Rebuild with `uv run --with fonttools==4.65.0 --with skia-pathops==0.9.2 python scripts/build-terminal-font.py`. No extra dependencies are needed to run the game.
## Debris physics
Rapier 3D (`@dimforge/rapier3d-compat` 0.20.0), by Dimforge, Apache-2.0. The local npm package includes WebAssembly; no runtime CDN is required. Used for vehicle debris only. [Source and license](https://github.com/dimforge/rapier).
## Procedural pad teleport cue
`src/audio/teleport.js` generates original deterministic stereo PCM buffers at the browser sample rate. Authored in this project; no external recording, model or asset was used. A descending FM sweep collapses into a short digital transient, followed by a rising inharmonic shimmer and short stereo echo. Departure lasts 0.28 seconds, arrival 0.62 seconds; Clu's combined transition lasts 0.78 seconds. Buffers are cached and reused; one-shot nodes disconnect on completion/reset. This is an original game sound, not a claimed recreation of a film sample. `node tests/teleport-audio.mjs` writes a standalone listening preview to `test-results/teleport-preview.wav`.
## ENCOM terminal mark
`public/images/encom-terminal.svg` is a project-authored vector approximation of the ENCOM mark visible in the user-supplied original-film still `docs/references/images/MCP terminal.png`. Paths and glow styling were authored locally; no external font or logo asset is loaded. The underlying film branding is not an original project design; no additional license is asserted.
## Cycle arena entrance audio — September 28
`scripts/extract-arena-entry-sfx.py` extracts four local WAVs from the user-supplied `docs/references/videos/Enter Cycle Arena.m4v`. `public/audio/cycle-entry-source.json` records source hash, exact cut boundaries, channel selection and processing. Dialogue uses the 5.1 center channel (11.72–14.28 s and 17.32–18.65 s); transport uses the stereo downmix at 14.28–17.32 s and startup at 28.35–31.408 s. All use short edge fades, 60 Hz high-pass filtering and peak normalization to 0.72. These are TRON (1982), Walt Disney Productions soundtrack excerpts; no redistribution license was supplied. Transcription located dialogue boundaries; subjective listening approval remains pending. The full source video is not included in the game build.
## MCP conversation face — October 9
`docs/references/images/MCP/MCP.png` is the user-supplied TRON (1982) film still, attributed to Walt Disney Productions; no redistribution license was supplied. `src/mcp/face.js` maps this original onto a subdivided plane, trims the screenshot border through texture coordinates, and deforms the mouth/jaw during synthesized speech. No replacement image was generated. Movement was informed by the user-supplied `docs/references/videos/MCP/MCPandSARK.m4v`; the video is not bundled in the game. Speech now uses a reference-conditioned MCP clone through Chatterbox, with no browser voice fallback.
## MCP cloned speech — October 9
`public/audio/mcp-reference.wav` contains only MCP speech from 23.23–31.08 seconds of the supplied `MCPandSARK.m4v`, extracted from the 5.1 center channel. `scripts/extract-mcp-voice.py` records exact filtering, transcript, source hash and film attribution in `public/audio/mcp-reference.json`. Residual soundtrack may remain; this is not a claim of studio-isolated dialogue. The film source is TRON (1982), Walt Disney Productions; no redistribution license was supplied.
`public/audio/mcp-clone-preview.wav` is newly synthesized dialogue conditioned on that reference, not a film dialogue excerpt. Its adjacent JSON records text, pinned model revision, duration and generation time. Chatterbox by Resemble AI is MIT licensed; the ONNX conversion is `onnx-community/chatterbox-ONNX`, revision `3cab09af388d3f02bba43443fce88c1f4525ac43`, run through Transformers.js 4.3.1 (Apache-2.0). Model weights download only in MCP and remain in the client's IndexedDB cache. The application uses the library's Chatterbox interfaces; no code was copied from the demonstration app.
## Cycle roster renders — October 10
`public/images/cycle-roster/{gold,blue,red,green}.png` are transparent orthographic top-down renders of the existing Daniel Preti light cycle asset, including the same geometry repairs and team materials used in play. The front faces upward. Source attribution and use constraints are unchanged from the underlying cycle model. Regenerate with `node scripts/render-cycle-roster.mjs` while the local Vite server is running; these static thumbnails add no runtime 3D rendering loop.
## MCP same-dialogue comparison — October 10
`public/audio/mcp-match/` contains the film reference excerpt, newly synthesized matching dialogue, and processed pitch/reverb candidates. `comparison.json` records the transcript, model, measurements, processing search and limitations. `scripts/match-mcp-voice.py` produces the normalized listening files used by `public/mcp-voice-comparison.html`. The original retains the film attribution and constraints above; the other samples are synthesized, not original actor recordings. Objective pitch/tail measurements are an aid to listening, not proof of a perceptual match.
## MCP dot-matrix lettering
`public/fonts/MCPDotMatrix-Regular.ttf` is an original local dot-matrix font,
inspired by the user's supplied “END OF LINE.” film reference. Its geometric
5×7 glyph patterns and round dots are generated by `scripts/build-mcp-font.py`
with FontTools. No existing font outlines or film pixels are embedded. Glyph
design is dedicated to CC0 1.0; the license accompanies the font. Only MCP uses
this typeface, with one shared size and normal/dim blue text colors.
## POV-Ray light-cycle experiment — October 12
The game and `raycast-cycle.html` study adapt **Carl N. Hoff's** solid-geometry light cycle from [Jason Cox's POV-Ray scene](https://github.com/jasonacox/lightcycle-povray). Unmodified source, repository MIT license (Jason Cox, 2019), provenance and adaptation notes are retained in `docs/models/povray-lightcycle/`. The browser shader transcribes the model's 54 primitives and Boolean operations, changes coordinates/scale, and substitutes experiment lighting and the game's grid. It does not import the original scene's reflective floor, animation or trails. A submillimeter surface-priority tolerance stabilizes the window/roof boundary in GPU float precision. The CSG model is the default cycle renderer and credit preview; the original mesh remains the fallback. The CSG intersection kernel is project-authored; Three.js supplies WebGL and post-processing.
## Retired grid cloud asset
The grid cloud is no longer loaded or shown in the game or credits. The retained study files were extracted from [Tron 1982 by jvouillon](https://sketchfab.com/3d-models/tron-1982-d7b1e9a03bca4bb6aa636b56ae45ec88), under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), centered and converted to game coordinates. This records provenance for the archived asset.
---
Source: /fun/TRON/docs/cycle-ai/
---
title: Fast, compelling cycle AI
topTitle: TRON
subtitle: Bounded JavaScript tactics, resource planning, and measured evolution
---
## Obstacle-aware training reward — October 15
The offline trainers now include bounded approach credit based on navigable route distance, not Euclidean proximity. See the [current plan](../) for deployment state, checks and limitations. `tools/ai/approach.mjs` samples a swept-disc route graph once per simulated second, using the same active trail segments as collision. It credits the AI's own progress toward a frozen target, checks that the opponent is still reachable, and excludes passive closing and disconnected regions. A per-pair progress high-water mark limits repeated approach/retreat credit; initial enemy-pair normalization prevents team-size/death artifacts. The maximum approach bonus remains 0.15. Fitness version 2 now assigns one base point plus up to one early-confirmation point per eliminated enemy, shared between direct wall owners and successful trappers. Unfinished confinements no longer earn fitness. See the current plan for the bounded evolution results and limitations.
This is offline instrumentation only: it does not add pathfinding to the live controller or change the deployed policy. The 2.4 m graph is a conservative route estimate; it can miss narrow passages and does not predict future trails or enforce heading/turn dynamics. No Euclidean fallback is used. Training outputs record the reward configuration. All 583 unit tests and a four-seat tournament smoke pass; a subsequent two-generation run and fresh-seed comparison are recorded in the current plan. Its candidate improved trap assists and combined fitness but not direct kills, and remains unpromoted.
## Implemented locally — October 14
The first bounded controller is implemented and enabled by default for multiplayer and the initially unbreached standalone arena. It is plain shared JavaScript: no service requests, inference library or background training. The previous controller remains available with `race.aiMode = 'classic'`; exterior/open-arena behavior stays on the existing controller unless explicitly overridden for development. No production deployment is part of this implementation request.
Implemented behavior:
- One scheduled bot decision per 120 Hz tick for ordinary sequential IDs, each bot planning at 10 Hz; the hard ceiling is two plans per tick. A short safety guard runs every tick. Human seats are untouched, commands are applied after all bot observations, and the six-tick turn cooldown uses simulation time even across partial resource-exhaustion steps.
- A shared, fixed-capacity trail index uses the actual trimmed/damaged/expired collision segments. Queries cap work and treat overflow as unknown/blocked. The index currently rebuilds each step; measured cost fits the reference-machine gate, so incremental maintenance is deferred rather than adding invalidation complexity prematurely.
- Nine joint heading/pedal candidates. The speed forecast shares the authority's response formula and step order; arrival checks account for acceleration, available charge and an opponent's possible turbo response. Cutoffs require an arrival lead and an exit. Closing a confined approach earns more than crossing open space. Brake/yield and resource-conserving coast options compete with turbo.
- Stable target selection and switching penalties, plus seeded choice only among safe near-best candidates. Planning flood fills share a 255-cell limit across the three directions. Full two-turn search, explicit multi-bot role reservations and learned opponent prediction are not implemented yet.
- Versioned bounded scoring weights, an offline evolutionary tournament tool, and an optional 32→16→1 neural residual scorer. Neural output is capped and cannot bypass safety. The scorer is linear; the playtested evolved weights are now the local default (see promotion below). No neural weights are enabled.
- Local development controls in TAB → Arena AI, plus `__tron.configureCycleAI({mode:'bounded', measure:true})` and `__tron.cycleAI` diagnostics. The latter includes decision/guard counts and cumulative AI milliseconds. Multiplayer AI remains authority-owned; the client panel cannot edit it. Settings apply to the current local arena, persist across its rounds, and are not saved globally.
### Verification and reproducible commands
Reference machine: Apple M2 Max, macOS, Node v22.22.0; headless Chrome 155 at 1280×800. These measurements are not server-runtime or low-end-device guarantees.
| Check | Result |
| --- | --- |
| `node tools/ai/benchmark.mjs classic 3 180` | Existing full-tick p99 approximately 1.80–1.82 ms across seeds 1982–1984; matches ended in 35–69 simulated seconds |
| Initial all-bounded full matches | Full-tick p99 approximately 0.38–0.57 ms; several matches reached the 180-second horizon, so stalling/endgame variety still needs play review |
| `node tools/ai/stress.mjs` | Twelve bots and 2,048 changing segments: AI p95 0.262 ms, p99 0.493 ms, max 0.867 ms; no index overflow |
| `node tools/ai/stress.mjs --crowded` | Same history with twelve nearby threats: AI p95 0.399 ms, p99 0.487 ms, max 0.871 ms |
| `node tools/ai/compare.mjs 3` | New team won 9/12 matches against three classic teams, rotating through all four spawn teams for seeds 2200–2202. No draws. A small correlated sample, not a statistically established general win-rate gain |
| `node tests/browser/cycle-ai.mjs` | Local arena/tuning work without page errors; dense browser AI p99 0.4 ms. Actual AI work accumulated per rendered frame: p99 0.4 ms across 481 frames; max 0.7 ms. The rendered test has five AI cycles; the separate dense test has twelve |
| `npm run build` / `npm run worker:check` | Production build/asset audit and Worker dry run pass; no deployment |
| `node tests/multiplayer-browser.mjs` | Two rendered clients, twelve-cycle arena, fourteen connections, queue and restart/lifecycle checks passed |
| `node tools/ai/evolve.mjs --smoke` and `--smoke --neural` | Linear and neural candidate tournament pipelines run and emit a review artifact; short 15-second smoke matches all timed out. No claimed learning improvement or policy promotion |
| `npm test` | 550 tests pass, including collision/expiration/overflow, deterministic decisions, human isolation, policy bounds, neural safety mask, speed/resource forecast, turbo cutoff, brake/yield, turn cooldown and containment fixtures |
Run `node tools/ai/evolve.mjs` for the full offline linear experiment, or append `--neural` for the optional network experiment. It writes `test-results/cycle-ai-candidate.json`, never edits the shipped policy and never trains during play. Full runs intentionally cost more than the smoke check: multiple generations, spawn rotations and a held-out suite. Inspect performance and match results, run scenario checks and human review before importing `artifact.policy` through the development helper or changing the default policy in source. The reported Wilson interval is exploratory: spawn rotations share seeds and are correlated; it is not a sufficient promotion test by itself.
Remaining release-quality evidence: slower devices, actual deployed authority CPU/tick debt, long dense-history full-collision matches, larger held-out tournaments, and real-player impressions of aggression and endgame pacing. CPU timings below the gate do not prove that every overload fallback is strategically good. Geometry saturation stays conservative and can cost a bot an escape. No automated check establishes that the bots are already compelling to human players.
The staged design below remains the longer-term target; unchecked advanced stages are not represented as completed by this first controller.
## Decision and priorities
Design a plain-JavaScript arena AI that wins through positioning, timing and resource use. **Runtime performance is the first priority.** Add intelligence only when it fits the measured budget. The implementation status above distinguishes completed work from the remaining design; this page does not authorize deployment.
Priority order:
1. Keep simulation ticks and rendered frames smooth with twelve cycles and long trail histories.
2. Remain competent: avoid preventable crashes, preserve exits, obey the same movement and resource rules as players.
3. Act aggressively: cut off opponents, take their escape lanes and cooperate to box them in.
4. Use turbo and brake to change an encounter's outcome, conserving charge when spending it provides little benefit.
5. Vary strong decisions without randomly choosing bad moves.
Optional progression: evolve a compact parameter set using controlled win-rate experiments. Trial a tiny neural scorer only if it improves play under the same performance and safety gates. No JEV calls, model downloads, GPU inference or training in the game loop. JEV for tanks/Recognizers is outside this change.
## Current implementation and integration boundary
The authoritative movement and AI currently meet in `shared/simulation/light-cycles.js`. `chooseCycleDirection` compares forward/left/right using clearance, bounded flood-fill space, nearby cycles, a bias toward opponents' forward lanes, optional `jevGoal`, and small random score noise. Turbo/brake decisions then use clearance thresholds. AI decisions run on sampled reaction intervals; movement is continuous with bounded 120 Hz steps. The planning occupancy grid has 4.8 m cells; it is not the continuous collision authority.
Reuse the movement, speed response, resource accounting, trail trimming/damage/lifetime and swept collision rules in `shared/simulation/`. Avoid maintaining an approximate second ruleset that can approve a path the real simulation rejects. Extract small shared pure helpers where needed, with parity tests before switching controllers.
Multiplayer runs at 120 Hz with at most twelve cycles and a configured 2,048-trail limit (`shared/multiplayer/config.js`). A tick is about 8.33 ms; that is the budget for the entire authority, not an allowance for each AI. Normal speed is 38.4 m/s, maximum turbo target is 96 m/s, brake target is half normal speed. Turbo/brake each have five seconds of full-charge use and sixty seconds of recharge; acceleration/deceleration are gradual. Trail rewards can change available charge. Read all of these from configuration rather than copy constants into the planner.
Scope the first release to the closed arena in single-player and multiplayer. Preserve the existing exterior-road controller and explicit transitions to/from it. Apply the new controller only to AI-owned cycles, never human seats. Remove cycle-specific JEV goal influence and scheduling only when the replacement passes its gates.
## Performance contract before tactics
The following are **provisional acceptance targets, not measurements**. Stage 1 must establish them on a named reference desktop/browser and the actual server runtime. Include a slower browser/device profile; do not judge speed solely on this development machine.
| Quantity | Initial ceiling or target |
| --- | --- |
| All AI work per authority tick, twelve bots | p95 ≤ 0.5 ms; p99 ≤ 1 ms, including geometry-index maintenance |
| Whole authority tick | p99 < 8.33 ms; no growing tick debt in sustained worst-case matches |
| Browser frame cost attributable to AI | p99 ≤ 1 ms at 60 Hz, including multiple fixed steps per frame; no AI-caused long tasks |
| Tactical planning | At most 2 bots per tick; each bot eligible every 12 ticks (10 Hz), stable ID-based staggering |
| Strategic/target refresh | Every 60 ticks (2 Hz), also staggered |
| Maneuver candidates | At most 12 per tactical decision |
| Detailed candidates/opponent replies | Best 3 candidates; at most 3 plausible replies for the selected threat |
| Local space estimate | At most 256 visited cells per bot decision, shared across candidates |
| Emergency guard | Every simulation tick, short bounded queries for each live bot |
Define additional explicit ceilings for segment tests, rollout steps and index updates in Stage 1. Count work for the **entire tick**, not merely per candidate. Profile a full 180-second match, dense intersections, repeated tight turns, trail retirement, deaths and catch-up bursts. Test the current trail cap and the maximum supported single-player arena history. Report p50/p95/p99/max, decision age, segment-test counts, allocations/GC, frame tails and server tick debt. Measure snapshot/serialization work as well as AI.
Use operation-count limits to choose behavior; wall-clock timing is telemetry, not the normal search termination rule. This makes replays independent of machine speed. If a budget is exhausted, retain a still-valid plan or select a bounded defensive fallback. Never interpret unfinished searches as proof that a corridor is safe. Safety work has reserved capacity; tactics cannot consume it. If even the guard cannot complete its required checks, release turbo and choose the best verified alternative, recording the overload. No fallback can guarantee survival once every route is lost.
Do not run a full flood fill or recursive search every tick. Use reusable typed arrays, generation stamps instead of clearing whole maps, and fixed-capacity candidate/rollout buffers. Avoid per-bot copies of the full race, trail list or occupancy grid. Maintain one shared trail spatial index incrementally; account for active endpoints, removals, damage and death fading. Bound index queries in dense buckets too. Overflow produces conservative uncertainty, never silently missing a barrier. A Web Worker is a later measured option, not the baseline: snapshot copying and delayed answers could erase its benefit.
## Controller structure
Keep a few focused modules under a proposed `shared/simulation/cycle-ai/` directory:
| Module | Responsibility |
| --- | --- |
| `controller.js` | Per-bot plan state, fixed scheduling, emergency guard and legal turn/control output |
| `geometry.js` | Shared local queries, bounded space estimates and index maintenance |
| `maneuvers.js` | Generate a small set of attack, escape and resource-control candidates |
| `evaluate.js` | Safety filtering, short rollouts, features and utility scoring |
| `config.js` | Named units, immutable runtime limits and versioned tunable behavior weights |
A candidate is a compact sequence of at most two turns with trigger ticks/positions, a short turbo/brake/coast schedule, target ID, expected exit and expiration. Internally use meters and seconds, converting at the adapter to existing cell-coordinate state. Execute only the next committed control, then reassess. Do not expose future AI plans as public opponent intent.
All bots planning on a tick read the same pre-command observation; apply controls afterward. Use stable ID ordering for tie resolution so array order does not create an accidental advantage. Bots may observe arena geometry and public cycle pose/control/resource information. They must not inspect human pending inputs, future AI commands or hidden private intent. For fair difficulty, record perception/reaction cadence explicitly; the emergency guard is a bounded reflex, not a license to react to unseen commands. Keep turn cooldown and speed/resource limits equal to the relevant player rules.
Split cheap danger handling from planning. Each tick checks the current short swept corridor and plan validity. It can cancel turbo, brake when that actually helps, or execute a previously validated escape. Tactical planning selects the next maneuver. Strategic planning picks a useful opponent/region and, later, complementary team roles. Keep target and maneuver hysteresis so tiny score changes do not cause aimless turn oscillations.
## Aggression that creates pressure
Generate candidates from geometry rather than random action trees:
- **Cross ahead:** identify an intersection with the opponent's likely route; reach it early enough to establish a trail, then turn into a verified exit. Cancel when the opponent can arrive first or the crossing traps the attacker.
- **Overtake and cut across:** in a parallel race, test whether a short turbo burst earns enough lead for the rear emitter/trail to block the other lane. Being slightly ahead with the bike's nose is insufficient.
- **Seal an exit:** use an existing wall/trail as one side of a trap; draw the missing boundary while keeping an exit for oneself. Reward reducing the opponent's reachable choices, not merely approaching it.
- **Pressure into a pocket:** take an open lane that leaves the target choosing between increasingly confined regions. Distinguish useful containment from following behind an opponent's barrier.
- **Brake to reverse the advantage:** let an opponent cross first when braking opens a safe follow-through or defeats its cutoff; resume normal speed as soon as the timing advantage is secured.
- **Disengage and preserve space:** abandon an attack whose escape is lost. Keep an available-space maneuver in every candidate set.
For each attack, compare arrival times under the real acceleration and reserve rules. Check when the emitted trail actually occupies the crossing, continuous body clearance and a safety margin for opponent response. Evaluate a small reply set: continued motion, a legal escape turn, and a relevant speed response. Select replies from nearby threats, using conservative motion envelopes for others. A full twelve-agent minimax search is explicitly out of scope.
Use a coarse occupancy/region estimate to rank promising routes, followed by continuous swept checks for finalists and the executed prefix. Include the attacker's newly emitted trail in rollouts: a two-turn maneuver can cut off its own exit. Model known trail expiration, shortening and damage; never count a barrier as already gone just because it is visually fading. A confirmed opponent turn invalidates its old motion forecast immediately, while expensive replanning remains scheduled and bounded.
Score opponent escape loss and territory advantage alongside personal survival, remaining exits and charge. Use a hard safety filter before scoring, rather than letting a large aggression bonus outweigh a predicted collision. When no safe candidate remains, choose the best survival opportunity and record the failure. Short-horizon verification is not a proof against every future opponent action.
Team coordination is a second-stage feature: assign at most one closer and one exit-denier to a target, with brief reservations for their intended corridors. Teammates stay collision hazards. Penalize redundant pursuit and blocking an ally's escape. Do not assume a human teammate follows the plan, and do not require perfect teammate cooperation for the attacker's own safety.
## Turbo and brake as part of the maneuver
Evaluate turn and resource choices together. Candidate schedules are short pulses followed by release, not simply “turbo whenever clear.” Integrate actual speed response and charge to compare arrival times; braking reduces speed, it does not instantly stop the cycle.
Spend turbo only when it creates a robust lead, reaches an exit before closure, or materially expands safe territory. Stop boosting once the required lead is achieved. Spend brake to yield a dangerous crossing, avoid entering a closing corridor or change an opponent's cutoff timing; reject braking that only delays an unavoidable impact while sacrificing the escape.
Price reserve use by context. Charge has higher value with nearby threats or few exits, and lower value when an immediate advantageous cutoff is available. Keep a configurable reserve floor for ordinary attacks, overridable for a verified escape or a sufficiently strong finishing opportunity. Test exhausted reserves and rewards; releasing a request matters because recharge depends on requested controls. Apply switching hysteresis to avoid turbo/brake flicker.
## Randomness without deliberate mistakes
Remove independent noise that can promote an inferior unsafe move. First reject unsafe and clearly dominated candidates. Then choose among alternatives within a small normalized utility tolerance of the best, provided each meets the same clearance, exit and reserve constraints. With only one strong move, take it deterministically.
Sample modest personality preferences once per round: cutoff preference, target stickiness, resource conservation, desired open space and preferred side in symmetric situations. Keep safety margins and computational ceilings outside this variation. Maintain commitment until invalidation or a materially better option appears; do not resample every frame.
Use an independent seeded PRNG stream per bot, round and policy version. A different bot's death must not consume this bot's random sequence. Replay the same seed, policy and observed inputs identically on the same supported runtime. Cross-engine bit-identical floating-point behavior is a separate test requirement, not a promise. Multiplayer remains server-authoritative; clients do not need to reproduce AI decisions to render them smoothly.
## Optional evolution from win rates
Begin with approximately 8–12 bounded parameters: cutoff reward, containment reward, space value, reserve price, attack reserve floor, timing margin above the hard minimum, target-switch cost, commitment duration and near-best selection tolerance. Separate these from non-evolvable safety minima, reaction rules and runtime ceilings. Store a versioned JSON policy with parameter bounds, training seeds, benchmark results and parent policy ID.
Run a plain Node.js headless tournament outside live matches. Mutate a small population around the champion, evaluate cheaply to reject poor candidates, then spend more trials on promising challengers. Use paired seeds with swapped spawn locations, team colors and teammates. Compare against the frozen current bot, several historical champions, defensive and aggressive opponents, plus mixed-policy teams. Separate duel and six/nine/twelve-cycle results. Rotate a held-out seed pool and keep the final promotion suite unseen during optimization.
Rank performance lexicographically: runtime gate, safety regression gate, then combined aggression-and-win fitness, then team win rate. Record draws/timeouts explicitly, treat the whole match as the statistical unit, and report sample counts and uncertainty. Do not promote on a handful of wins or repeated testing against the same seeds. Require a prespecified statistically supported improvement on held-out matches and no material regression in scenario failures, resource efficiency or performance tails. Keep diverse historical opponents to discourage cyclic overfitting. Human play review remains necessary: higher win rate can still mean dull stalling.
Public win rates are useful monitoring data, but depend on human skill, seats and teammate composition. Do not directly update weights after each public win/loss. Collect policy-tagged results, use them to identify weaknesses, validate a challenger offline, and activate a frozen approved version between matches with rollback available. Never mutate behavior midway through a match. Keep training opt-in and off the browser/server simulation loop.
## Optional tiny neural scorer
A small network is appropriate to **rank validated candidates**, not to replace collision checking or invent arbitrary controls. First establish the linear weighted scorer and evolutionary tuning baseline. Only keep the network if a controlled comparison earns its extra complexity under the same total decision budget.
Initial experiment: 32 normalized candidate features → 16 ReLU hidden units → 1 utility output. This is 528 weighted multiply-adds and 17 biases per candidate, or 545 float32 parameters (2,180 raw weight bytes). With twelve candidates it requires 6,336 weighted multiply-adds per decision. These are operation counts, not timing claims; feature extraction, geometric queries and rollouts can cost more than inference.
Implement inference with fixed typed arrays and ordinary JavaScript loops. No tensor framework, WebGPU, WASM runtime or additional download is required. Clamp/validate inputs and outputs; a non-finite score falls back to the linear scorer. Keep candidate generation, safety masks, resource rules, scheduling and deterministic PRNG outside the network.
Features should describe the candidate rather than a raw arena image: verified clearance/time-to-impact, own exit count, bounded reachable space, target escape reduction, crossing arrival margin, nearby threat distances/headings, current and predicted reserves, speed-control cost, ally interference and plan-switch cost. Include truncation/uncertainty flags so a capped space estimate is not treated as complete information. Features must use only permitted observations and have documented scales. The compressed view can miss global traps; the neural scorer does not remove that limitation.
Train and evaluate outside live play. A first experiment can use bounded weight mutation and tournament selection; compare its sample cost with evolving the smaller linear parameter set. Another option is offline supervised fitting to stronger bounded-search labels, followed by actual match evaluation. A score trained from match outcomes is not automatically a calibrated win probability. Do not call improved imitation loss improved gameplay without tournament evidence.
Version features, architecture and weights together. Benchmark linear and neural scorers with identical candidate sets, seeds and runtime ceilings; measure complete AI cost, not inference alone. Keep the linear policy if neural gains are inconclusive. The hybrid idea has precedent in [Stockfish's neural evaluation](https://stockfishchess.org/blog/2020/introducing-nnue-evaluation/); its results and specialized NNUE architecture are not evidence that a network will improve TRON.
## Implementation stages and acceptance gates
### 1. Establish the budget and baseline
- [x] Add a headless seeded tournament/performance harness and fixed encounter fixtures, retaining the classic baseline.
- [ ] Measure current twelve-bot full matches, near-cap trail histories, simultaneous threats and browser frame tails on named hardware/runtime versions.
- [ ] Specify segment-test, rollout and whole-tick operation ceilings; verify deterministic budget exhaustion and bounded memory. Ratify or tighten the provisional timing targets.
- [ ] Record win rates, unforced crashes, attack opportunities attempted/completed, resource spent per useful maneuver and timeouts. Separate performance timing runs from debug overlays/logging.
### 2. Ship a bounded competent controller behind a flag
- [ ] Extract rule helpers with parity tests; add shared spatial queries and staggered planning with emergency guard.
- [ ] Add escape/open-space candidates, explicit continuous validation and resource-aware timing. Test off-grid turns and speed changes.
- [ ] Pass runtime gates before adding attack search. Validate reset/death/round changes and release all controller/index state correctly.
### 3. Add attack maneuvers and controlled variety
- [ ] Add cross-ahead, overtake/cutoff, seal-exit and brake/yield fixtures, including adversarial replies and abort conditions.
- [ ] Test equal-arrival crossings, own-trail traps, fading/damaged/trimmed trails, multiple threats, depleted charge and a lost exit immediately after commitment.
- [x] Add near-best seeded choice with fixed policy preferences and safety filtering. Per-round personality sampling remains deferred.
- [ ] Add complementary teammate roles only if individual tactics fit the budget. Test ally obstruction and uncooperative human teammates.
- [ ] Compare against the frozen baseline with paired seeds; inspect rendered matches for readable attacks, competent escapes and absence of jitter/stalling. Repeat runtime gates with all tactics enabled.
### 4. Evolve parameters; optionally compare a neural scorer
- [x] Implement versioned policies and offline challenger tournaments with runtime checks and held-out matches. Automatic promotion is disabled; paired statistical and scenario review remain required.
- [ ] Demonstrate a repeatable team-win-rate improvement with uncertainty reported; retain historical opponents and rollback policy.
- [ ] Optionally implement the 32→16→1 scorer and compare it fairly with the tuned linear baseline. Reject it if it fails the same runtime gate or lacks meaningful gameplay gains.
### 5. Integrate and validate release readiness
- [x] Use the same controller in the local initially unbreached arena and multiplayer authority; keep client prediction/rendering separate.
- [x] Disable cycle JEV goal requests/influence for the new mode; preserve tank/Recognizer JEV behavior and the exterior cycle controller.
- [ ] Run unit/parity/replay tests, browser lifecycle checks, sustained authority load and multiplayer tests with human and AI seats. Confirm no added startup or frame hitching from policy/index setup.
- [ ] Record real-player impressions of cutoff pressure, deaths, turbo/brake intent and variety. Document remaining defects and actual benchmark hardware/results before recommending deployment.
Validation status: implementation measurements and completed checks are recorded above. Unchecked stages remain outstanding, especially cross-device/server benchmarking, advanced coordination, training promotion and human play review.
## Persistent trap attribution and aggressive evaluation — October 14
The authority now tracks coarse reachable space on a 40×40 graph. One cycle is sampled every 0.1 seconds, at most three opposing wall owners are tested per sample, geometry is capped at 2,048 segments, and all floods in one update share a 16,000-node visit budget. This is attribution only, never a replacement for swept collision. A new enemy wall must reduce reachable space substantially: the remaining area is at most 40% of the arena, and removing its newly added barriers restores at least 80 coarse cells and 1.5× the area. Mere proximity and following do not earn credit.
A detected trap remembers the original walls and region. New walls laid inside that region—including the victim filling it with a spiral—do not erase attribution. Before awarding an assist, the authority checks that the victim remains inside the original region and that surviving original barriers still enclose it. Removing the attacker's wall must restore meaningful escape space. There is no timeout. A direct hit on that same attacker's wall remains a direct kill, not an additional assist. Multiple attackers share at most one assist reward per victim. Hollow victim-colored roster dots show assists; filled dots remain direct wall eliminations. Compact snapshots carry assists, but not graph/history state.
Conservative limitations: this graph cannot prove every narrow passage or every driving maneuver is viable. Breaches expand to coarse cells and deny uncertain credit. Trail damage, trimming or retirement invalidate pending attribution rather than risk keeping credit across a sub-grid opening; this can miss valid traps, including when an unrelated trail changes. Budget exhaustion cannot award an assist. A trap completed between scans can be missed. Large-region partitions and non-cardinal road joins are deliberately excluded. Human attribution review and slower-hardware/Worker profiling remain outstanding.
Offline tournament fitness now awards 1 point per team win, 0.5 per direct enemy wall elimination, 0.35 per trap assist, and 0.1 per newly created confinement. Confinement and assist rewards are separately capped to one shared reward per victim per match. Runtime compliance ranks first; combined fitness ranks next, with win rate breaking ties. Thus aggression is a primary objective, not merely a tie-breaker for equally winning passive policies. Artifacts retain raw wins, draws, direct kills, assists and confinements. Promotion remains manual, with held-out checks and human gameplay review; no evolved weights have been deployed.
Validation on Apple M2 Max, Node 22.22.0 and Chrome: 561 unit tests pass, including persistent 90-second trapping with self-fill, escape/opening invalidation, duplicate/direct/friendly exclusions, and shared reward ranking. Chrome roster checks pass; hollow assist dots visually inspected. A 12-cycle/2,048-trail stress fixture (`node tools/ai/trap-stress.mjs`) measures scheduled attribution work at p95 0.152 ms, p99 0.187 ms and max 0.191 ms. One 180-second bounded-AI match produces one trap assist despite zero direct wall kills, with total simulation tick p99 0.660 ms; the match times out, so this is not evidence of improved winning or entertainment. Evolution smoke pipeline and production build pass. Implementation is local, not deployed.
## First aggression evolution trial — October 14
Completed `node tools/ai/train.mjs`: four policies × two generations × four training seeds × four spawn rotations (128 training matches), then eight fresh seeds × four rotations for both the selected candidate and baseline (64 evaluation matches). Each match allows 180 seconds. Mutations are seeded (44123); historical opponents remain in training. Three worker threads execute simulation matches without timing instrumentation. Serial, uncontended performance checks gate each generation winner and the final candidate. The neural scorer remains disabled.
Selected policy: `aggression-1-0`. Full weights, training scores, per-match held-out results, configuration and serial timing are retained in [the experiment artifact](experiments/aggression-2026-10-14.json). No automatic policy promotion or deployment occurred.
| Fresh-seed results (32 matches each) | Baseline | Candidate |
|---|---:|---:|
| Direct enemy wall eliminations | 3 | 5 |
| Trap assists (shared credits) | 9 | 10 |
| Confinements (shared credits) | 42 | 44.17 |
| Wins | 4 | 5 |
| Timeouts/draws | 16 | 13 |
| Losses | 12 | 14 |
| Mean combined fitness | 0.4016 | 0.4818 |
This is a modest exploratory gain, not established superiority. Candidate fitness is better on five seed groups, worse on two and tied on one; the approximate paired t interval across eight independent seed groups for the mean fitness difference is −0.301 to +0.461 (95%), spanning no improvement. Four spawn rotations within a seed are correlated. More losses and still many timeouts warrant gameplay review. These tournaments use closed arenas; they do not establish the same result under multiplayer wall-breach rules.
Serial M2 Max/Node 22.22 timing for the candidate: AI p99 0.186 ms, full tick p99 0.598 ms, maximum full tick 2.528 ms. Chrome 155 on macOS verifies the exact selected weights, no page errors, and a rendered frame AI p99 of 0.300 ms; the isolated 12-cycle/2,048-trail browser planner fixture gives p99 0.400 ms. Captured and inspected the arena rendering. Sixteen focused AI/fitness tests and the production build pass. Visual rendering does not establish that the opponent feels more fun.
To play locally, open `http://127.0.0.1:5173/?cycleAITrial=1` and choose LIGHT CYCLES. This development-only route loads `test-results/cycle-ai-trained.json`, validates the policy and requires its serial runtime check to have passed. The normal URL retains the baseline, and multiplayer authority is unchanged. Neither the loader nor the training artifact URL appears in production bundles. To reproduce the saved trial without retraining, copy the experiment JSON above to `test-results/cycle-ai-trained.json`. Browser check: `node tests/browser/cycle-ai.mjs --trial`.
## Playtested policy selected for solo and MP — October 14
At the user's request after playing the trial, `aggression-1-0` is now the shared default in `shared/simulation/cycle-ai/policies/aggression-v1.js`. Both solo arena bots and the multiplayer authority use these frozen weights. Solo LIGHT CYCLES keeps bounded AI enabled after a breach. Human controls and road mode remain unchanged. Training artifacts cannot replace this gameplay policy automatically; further evolution is separate. The original `bounded-v1` weights remain available as `BASELINE_POLICY` for reproducible offline comparisons.
The earlier trial-only restriction no longer applies to these weights: normal local solo and MP games use them without a query parameter. This is a local source change, not a production deployment. Focused authority/AI tests verify both paths select the same policy; two-client Chrome MP lifecycle checks and the Worker dry run pass.
## Extended evolution reached a plateau — October 14
`node tools/ai/evolve-until-plateau.mjs` completed four generations and stopped after three consecutive generations without a qualifying validation improvement. This is a plateau for this mutation/search setup, not proof that no better policy exists. The validation winner `plateau-0-0` scored 0.4484 mean fitness on 32 fresh matches, versus 0.4664 for the playtested `aggression-1-0` and 0.3781 for `bounded-v1`. The challenger had 5 wins, 24 draws and 3 losses; the playtested policy had 4 wins, 14 draws and 14 losses. More survival did not produce better combined aggression fitness. Neither the trial artifact nor gameplay default was replaced.
Full results: [plateau experiment](experiments/plateau-2026-10-14.json). Serial M2 Max Node timing passed: AI p99 0.252 ms and full simulation tick p99 0.661 ms. Closed-arena evaluation and small seed count remain limitations. Shared default integration passes 562 unit tests, Chrome AI and two-client multiplayer checks, and the Worker dry run; production remains unchanged.
## Research findings — October 14
### Faster-win reward follow-up
Offline evaluation now adds `fastWinBonusPoints × max(0, 1 − seconds / matchLimitSeconds)` only for the winning team. `AI_FITNESS.fastWinBonusPoints` defaults to 0.5, in addition to the base win point: a 90-second win in a 180-second evaluation earns 1.25 before aggression rewards. A deadline win earns 1.0. Losses and timeouts earn no speed bonus, while earned wall, assist and confinement credit remains. Missing or invalid timing earns no bonus. Confinement credit is already recorded when a trap is detected and is not discounted by time spent filling the trapped region.
Match results carry their configured limit, and tournament worker rows include the speed contribution. Fitness weights are retained in new training artifacts. Earlier artifacts retain their original scoring and must not be compared directly with new scores. Four fitness tests and the offline evolution smoke pass. No live simulation work or gameplay-policy change is introduced; a new training comparison is still required to establish behavioral improvement.
### Original sources
Original sources suggest several useful directions:
- [Andy Sloane's winning 2010 AI postmortem](https://www.a1k0n.net/2010/03/04/google-ai-postmortem.html): bounded search plus obstacle-aware territory ownership and chokepoint/chamber analysis. Separate contested play from isolated space filling. His [simplified JavaScript opponent](https://www.a1k0n.net/code/tron.html) is directly inspectable.
- [Animal–Animat Coevolution, 1998](https://www.demo.cs.brandeis.edu/papers/tronsab98.html): eight obstacle-distance sensors fed evolved controllers. Early perceptron evolution produced passive spirals; human-opponent evaluation helped escape this self-play equilibrium. The authors identify missing opponent position/heading as a sensory limitation.
- [Modular Reinforcement Learning for Playing the Game of Tron, 2022](https://doi.org/10.1109/ACCESS.2022.3175299): separate interaction and isolated-survival models, with board channels for arena walls, own head/trail and opponent head/trail. Positive per-step survival rewards encouraged avoidance; negative per-step rewards produced more aggressive play in their experiments. Their convolutional network is larger than our proposed tiny scorer. Small two-player grids and a simplified search baseline limit transfer of strength claims to our arena.
- [Simultaneous-move MCTS study, 2013](https://repository.tudelft.nl/record/uuid%3A814a4ba7-9508-410e-ba12-79de980cb67b): explicitly model simultaneous decisions; decoupled selection performed best among tested variants. It does not establish suitability under our twelve-cycle runtime budget.
- [Armagetron close-combat source](https://github.com/ArmagetronAd/armagetronad/blob/trunk/src/tron/gAIBase.cpp): `ThinkCloseCombat` uses relative opponent position, direction and speed, reaction allowance, wall sensing and explicit cutoff comparisons, with emergency survival checks. This is a useful real-time interception reference, not evidence of learned turbo overtaking.
Proposed next experiment, not implemented: keep bounded geometry/safety work, expose opponent-relative motion and speed reserves to a small policy controlling turn/turbo/brake jointly, and train against diverse historical opponents with explicit overtake/interception scenarios. Evaluate aggressive wins and successful confinement rather than survival time alone. A modest time penalty needs testing against suicide/recklessness. Maintain runtime as the first gate; use expensive search offline if it helps produce training examples. Our variable-speed, multi-cycle arena requires its own validation.
## Faster-win evolution completed — October 14
Ran `node tools/ai/evolve-until-plateau.mjs --fast-wins` using Node 22.22.0 on Apple M2 Max. Start from frozen `aggression-1-0`; mutation seed 882913, training seeds beginning at 220000, validation 240000–240003, final 300000–300007. Each seed has four team-position rotations, with 180-second match limits. Runtime measurements run serially after parallel match evaluation.
All three generations failed the predeclared improvement criterion (fitness gain at least 0.02, improvements in at least three of four validation seed groups, and runtime pass). Training-selected challengers had validation fitness deltas −0.1560, −0.3443 and −0.2188; only the first improved any seed group (one). Stop after three consecutive failures. This establishes a plateau for this bounded weight-mutation experiment, not absence of better policies. No neural policy or new action repertoire was trained.
The run executed 352 matches: 16 initial validation, 192 training, 48 challenger validation and 96 final evaluations. Because the champion stayed unchanged, two final 32-match evaluations use the same policy and seeds and produce identical results; they are a reproducibility check, not independent evidence.
| Fresh evaluation | Retained playtested policy | Original baseline |
|---|---:|---:|
| Wins / draws / losses | 5 / 19 / 8 | 5 / 12 / 15 |
| Mean winning duration, seconds | 147.46 | 151.20 |
| Direct wall eliminations | 6 | 6 |
| Trap assist credits | 9 | 12 |
| Confinement credits | 52.5 | 50 |
| Total faster-win bonus | 0.4519 | 0.4000 |
| Mean combined fitness | 0.5266 | 0.5500 |
Only five wins per policy contribute to mean winning duration, and they need not be the same scenarios. Eight independent seeds with correlated spawn rotations are insufficient to claim a reliable speed advantage. The retained policy has more draws and slightly worse combined fitness than the original baseline here. Neither this comparison nor unsuccessful mutations demonstrate that the new reward reduced spiraling. Trial and gameplay weights remain unchanged.
Serial runtime passes the existing gate: AI p99 0.235 ms and full tick p99 0.478 ms; maximum full tick 10.860 ms exceeds one 120 Hz tick budget, so the p99 result is not a guarantee of no overruns. Closed-arena fixtures do not establish performance with MP breaches or on slower/server hardware. Four fitness unit checks pass; script syntax and generated-document checks pass. No browser test or deployment was needed for this offline experiment.
Preserved [full weights, rows, configuration, validation and timing](experiments/fast-wins-2026-10-14.json). Further work should change the training situations or available decision structure rather than claiming more repetitions of this search have found a better AI.
---
Source: /fun/TRON/docs/history/
---
title: Project history
topTitle: TRON
subtitle: Original milestones and earlier development notes
---
[← Current implementation plan](../index.html)
This archive preserves the original plans and earlier implementation notes. Later decisions in the current plan take precedence.
## Historical structured-chase plan
The following records the original M0/M1 work and decisions. Its timed escape, capture and scripted encounter requirements are superseded by the [current simulation plan](../index.html).
Prepared 2026-09-12 from the [shared design conversation](https://chatgpt.com/share/6aa58280-6f74-83e9-a410-aa7c54032fe4). `../../Goal.md` defines the experience; `../../AGENTS.md` guides implementation.
A first playable is implemented as of 2026-09-12. Open the game at http://127.0.0.1:5173 after `npm run dev`. It includes a roughly 75-second authored canyon, procedural tank/Recognizer models, driving and collision, chase camera, positional synthesized audio, pause/restart, capture, and cannon fire. The user explicitly added shooting during implementation; three projectile hits destroy a Recognizer.
Technical prototype work is substantially complete; M0/M1 are not represented as fully signed off. The supplied multi-angle film stills have now been inspected and used for a second art pass. Remaining items include final asset fidelity, actual Safari and human playtesting, and the unchecked detailed items below. See [Validation](../validation/index.html) for evidence and limitations. Supporting documents: [Assets](../assets/index.html) · [References](../references/index.html).
## High-level roadmap
| Milestone | Result | Completion gate |
| --- | --- | --- |
| M0 — Foundation and visual study | Reproducible browser app, reference/asset inventory, representative canyon scene, model import path, simulation skeleton | Scene runs locally and as a production build; references and gaps documented; lifecycle and rendering smoke checks pass |
| M1 — Playable chase | One 60–90-second authored escape with tank handling, collision, chase camera, one Recognizer, sound, and restart | Full win/fail/restart loop, fair threat, controllable drive, recorded browser and performance checks |
| M2 — Fidelity and feel | Refined or replacement vehicles, closer terrain/material matching, better sound, camera and encounter tuning | Reference comparisons and playtesting resolve the main visual and experiential gaps |
| M3 — Browser release candidate | Loading polish, quality settings, accessibility basics, compatibility/performance fixes, asset credits | Repeatable production build and documented release checks; ready for a separately authorized deployment |
Keep later milestones flexible. Basic cannon combat was explicitly requested and is implemented. M1 playtesting should determine whether additional enemies or more elaborate combat add value before expanding scope.
## Working technical decisions
| Area | Initial decision |
| --- | --- |
| Runtime | JavaScript ES modules, Three.js `WebGLRenderer`, Vite; plain HTML/CSS overlay |
| Browser baseline | Desktop Chromium and Safari on the development Mac; Firefox smoke check before M1 closes; WebGL 2 required |
| Simulation | Fixed 60 Hz update with interpolated rendering; cap accumulated elapsed time and catch-up steps; pause on hidden tab/focus loss |
| Tank | Kinematic arcade controller on an essentially flat driving surface; acceleration, braking, speed-dependent steering, limited reverse |
| Collision | Simplified authored canyon boundaries and tank footprint; sweep fast movement, slide along walls, handle corner contacts |
| World | Authored route data with canyon sections, collision boundaries, waypoints, encounter volumes, start and exit |
| Camera | Low third-person chase, damped position/look target, turn anticipation, obstruction correction |
| Enemy | Explicit approach/overtake/intercept/recover states following authored flight paths and bounded player-relative targets |
| Assets | GLB/glTF through a replaceable model adapter; reusable procedural facets for terrain; original or permitted audio |
| Rendering | Solid flat-shaded/faceted forms, controlled gradients and fog; optional restrained bloom/softness/grain |
| UI and tuning | Minimal DOM HUD; development panel for handling, camera, enemy, palette, fog, bloom, and render scale |
| Persistence | Settings only if useful; no backend or saved progression in M0/M1 |
The current [Three.js renderer documentation](https://threejs.org/docs/pages/WebGLRenderer.html) specifies WebGL 2 and no WebGL 1 support. Use the documented [GLTFLoader](https://threejs.org/docs/pages/GLTFLoader.html) import path. At setup, choose compatible stable dependencies, check the [Vite runtime requirements](https://vite.dev/guide/), and commit the lockfile rather than guessing versions now.
Suggested structure, created as needed:
```text
src/
main.js
game/ # lifecycle, fixed-step loop, input, configuration
simulation/ # tank, collision, recognizer, encounter state
rendering/ # scene, camera, materials, model adapters
audio/ # engine, ambience, positional threat sounds
levels/ # authored route and encounter data
ui/ # start/results/HUD and development tuning
public/assets/ # permitted models and sounds
docs/ # references, asset manifest, validation records
tests/ # meaningful simulation and browser checks
```
Simulation should expose state to rendering and audio without depending on scene meshes. A reset should recreate run state from route/configuration data while safely reusing loaded assets.
## M0 — Foundation and visual study
### Objective
Make the first implementation session produce a reproducible app and a representative visual scene that can be compared with the film. Establish the model/scale/camera conventions and technical scaffolding needed for M1. Full driving and pursuit are M1 work.
### M0.1 — Reference and asset preparation
- [x] Create `docs/references/_index.md` with identified original-1982 frames or links: tank side/front/rear views, Recognizer front/side/overhead views, canyon-wide composition, and chase-camera framing. Record provenance and timestamp where available; distinguish film frames from fan renders.
- [x] Inspect the actual reference images. Write observations on silhouettes, relative scale, wall profiles, surface gradients, lit details, and framing. Do not use the rejected generated images as vehicle specifications.
- [x] Create `docs/assets/_index.md`: candidate source URL, creator, format, license/use constraints, attribution, size, suitability, and selected/placeholder status. Evaluate vehicle proportions and model quality before selection.
- [x] Prefer suitable open models; record paid candidates and price for a later concrete purchasing decision if needed. Continue with reference-based stand-ins if no usable model is available.
- [x] Record audio needs: engine/load loop, background electronics, Recognizer approach/pass/intercept, collision and outcome cues. Plan synthesized placeholders where usable recordings are unavailable.
Deliverable: a concise reference guide and honest asset inventory, with no claim that uninspected or unacquired assets are ready.
### M0.2 — Application scaffold and lifecycle
- [x] Initialize npm/Vite with JavaScript modules and Three.js; add `dev`, `build`, and `preview` commands, lockfile, `.gitignore`, and setup README. Record the required Node version.
- [x] Create the canvas, resize handling, bounded pixel ratio/render scale, loading/error states, and a useful WebGL 2 unsupported message.
- [x] Create one owned animation loop and explicit loading/ready/running/paused/disposed lifecycle. Bound delta time and catch-up work; reset the accumulator on resume.
- [x] Establish input state with blur cleanup. Reserve WASD/arrow driving, Escape pause, R restart, and M mute for M1; prevent page scrolling while gameplay owns these keys.
- [x] Provide cleanup paths for listeners, renderer resources, and future audio. Handle context loss with a visible recovery/reload state.
Deliverable: clean local startup and production preview, stable resizing and tab switching, and no duplicated loops.
### M0.3 — Representative canyon and model study
- [x] Build a short authored test section containing a straight, bend, irregular wall heights, and an opening. Keep its visual geometry separate from future collision data.
- [ ] Add tank and Recognizer preview adapters with documented dimensions, origin, forward axis, and uniform scaling. Support GLB loading plus labeled stand-ins; preserve source mesh proportions.
- [x] Build a stationary chase composition and an inspection camera. Start with the tank near the lower 15–20% of the image as a tuning hypothesis, then compare with references.
- [x] Establish solid faceted terrain, restrained blue/purple surface gradients, dark distance and sparse emissive details. Add optional postprocessing only after the base materials read correctly.
- [ ] Add development controls for palette, fog, exposure, bloom, camera offset/FOV, and render scale; provide a reset-to-defaults action and a way to record settings.
- [x] Capture a representative chase view and vehicle inspection views. Note remaining silhouette/material gaps alongside the references.
Deliverable: a visual test scene and saved baseline settings. If vehicle assets remain temporary, the milestone report must say so.
### M0.4 — Validation and handoff
- [x] Verify install instructions, development startup, build, and production preview from documented commands.
- [x] Add a small browser smoke check for canvas initialization and startup errors, using Playwright if compatible with the environment. Visually inspect a real GPU-rendered frame; headless launch success alone is insufficient.
- [ ] Exercise resize, tab hide/resume, input blur cleanup, unsupported WebGL behavior, and disposal/reinitialization without duplicated resources.
- [x] Record browser/OS/hardware, viewport and render scale, scene statistics, and observed frame timing in `docs/validation/_index.md`.
- [x] Summarize reference coverage, asset availability, visual gaps, and any changed assumptions before starting M1.
### M0 exit criteria
The app renders its representative scene from both dev and production preview without uncaught errors; basic lifecycle checks pass; model and world conventions are documented; reference comparisons and selected/temporary asset status are recorded. A first visual direction is reviewable. User feedback can refine it while independent M1 controller work proceeds.
## M1 — First playable chase
### Objective
Deliver a complete short escape through one authored canyon. Prioritize handling and camera first, then layer in a readable threat and sound. Initial art must follow M0's reference direction, with unresolved fidelity work explicitly carried into M2.
### M1.1 — Tank handling and collision
Depends on M0's loop, conventions, and test scene.
- [x] Implement throttle, braking, reverse, acceleration limits, drag, maximum speed, and speed-sensitive steering. W/up accelerates; S/down brakes and reverses after stopping; A/D or left/right steer.
- [x] Start with approximately 22–31 m/s top-speed experiments, reflecting the conversation's proposed 50–70 mph feel. Adjust against terrain scale and corner readability; these are tuning values, not fixed requirements.
- [x] Use a tank footprint against simplified static boundaries. Sweep the intended displacement to prevent tunneling; resolve sliding and corners without instability or steering through walls.
- [x] Make wall contact slow the tank and produce mild feedback. Ordinary contact alone does not end the run; loss comes from the defined pursuit rule.
- [ ] Expose acceleration, braking, reverse limit, steering curve, drag, and collision debug display. Preserve a repeatable test course.
- [x] Test displacement consistency across render rates, braking/reverse transitions, high-speed wall contact, glancing slides, and corner recovery.
Acceptance: the tank remains within the drivable corridor, behaves consistently at different render rates, and can be driven through a bend and recovered from wall contact without resetting.
### M1.2 — Chase camera and route
Depends on M1.1; route art and layout can be developed alongside camera tuning.
- [x] Implement damped following and look-ahead tied to heading/turning. Resolve obstructions between camera target and desired camera position with a small clearance volume; recover smoothly when clear.
- [x] Author a route with a learning straight, tight passage, blind bend, wider reveal, second bend, interception opening, and exit. Use landmarks and geometry to make the forward path readable.
- [x] Tune the full route for a 60–90-second successful run; derive length from measured average driving speed instead of assuming top speed throughout.
- [x] Keep spawn and exit volumes clear of collision boundaries. Add route progress tracking and reproducible encounter triggers; backward movement must not retrigger completed encounters.
- [ ] Test camera behavior near walls, inside bends, at low speed/reverse, on restart, and at different aspect ratios. Add reduced camera motion as a simple setting.
Acceptance: an empty-canyon drive is enjoyable and readable before pursuit is added; the camera avoids walls and provides enough forward visibility to react.
### M1.3 — Recognizer pursuit and game rules
Depends on a stable route and controller.
- [x] Add one Recognizer with approach, overtake, intercept, recover, and exit/reset states. Use authored flight lanes above/within clear sections of the canyon and bounded tracking toward the player's progress.
- [x] Stage an audible approach behind the player, an overhead crossing, and a descent into a wider interception area. Use distance/progress triggers with local state timers so pacing tolerates faster and slower players.
- [x] Define the initial failure mechanic: a telegraphed capture zone beneath the Recognizer builds capture exposure while the tank remains inside. Leaving the zone reduces exposure; a configurable threshold ends the run. Show exposure on the HUD.
- [x] Give the capture zone a visible boundary or clear world cue. Ensure the interception leaves a traversable escape lane and never activates before its warning. Tune reaction time from actual runs.
- [x] Reach the exit alive to win. Resolve capture before exit if both occur in the same simulation tick, and prevent multiple outcome transitions.
- [x] Define recovery for a stalled/reversing player and ensure no permanent obstruction or unbounded enemy chase target. Keep tuning deterministic for repeatable testing.
- [ ] Test encounter order, one-shot triggers, capture entering/leaving, threshold behavior, terminal-state priority, and reset. Add a second Recognizer only after the single-enemy loop meets acceptance.
Acceptance: the pursuer creates visible and audible pressure, capture is avoidable through player action, both victory and defeat are reachable, and failure has a clear explanation. This capture rule is a provisional game design choice, not a claim about the film.
### M1.3a — Cannon combat (added at user request)
- [x] Fire with Space or left mouse; hold to repeat at a bounded cooldown.
- [x] Turn the level turret independently with Q/E; assist projectile height only when horizontally aligned with a target (updated at user request).
- [x] Simulate projectile travel with substeps and Recognizer hull/crown/leg hit volumes.
- [x] Show target acquisition, remaining enemy health, muzzle flash, hit flash, debris, and kill count.
- [x] Destroy a Recognizer in three hits and cancel its capture zone.
- [x] Verify stationary-target destruction and all three encounters during a complete driven route.
- [ ] Collect user feedback on aim assistance and whether three hits feel right.
### M1.4 — Sound and player flow
Depends on lifecycle and encounter events; audio placeholders can be prepared earlier.
- [x] Unlock the audio context from the Start action. Add an engine loop driven by speed/load, quiet ambient electronics, positional Recognizer cues, wall-impact feedback, and short win/fail cues.
- [x] Attach the audio listener to the camera, update sound positions from simulation state, and tune attenuation so the approach is heard before the overhead reveal. Audio must not be the only threat cue.
- [x] Provide start instructions, minimal route/capture HUD, pause/resume, mute/volume, outcome text, and R/button restart. Keep technical tuning controls out of ordinary player flow.
- [x] Pause simulation/audio on focus loss; require an explicit resume action. Clear held keys so a missed keyup does not keep the tank accelerating.
- [x] Reset vehicle, camera, enemies, encounter flags, timers, capture exposure, HUD, and audio without reloading assets or the page. Repeated restarts must not layer loops or events.
Acceptance: a new player can start, understand the objective and controls, pause, win/lose, and immediately replay. Muted play remains understandable.
### M1.5 — Verification and playtest
- [x] Run focused simulation tests from M1.1/M1.3, then browser checks for start, real input-driven movement, pause/resume, both outcomes, and restart. A test hook may place a run near a scenario, but must exercise real collision/outcome rules rather than directly setting a win flag.
- [ ] Manually complete a normal winning run and intentionally lose; check that the intercept is fair at both cautious and intended driving speeds.
- [ ] Check current installed Chromium, Safari, and Firefox; record versions and any unsupported behavior. Browser automation can cover Chromium, but does not substitute for checking actual Safari.
- [x] Run 10 restarts and a 10-minute repeated-play session. Inspect console errors, duplicated audio/listeners, and renderer resource counts for continuing growth.
- [ ] Record a timed run, representative screenshots, and notes on tank weight, corner readability, pursuer scale, visual resemblance, and sound timing. Collect user playtest feedback; report subjective judgments as pending until received.
- [x] Update `docs/validation/_index.md`, asset credits, README controls, and milestone status with the actual results and remaining M2 work.
### Performance targets and measurement
Choose and record the baseline development machine in M0. Initial targets are 60 fps at 1920×1080 with pixel ratio capped at 1, and a stable 30 fps lower-quality fallback with reduced render scale and postprocessing. After shader/asset warmup, aim for 95th-percentile frame intervals of at most 20 ms at normal quality or 35 ms in fallback during the full chase. Record measurements rather than claiming broad hardware coverage.
If the target is missed, first reduce postprocessing and pixel cost, then inspect draw calls, repeated geometry, per-frame allocations, and resource leaks. Re-measure after a relevant fix; do not remove the defining terrain/vehicle forms simply to hit a frame counter.
### M1 exit criteria
The full route supports a roughly 60–90-second successful chase, with working collision, readable camera, at least one fair Recognizer encounter, positional sound, victory, defeat, pause, and immediate restart. Required checks and performance measurements are recorded. There are no known blocking control/collision/reset defects. Remaining visual or sound placeholders are listed, and user feedback or its pending status is recorded separately from technical completion.
## Risks and working responses
| Risk or uncertainty | Response |
| --- | --- |
| Suitable vehicle models are unavailable or inaccurate | Evaluate candidates early; use replaceable reference-based stand-ins and record the fidelity gap |
| Generic neon appearance replaces the requested film look | Compare fixed camera captures to original references; fix silhouettes, surface tones, and terrain geometry before adding effects |
| Heavy handling becomes frustrating in tight turns | Tune camera, route width, speed, and steering together in the empty canyon |
| Scripted pursuit breaks at different player speeds | Trigger by route progress and use explicit recover behavior; test stopping and reversing |
| Threat is arbitrary or unavoidable | Telegraph capture, retain an escape lane, and verify successful avoidance with normal controls |
| Browser audio or effects fail on the baseline | Unlock sound through user input, show visual warnings, retain render-scale/postprocessing fallbacks |
| M1 expands into a full game | Keep one route and one enemy until the complete loop passes its gates |
## Design topics to revisit before pursuit implementation
These do not block M0 or the empty-canyon driving work.
- **Player agency:** the user requested shooting, and the first playable now supports destroying Recognizers or evading their capture zones. Playtest the aiming assistance, hit count, and timing before considering alternate passages or further combat complexity.
- **Tension and pacing:** include a brief safe opening and a moment of relief between threats. Continuous maximum pressure can make the Recognizer feel ordinary and leave no room to appreciate the environment.
- **Faithful appearance versus readable play:** keep vehicle and terrain forms faithful while tuning corridor width, camera offset, warning time, and speed for player control. Treat those as deliberate adaptations and judge them in motion.
- **Distinctive sound identity:** aim for separate, recognizable engine and pursuer signatures. A muffled approach, overhead pass, and receding threat should communicate encounter state without requiring the player to look backward.
## Next implementation action
A subsequent user correction removes smooth route interpolation: straight wall sections now meet at hard authored corners. Q/E manually turns the turret; the barrel stays level and bullets receive height assistance only. External replacement model candidates and preview comparisons are in the asset inventory; source downloads require a signed-in Sketchfab account.
The September 12 reference pass replaces the home screen with an animated blue terminal, rebuilds the tank and Recognizer, and changes the maze to flat-topped slabs and grid courts. See the updated reference inventory.
Playtest the browser build, collect feedback on handling, camera, cannon aiming, visual proportions, and pursuit timing, then refine the remaining M0/M1 items before expanding scope. Asset purchases and deployment have not been performed.
## Recognizer crush attack — September 13
- [x] Use the supplied attack frames for turning legs, inward closure and a vertical drop.
- [x] Require recent direct sight and map clearance; commit the strike position before folding so an escaping tank can be missed.
- [x] Recover altitude and reopen after impact. A crushed tank stays in the scene until R resets; autonomous simulation continues.
- [ ] User review of motion timing and final pose against the film sequence.
- [x] Give Recognizers forward-only horizontal thrust and persistent momentum; steer to avoid aircraft instead of applying lateral propulsion. Keep the body level and slow before committing to a crush.
- [x] Increase Recognizer drag and hold heading while braking inside the strike footprint to reduce attack-approach circling.
- [x] Replace competing close-range pursuit/avoidance steering with yielding to a nearer aircraft, turn-in-place alignment, and stable hovering even when a wall prevents descent.
- [x] Use Space Paranoids as the game title and home branding.
- [x] Give V aerial view lighter fog, a visible floor, and grid fading appropriate for its camera distance.
- [x] Add the terminal-to-grid opening and camera descent to Clu, with pause/resume, skip controls and reduced-motion support.
- [x] Extract tank, cannon and two Recognizer samples from the user-supplied local video. Add stereo world emitters, Doppler, wall muffling and a sound audition page.
- [ ] User audition of the film-derived cuts for background score/dialogue and loop character.
- [x] Frame nearby visible Recognizers with a smooth upward camera tilt, modest pullback and wider field of view while retaining the tank top in frame. Keep aerial and studio cameras independent.
- [x] On confirmed Clu destruction, clear sightings, share the result by radio, finish recovery and return to patrol. Ignore subsequent stale sightings of the wreck until reset.
- [x] Suppress the repeating tank-loop tone with a targeted notch and softer high-frequency cutoff.
September 13 updates: the terminal remains black until Return, with a block cursor, uneven typing cadence, character clicks after browser audio unlock, and a synthesized two-tone access beep. Recognizers patrol during the camera descent while Clu starts at maximum speed with forward throttle held until W is released (pause/blur also clears it). The tank starts one maze width outside the entrance; radio reports travel only 1,000 feet (304.8 m), including vertical separation. Gameplay has no top header; standby is small top text, and any key resumes. Held driving/turret keys carry through resume; fire/reset/view keys are consumed. The follow camera orbits with the turret. Ground/maze fog remains identical within 250 m and tapers to retain distant silhouettes. Wall contact retains sliding speed and clears blocked drive pressure for immediate reverse. Tank sound is 30% quieter with subtle turn-dependent pitch/filter modulation.
Opening pursuit: three of the five Recognizers start 120–180 m behind Clu in a staggered formation, already moving forward. Their initial pursuit comes from normal line-of-sight sensing. They remain autonomous during the zoom; the other two patrol the maze.
September 13 pursuit/destruction revision: the three opening pursuers now start abreast 300 m behind Clu, 130 m apart. Nearby squadmates hold separate lateral interception lanes and slow when ahead of the group, creating a broad net. Coordination uses each craft's own target memory and neighbors within radio range. The shot-assistance correction supersedes vertical-only aiming: narrow-cone acquisition remains manual, but the fired projectile aims at a constant-velocity 3D intercept and never homes afterward. Destruction partitions the posed imported model into 18–27 random surface groups, with retained trim, a brief yellow-white flash, inherited momentum, varied spin, gravity, coarse wall/floor collisions and 10–12 s cleanup. Camera interest briefly follows the breakup.
Latest control/coordination corrections: F returns the turret smoothly to forward; Q/E cancels centering. Driving, turret control and firing work throughout the opening zoom. Enter skips the opening; Space fires. Held inputs survive the transition into gameplay, and brief fire taps are queued until a simulation step. Nearby pursuers within 180 m dynamically choose the nearest craft to their own last-known target as lead; it pursues directly while supports flank and trail. Fixed formation slots are removed. Lead destruction or separation triggers immediate reassessment; isolated aircraft pursue independently.
Terminal presentation: text reduced by roughly 30% on desktop, with a subtle four-pixel CRT raster overlay confined to the home screen. Scanlines dissolve with the existing opening text fade and are absent from gameplay.
Turbo/ambush revision: T starts a ten-second 1.75× speed boost, with forward thrust unless braking, no stacking during an active boost, and a smooth return to normal speed. Shift+T opens development tuning. Controls appear centered at the top after three seconds without input, wrapping only when necessary. All five Recognizers now start in pursuit 300 m behind Clu, spaced 100 m apart, and the opening frames the wider group. Stomp planning linearly predicts from the latest observation, accounts for approach, folding and drop time, and includes braking drift at commitment. The committed drop does not track later tank maneuvers.
Final turbo/placement tuning: turbo raises Clu to 55 m/s (2.5× cruise), substantially faster than Recognizer pursuit. It can activate once per 60 simulated seconds, including its ten-second active period; the top-right meter shows boost/recharge/ready state. The five initial aircraft retain the reference-inspired irregular wide line at roughly 300 m distance, but normal altitude is restored so their feet clear the 54 m maze roofs.
Aerial camera: V still toggles the overview; wheel/two-finger scrolling smoothly changes its scale from 0.25× to 4× (150–2,400 m height). Zoom is retained across V toggles, resets with a new run and also works while paused. Ground extent and camera far clipping expand for distant views.
## September 13 — carrier and localized breakup
- [x] Convert the user-supplied Sark carrier DAE with all textures, retaining original proportions, and add an orbitable `/carrier.html` preview.
- [x] Add a straight background flight at 360 m center altitude and 24 m/s, tied to simulation time.
- [x] Fragment only the struck Recognizer body/leg section; let the other two sections fall apart whole.
See [Models](../models/index.html) for source, parameters and limitations and [Validation](../validation/index.html) for checks.
## September 13 — explicit terminal start
The first screen is a single BEGIN button on black. Activation unlocks audio and waits for sample loading before typing begins. Return retains its skip-text / enter-game behavior. Clicking elsewhere no longer restarts the text. Carrier hull materials now have a restrained blue ambient emission so the whole hull remains visible, including the underside.
## September 13 — roaming map patrols
Four additional Recognizers start at seeded random, separated open map positions above the walls, alongside the existing five opening pursuers. They begin wandering with no target memory and use the same autonomous search/pursuit logic. Placements repeat on restart. Direct detection requires unobstructed line of sight, the existing field of view (including downward vision), and a three-dimensional distance no greater than the maze's longer side. Radio remains limited to 1,000 feet; radio reports are remembered positions, not direct visibility. All nine aircraft have render instances and spatial sound voices.
## Carrier presence and breakup refinement
- Carrier placed farther back and left, with a film-derived stereo rumble candidate and estimated red beacon cadence from the final shot. See References/Assets for timing evidence and limitations.
- Maze wall seams brightened from 0x25334c / 0.48 opacity to 0x354963 / 0.68.
- Recognizer breakup uses six structural sections rather than three rig groups; only one section fragments per destruction.
Clu now breaks apart instead of flattening under a stomp, with visible fracture seams. The carrier now starts 9 km to the left of Clu’s initial heading, outside the normal forward view.
Latest terminal direction: show the complete access text immediately, with no BEGIN button or typing animation/sounds. Return starts the existing dissolve/zoom and unlocks audio. The cursor and CRT treatment remain. This supersedes the earlier explicit-button/typing flow.
The complete home-screen message fades in over 0.5 seconds (no typing); reduced-motion preference shows it immediately.
### Recognizer searchlights — September 13 first pass
Search and investigate states use a sweeping cyan-white beam with a soft blue-violet halo, projected from the crown. Patrols scan intermittently (six seconds per 22-second cycle, staggered by aircraft). The seven-second sweep, 260 m range and spread are provisional until a motion reference is supplied. Aim follows only the aircraft's own goal/memory, constrained to its forward sector; it does not acquire hidden Clu. Rays stop at maze walls/roofs and ground. Attack/pursuit transitions fade the beam; destruction hides it immediately. This is an additive visible-beam approximation, not full volumetric lighting or a projected pool on surfaces.
### Ground escorts — September 14
Eight enemy tanks follow the carrier's X-axis transit on the ground in staggered ranks, 38 m between lanes. They use the existing normalized tank model with red insignia and independent turret joints. Escort cruise can reach 32 m/s to catch the carrier's 24 m/s transit. Sight requires facing, clear line of sight and range no greater than the maze length. Ground and air units now share the same delayed 1,000-foot radio network, including confirmed Clu destruction; relays preserve sighting timestamps.
Observed Clu interrupts escort duty. Nearby ground pursuers reuse dynamic nearest-leader/support formation rules with narrower lanes, abandoning blocked flank slots. Independent turrets lead observed velocity and fire level-barrel rounds when aligned, unobstructed and clear of allies. Lost targets are investigated using bounded last-known prediction, then searched; expired memories return units to their moving escort slots. Walls use swept hull collision and bounded local A* routes. If routing cannot find a path within its budget, the tank holds and retries. Crowded passages may need further driving refinement.
Clu and enemy tanks take three cannon hits; destruction uses the existing fragment effect. Enemy cannon samples are positional. The eight tanks reuse model geometry rather than downloading additional copies. They start far left under the carrier, not near the initial chase.
### Startup presentation — September 14
The HTML supplies a black background immediately and loads the stylesheet through a render-blocking link. Content is hidden until styles apply, avoiding an unformatted text flash. Controls appear at game entry and remain until ten simulated seconds after the first gameplay key press; the Return used to start does not count. Later keyboard activity re-enables the idle reminder. Pausing freezes the countdown, and resetting starts it over. `npm run test:browser -- --startup-ui` passed with deliberately delayed CSS, initial controls visibility, timed fade and subsequent idle reminder checks.
### Gunner view, driving and alertness — September 14
P toggles a first-person sight patterned after `Tank POV cross hairs.png`. O cycles approximately 1×/2×/4× fields of view. I/J/K/L aim vertically and horizontally; J/L rotate the turret in both views; Q/E no longer rotate it. Hull rotation is compensated to hold the sight's world heading. Gunner shots follow the manual sight without automatic target lead/height assistance; the third-person weapon keeps its existing assistance. The visible barrel stays level. Aiming rates scale with the tangent of the zoom field of view for consistent screen-space motion.
Shift+W latches forward throttle until W is pressed again; S overrides it while held. The latch survives pause and clears on reset/destruction. Turbo follows the current motion direction (or requested reverse from rest). Normal reverse caps at 16.5 m/s versus 22 m/s forward; boosted reverse caps at 41.25 m/s versus 55 m/s forward. Both ease back to normal speeds after boosting.
Recognizer alertness is independent of movement state. Actual sightings and fresh radio reports raise an alert until three minutes after the original observation time. Relays do not refresh the timestamp. Searchlights require an alert, no direct visual contact, and no position report newer than 1.5 seconds. They operate while wandering/searching/investigating, fade over the final 30 seconds of alertness, and shut off immediately on reacquisition or confirmed Clu destruction. Routine unalerted patrols no longer scan.
Gunner yaw and pitch now ease toward the requested aiming rate, reaching approximately 95% speed in 0.21 seconds and shedding 95% of motion in 0.14 seconds after release. Hull compensation remains immediate; zoom scales both axes consistently. Centering clears yaw inertia, and leaving gunner mode clears both rates.
On Clu destruction, the breakup remains visible for 1.1 seconds, followed by a one-second fade to the terminal. The terminal displays “ILLEGAL CODE” and “CLU PROGRAM DETACHED FROM SYSTEM” with the supplied film still’s layout, no cursor, and a half-second text reveal. Return starts a fresh run; pause freezes the in-progress transition. This supersedes the earlier requirement to leave the wreck view running indefinitely.
F now levels aim elevation smoothly to the ground plane while preserving turret azimuth, replacing the former forward-yaw centering shortcut. Manual I/K input interrupts leveling.
Clu rounds now last five seconds at 165 m/s (825 m maximum travel). Chase-view acquisition extends to 740 m with a matching five-second intercept horizon. Enemy ammunition retains its prior range.
Recognizers are 30% larger than the previous half-scale version (model scale 0.65). Hit volumes, separation, avoidance, crush footprint/sole height, flight clearance and searchlight mounting offsets follow that scale.
Recognizer destruction now separates pieces outward twice as fast, with a shorter 0.1-second flash and faster tumbling. Debris gravity is 14.7 m/s² with less upward lift; the effect lasts 5–6 seconds with a one-second fade. Only the struck anatomical section fragments; other sections detach whole. Ground-tank breakup timing is preserved.
Recognizer blast-force follow-up: outward impulse is now 2.5 times the preceding version, with faster tumbling. Whole sections scatter outward from the craft center while struck fragments scatter from the impact point, helping the pieces diverge. Vertical impulse and faster gravity remain as before.
Gunner zoom now cycles 1× / 2× / 4× / 8× with O. The fourth level uses a 9-degree vertical field of view; aiming sensitivity automatically follows the field of view.
### Film-inspired Recognizer destruction — September 14
Studied frames from 1:46–1:50 of the supplied Clu scene. Recognizer destruction now uses a brief randomized serrated yellow-white flash, expanding thin double yellow rings, a red after-flash and short-lived orange sparks. The effects stay at the impact in world space, face the camera and respect depth occlusion. Intact sections detach after small randomized delays and tip end-over-end; fragments scatter immediately with varied fracture seeds, spins and an asymmetric blast bias. Only the hit section is fragmented. Strong outward force and faster debris gravity are preserved. The flight loop now uses exactly 2:04–2:06 and destruction uses the stereo 3:06–3:07 excerpt with positional playback.
Clu muzzle flash now follows the supplied still and frames around 1:51: a broad cyan-white scalloped starburst with a blue halo, contracting and fading within 0.17 seconds. It follows the posed barrel in chase/aerial views; gunner mode shows a reduced burst just ahead of the sight. Shot-dependent shape variation, pause freezing and normal depth testing are preserved. Recognizer explosion audio was corrected to 1:47.84–1:48.69 and rebuilt from the supplied M4A with its 47.89 ms offset accounted for.
Gunner view now hides Clu’s muzzle flash entirely; external views retain the cyan burst. Prepared balanced and stronger cleanup candidates for the user-supplied short Recognizer explosion edit, accessible on `/audio.html`; these are audition candidates, not a replacement for the active game sample.
Added a sound resource shelf with 25 entries and 17 local audio players, including four Syna-Max recreations retained for future use. Six stock-effect IDs were checked against publisher catalogs; their association with film scenes remains a community-source lead pending audition. Saved Serafine Sci-Fi I/II metadata exports (420 and 1,028 rows) with source links. This does not change runtime sounds or require an account/purchase.
Nonfatal Recognizer hits now play a dedicated 0.3-second synthesized armor impact: electrical snap, low body and brief inharmonic metallic ring. Each impact is positioned at the bullet strike with small pitch variation. Hit events identify target type and fatality so the armor sound is omitted on destruction. Runtime synthesis and the audition WAV share `src/audio/recognizer-hit.js`; regenerate the WAV with `node scripts/generate-hit-sfx.mjs`.
- [x] Support static deployment under `/fun/TRON/` with relative build and audio paths; verify the production build in Chrome.
- [x] Match enemy tank top speed to Clu normal cruise (22 m/s), with turbo exclusive to Clu; carrier transit matches escort cruise. Add brief impact camera vibration for bullet hits and wall collisions, scaled by impact and gunner zoom.
- [x] F now returns turret yaw forward and elevation level together, using manual aim rates, gunner acceleration and zoom sensitivity. Manual input overrides each axis independently.
- [x] Cannon refills all three extra shots together after ten seconds without firing. Fresh Space/click presses may spend extras to bypass the 0.38-second recharge; held fire retains the normal cadence. Any shot restarts the full ten-second reserve recharge. Instruments show banked extras.
## Patrol mix and spotlight confirmation — September 15
- [x] Two initial Recognizer pursuers plus one maze patrol; three ground maze patrols in addition to the eight carrier escorts. Patrol tanks choose reachable routes through clear maze cells and retain the shared perception/radio rules.
- [x] Searching Recognizers smoothly aim their projector at a candidate sighting while braking. No new target memory, pursuit, or radio report until the beam actually aligns within visual detection range with unobstructed projector-to-Clu visibility.
- [x] Confirmed beams track observed Clu for 1.5 seconds during pursuit, then fade over 0.8 seconds. Losing sight stops beam tracking of hidden movement. Pause freezes projector motion; destruction/reset clears acquisition.
- [x] Correct spotlight reach for distant visible targets: scanning retains a 260 m throw, acquisition/tracking extends to the observed target within sensor range. Projector yaw/pitch rates reduced to 0.5/0.4 rad/s, with an 11-second scan cycle and bounded motion across goal changes. Aim compensates for the age of the last visible sample.
## Camera transitions and maze-tank awareness — September 16
- [x] V eases between driving and aerial views instead of resetting the camera. Aerial viewing direction follows turret azimuth; floor and fog blend with elevation. Wheel zoom and reduced-motion support remain available.
- [x] Ground tanks detect unobstructed Clu within 45 m from any direction and use turret direction as well as hull direction for distant sight. Nonfatal bullet impacts trigger a five-second investigation toward the incoming shot; this clue never reveals the hidden shooter position or permits firing/radio without visual confirmation.
- [x] Fix blueprint patrol stalls at corners: steering and pathfinding share swept clearance, waypoint advancement cannot cut blocked corners, and a valid route survives a failed replacement search. Radio updates trigger replanning; sightings too close to a wall receive a nearby clear approach point. Unreachable patrol destinations are replaced.
- [x] Stabilize Clu turret heading in every view and enemy turret heading during hull turns. Manual aim and stabilization share one yaw motor limit (Clu 1.2 rad/s; enemies 1.3 rad/s). Hull disturbances can cause temporary aim error, recovered at the capped rate; no instantaneous compensation. F remains an explicit hull-forward/level command.
### Mouse gunner aiming — September 16
P enters the gunner view; click to enable mouse-look (Escape releases and pauses). Mouse motion controls the viewing direction immediately at zoom-scaled sensitivity. A small central dot selects the nearest visible world/vehicle surface along the view ray; empty sky uses a distant fallback. The physical cannon aims from its muzzle toward that point using its existing accelerated, speed-limited motor. Film-style crosshairs show actual gun direction and converge on the desired dot. Shots always follow the physical barrel. This replaces the earlier desktop-cursor experiments.
F centers and levels, then locks to the hull until manual aiming. Driving, firing, zoom and view changes preserve that lock. J/L and I/K switch back to keyboard aiming. The barrel cannot aim below the tank base (the hull currently has yaw-only rotation). Mouse capture is optional and keyboard aiming remains available.
### Visibility and pursuit polish — September 16
The whole film gun sight follows the actual barrel as one unit. Enemy tanks have isolated materials with a restrained blue-gray body fill and red trim so low-angle views retain their silhouette. Clu's red tread trim eases into a 2 Hz emissive pulse during turbo and fades back afterward; reduced-motion mode uses steady glow. Enemy materials are independent of this effect.
Confirmed Recognizer spotlights now remain on through pursuit until the observed tank is within 120 m horizontally, then fade over 0.8 s. Loss of visual observation also fades the beam without following hidden movement. The former 1.5-second tracking timeout has been removed.
### Mouse tracking smoothness — September 16
The full sight now uses the same interpolated hull/turret pose and elevation as rendering, rather than stepping through raw 60 Hz states. Mouse tracking has an explicit normalized acceleration bound of 6/s and a 0.22-second proportional slowdown near its target. At 1× this limits mouse-command yaw acceleration to 7.2 rad/s² and pitch acceleration to 4.8 rad/s²; both scale with zoom. The shared physical yaw speed cap remains in place for hull compensation. Mechanical elevation stops still clamp at their limits.
Music now fades from the fatal hit through the 2.1-second destruction/terminal transition, reaching zero before playback pauses. Restart restores normal music gain. The fade follows transition progress, so pausing does not consume the remaining transition.
The end terminal plays the supplied “02 Only Solutions.mp3” after the gameplay track fades. Music has a separate output gain so terminal playback does not reactivate simulation sounds; mute still applies. Restart switches back to the opening recognition clip.
Opening music now uses the supplied “03 We've Got Company Clips/1 recognized 1.mp3”, once per new run without looping, replacing the full gameplay track. Only Solutions remains the end-terminal track.
### Action-driven music
Clip categories are discovered from MP3 filenames in `docs/assets/music/Tron/03 We've Got Company Clips` using a Vite glob. Recognized (also the existing spelling “recongized”), pursued, and gotcha are case-insensitive filename words. New files enter the development catalog automatically and the deployed catalog on the next build/deploy. No runtime server directory access is needed.
- Start each run with “1 recognized 1.mp3” once.
- Fresh confirmed visual contact selects a recognition cue; an already-playing recognition cue covers simultaneous sightings. Acquisition and radio-only reports do not trigger it.
- When a cue ends, choose a pursued cue if a live enemy is pursuing/attacking. Avoid immediate repeats when alternatives exist. Searching/investigating alone does not count.
- A pursuing Recognizer entering 100 m in 3D triggers gotcha; the encounter rearms beyond 140 m. Gotcha takes priority over new recognition cues while close.
- Action changes may interrupt a track: fade out over 0.45 s before changing source, then fade in over 0.3 s. Clips never loop; death cancels pending changes and fades the current level into the end terminal's Only Solutions track.
When pursuit ends without a replacement cue, music now fades to silence over 3 seconds (or the remaining clip duration if shorter). The final seconds of an unneeded clip also fade before its natural end. Reacquisition during this fade restores the current phrase smoothly. Cue-to-cue changes retain their quicker 0.45-second fade.
P now blends camera position, quaternion orientation and field of view over 0.75 seconds in both directions. The complete sight fades with the transition; the exterior tank is hidden as the camera enters its volume. The transition follows moving Clu, restarts from the current camera pose on reversal, and is skipped with reduced-motion preference.
The end terminal now retains the same blinking blue block cursor as the opening screen; reduced-motion preference keeps it steady.
The chase camera now keeps an independent smoothed position while gunner mode or its transition controls the displayed camera. This removes feedback from the blend into chase height/pitch correction on exit.
## Closer carrier and randomized patrols — September 16
The carrier route is nearer to Clu: forward offset is halved and left offset reduced from 9,000 to 3,000 meters. Two tanks escort it below, and two Recognizers follow flanking airborne slots. Air escorts use normal sight/radio rules to leave formation for contact, returning when their memory expires. The two opening pursuers remain separate.
Three maze tanks and one maze Recognizer receive fresh positions, headings and route seeds each game. Ground starts require wall clearance and mutual spacing. `createRun(seed)` reproduces a run for diagnostics; ordinary starts and resets generate a new seed. Stalled ground patrols choose a new destination after five seconds.
Tank destruction now uses a dedicated 1.65-second layered electronic explosion for CLU and enemy tanks, replacing the generic noise burst. Three synthesized variants combine a low shock, electrical rupture and scattered metallic debris; playback is spatial and distance-attenuated. A sample is available in the sound library.
Gunner magnification changes now ease over 0.35 seconds, including cycling from 8× back to 1×; repeated zoom input starts from the current interpolated field of view. Reduced-motion mode changes immediately. The larger magnification label sits in the top-center gap of the crosshair frame and follows the complete sight when aiming.
The gunner view also accepts mouse-wheel zoom: up zooms in, down zooms out, clamped at 1× and 8×. Trackpad input accumulates with a small threshold and rate limit to avoid racing through levels; O still cycles. Aerial wheel controls are unchanged.
The gunner camera is now anchored at the turret pivot instead of the muzzle. Independent mouse-look no longer moves sideways as the barrel catches up, removing motor-driven camera sway and its changing raycast origin. Turret speed/acceleration limits and muzzle-based projectile aiming remain unchanged.
CLU turret motion now has a quiet, low-pitched servo tone that follows actual yaw/elevation motor speed, including centering and stabilization. It fades on stopping, pause or destruction, and is spatialized at the tank. Following listening feedback, its gain is 0.032 and fundamental pitch spans 55–110 Hz.
CLU has a small HEALTH bar below turbo, also visible in gunner mode. Missing health regenerates continuously at 1% of maximum every three simulation seconds (five minutes for a full bar). Pause freezes recharge; destruction sets health to zero and cannot regenerate. Enemy health is unchanged.
Turbo now uses one label line, `T / TURBO`, with readiness shown by the bar alone and no countdown text. HEALTH has no visible percentage (its accessible meter value remains). Health turns orange at 50%, red at 25%, and pulses red at 10%; reduced-motion preference retains steady red.
Enemy bullet impacts now retain `lastHit` (part, simulation time, world x/y/s) and `partHits` counters. Hit events include `id` and `hitPart`; fatal destruction events carry the same classification. Recognizers distinguish crown, crossbar, shoulders and legs, including folded poses; tanks distinguish approximate turret/hull/track armor zones. This is collision-region tracking, not triangle picking. Existing damage and renderer nearest-surface breakup selection remain unchanged.
## Location-dependent enemy damage
Recognizer crown impacts are critical one-shot kills. Other body hits deal one health point; leg and tank-track hits deal half a point. Two hits to the same Recognizer leg permanently disable stomping; a committed attack aborts into climb/unfold recovery. Track efficiency drops to 65% after one hit and 25% after two; both tracks disabled immobilizes the hull while turret aiming and firing continue. Ground hull turn rate also decreases with track damage. Damaged tracks and disabled legs emit intermittent orange sparks. These effects persist until destruction/reset, and do not grant knowledge of unseen Clu. Tank turret classification now uses measured imported-model bounds transformed with turret yaw; remaining armor zones are approximate.
Enemy tanks now fire every 3.4–4.2 seconds with per-shot seeded variation. Their rounds have elliptical spread up to ±1.2° horizontally and ±0.6° vertically. Leading, visibility and range rules remain; the perturbed trajectory is also checked for walls and friendly tanks before firing. Weapon randomness has a separate seed from patrol choices.
## Edge smoothing and gunner targeting — September 17
The HDR renderer retains 4-sample MSAA and adds SMAA before output conversion. Rendering uses 1.25–1.5× pixel density where the four-million-pixel supersampling budget allows; large windows retain at least native resolution. The existing render-scale control remains available. This improves edge smoothing without adding ray tracing.
Gunner mode outlines enemy tanks in soft cyan, with hidden edges suppressed. When a shot along the actual barrel should intersect a moving enemy, the central hash mark smoothly shrinks to half size, rotates 90 degrees over the same 220 ms, and glows yellow. It returns to normal on a predicted miss; the outer crosshairs remain green and all marks retain their shared aiming position. Targeting feedback uses no text. The prediction uses current linear velocity, projectile lifetime and maze occlusion, and shares the simulation's approximate part volumes. Future turns, acceleration or leg folding can invalidate the estimate. It does not steer shots.
The instruments feature is removed: no lower-left readout, H-key toggle or instruments control hint remains.
The top controls row separates hints with centered dots.
The R-key reset shortcut and its controls hint are removed. Development scenario tests use the existing development-only reset hook.
## Intact Recognizer explosions — September 17
Recognizers now burst into their 15 actual connected model blocks. Connectivity is computed across black surfaces and red trim before leg posing, replacing the six position-based regions that could cut through blocks. The struck block also remains whole. Each block receives randomized outward impulse, delay and tumbling before falling; original surface trim, flash and optical rings remain. No fracture shards or new cut seams are generated for Recognizers. Tank destruction retains its existing fracture effect.
## Data collection across maze sites — September 17
- Four maze sites: the original and three copies separated by roughly three maze lengths, with independently randomized rotations per page load. Layout remains stable across resets/hot reloads; beam locations and patrol starts are chosen anew for each run. Rendering, collision, line of sight, projectile blocking and routing share transformed wall geometry.
- Each site has one central red data beam, at a different local position with at least 20 m wall clearance. The column reaches beyond the camera far plane without a visible cap. Driving through its 7 m collection radius collects once, including fast swept crossings. No score panel or automatic ending is added.
- Collection triggers a short electronic confirmation and a 1.5 s shutdown: initial flare, expanding ground ring, narrowing shaft and an ascending pulse. State and visuals reset with a new run.
- Each maze has three ground patrol tanks and one Recognizer. The opening pursuit and carrier escorts remain separate. Patrols choose nearby destinations as they move, favor recently unexplored areas, avoid blocked routes and other tanks, and make short local escape decisions at awkward corners. Sight/radio information and pursuit rules remain shared across all units. No preset patrol circuit is used.
- Distant walls receive a gradual blue brightness lift beyond 350 m and reduced distant fog; nearby colors remain unchanged. The floor covers the expanded world.
- Recognizer blocks receive 3.6 times the prior horizontal blast impulse, retaining all 15 connected blocks and the existing downward acceleration.
- Nonfatal tank/Recognizer impacts use new layered pressure, electrical crack and descending synthetic tail, with three variations. Removed the old metallic resonators; tank impacts are shorter and pitched higher than Recognizer impacts. Stereo placement and distance attenuation remain.
- Tank projected shadows use a consistent opaque footprint and polygon depth offset, preventing overlapping transparent triangles from accumulating darkness and reducing floor depth fighting at long camera distances.
### Carrier surveillance — September 17
- [x] Two underside searchlights detect unobscured CLU beneath the carrier and turn smoothly toward him. They continue tracking while he remains visible and nearby.
- [x] Once a light is aligned and illuminated, report the observed position and velocity once per second to both enemy types within a horizontal circle centered on the carrier. The circle has one maze width diameter (half a maze width radius); delivery takes 0.45 seconds.
- [x] Wall occlusion stops new reports immediately and fades the lights. Existing reports preserve their original timestamps; destroyed CLU is no longer tracked.
### Data transfer and carrier drone — September 17
- [x] Adapt the vertical blue-white curtain in the supplied `Sark and MCP.mp4` at 53–54 seconds into 32 shafts around stationary CLU. Stop within the red beam to build the curtain over 1.2 seconds, hold for 2.5 seconds, and reverse over 1.2 seconds before collecting data and fading the red beam. Moving or destruction interrupts transfer; passing through no longer collects data.
- [x] Replace the carrier film loop with a steady synthesized 16-second machinery drone. Preserve the original `carrier-rumble.wav` in the sound library for future use. Retain positional attenuation and Doppler.
### Carrier escape shuttle — September 17
- [x] Inspect supplied four-angle stills and the 2:34–2:57 escape sequence; construct an open shell model with ribbed trays, upright end blades and a single green-paneled side wall.
- [x] Provide a standalone orbit/turntable studio at `shuttle.html`, six camera presets and GLB export. See [model notes](../models/carrier-shuttle/index.html) for uncertain dimensions and hidden surfaces.
### Maze music and presentation — September 17
- [x] Use the four supplied Tower Music clips: exploration once per game; approaching a nearby hidden beam; spotting its base; entering the column. Close means within 120 m. Visibility checks the camera frustum and wall line of sight to the base, not the infinitely tall shaft. Cues advance once per beam without restarting when the camera turns; pursuit has priority. Entry remains latched during its fade-in so driving through still triggers it. New runs reset cue history.
- [x] Replace near-coplanar shadow depth tests with explicit opaque draw order: floor, flat tank shadows, then vehicles and maze walls. This preserves occlusion while eliminating floor-versus-shadow depth competition.
- [x] Remove the C rear-view shortcut and its hints. Append `END OF LINE` to the closing terminal and show a small fan tribute at the bottom of that screen only. End-screen timing remains unchanged, per the user's correction.
The closing tribute now uses the FilmTerminal face, uppercase lettering and left alignment with the main terminal text. It types at 50 characters/second after a half-second pause, ends with MADE TOGETHER, ACROSS THE INTERFACE. followed by END OF LINE, and leaves the cursor there. Reduced-motion mode shows the complete text immediately. Restart cancels and resets typing.
### Beam transfer revision — September 17
Stopping inside a red beam pauses propulsion and engine audio while aiming and firing remain available. The shortened 18-second entry cue accompanies a 16-second transfer: surrounding shafts assemble over three seconds, hold until second nine, then open over seven seconds. Health restores gradually throughout. The column blends red through white into permanent blue; returning to blue does not repeat healing or effects. Completion sends a 12-second spherical wave out to half a maze width in radius, destroying ground/air enemies on contact and removing 25 of the carrier's 100 health per wave. Carrier damage darkens its hull and briefly flashes it; a carrier breakup sequence is not implemented.
All closing text matches the opening terminal font size. Closing tribute text now types at roughly ten characters per second with random keystroke spacing and longer punctuation/paragraph pauses. Reduced-motion preference still displays it immediately.
The transfer surround uses sky-reaching shafts that switch on individually at full height around CLU, then switch off in reverse order after second nine. No downward growth or visible upper ends.
The closing tribute waits after ACROSS THE INTERFACE until the outro media element reaches its final five seconds. It then inserts a newline, switches the cursor to red, and types END OF LINE in red. Timing follows actual playback, including pauses; reduced motion preserves the music cue but displays the final phrase without per-character animation.
Recognizer projector speeds now distinguish scanning (0.5/0.4 rad/s yaw/pitch), visible acquisition (1.2/0.9) and confirmed tracking (1.8/1.2). This allows turbo cross-traffic to remain illuminated while preserving slower searching, bounded movement, observed-velocity prediction and wall occlusion.
The closing screen holds for three seconds before the tribute starts typing; the final red sign-off remains synchronized to the last five seconds of the outro.
At second nine, when the transfer ring starts opening, the entry cue fades into the user-supplied `5 afterglow.mp3`, played once. The afterglow survives beam completion and leaving the beam; new pursuit may still interrupt it using the usual music rules.
### Recognizer shadows — September 18
Recognizers now project their actual body and folding-leg silhouettes onto the ground, using the same light direction and floor-first rendering as tank shadows. Shadows follow flight height and pose and disappear with the destroyed craft. They are excluded from breakup geometry and share the source geometry. This is a ground-plane projection, not shadow mapping onto roofs or vehicles.
### September 18 follow-up
Recognizers and breakup pieces now use a compact light-space shadow atlas to darken the maze walls, roofs, seams and floor. Debris shadows follow tumbling pieces and fade with their lifetime. Existing tank and Recognizer projected silhouettes remain on the ground. The atlas supports five simultaneous breakup groups, matching the existing debris lifetime cap. The shuttle studio and export now interpret the ribbed shell as an underside, with upward-looking cameras and illumination from below.
The outro never loops. Once the red END OF LINE finishes, the terminal fades to black over five seconds and returns to the opening screen without starting a new game automatically.
### September 18 combat and shadow corrections
- Five pursuers start spread behind CLU; each maze retains its own patrol and the carrier retains its escorts.
- Recognizer central core hits (crossbar and crown) destroy it in one shot. Shoulder/leg armor retains its existing damage behavior. Any bullet hit destroys an enemy tank.
- Gunner controls are keyboard-only for now: mouse aiming, clicking and wheel zoom are disabled in this view. O cycles 2× → 4× → 8× → 2×; the former 1× FOV remains only as the sensitivity reference. Aerial wheel zoom is unchanged.
- Active transfer-ring shafts destroy touching Recognizers at any flight height. Shaft visibility and collision use the same timed sequence; blue/inactive rings cannot cause contact damage.
- Shadow atlas rectangles now use physical texture coordinates independently of the game's supersampling ratio. Actual debris also has crisp projected ground silhouettes. Maze floor shadows use exact projected geometry rather than the coarse shadow map; wall/roof receiving still uses static shadow maps.
Wall-shadow refinement: exact floor silhouettes now use a lighter blue-black fill. Maze shadows on roofs, walls and vehicle surfaces use 50% attenuation; CLU, enemy tanks, Recognizers and carrier materials receive the static maze maps. Emissive material light is preserved.
### Faster beam sequence and outside-view cannon spread — September 18
Ring engagement takes 1.5 seconds, holds for six seconds, then disengages over 3.5 seconds. The transfer/healing/color sequence completes in 11 seconds; afterglow begins with ring opening at 7.5 seconds. The expanding damage sphere reaches its unchanged final radius in four seconds, three times its previous speed.
Outside gunner view, CLU shots have up to 0.6 degrees of horizontal and 0.3 degrees of vertical spread around their assisted trajectory. Level shots have no vertical spread, and all outside-view trajectories are clamped to the horizon or above to avoid floor strikes. Gunner shots remain precise. Spread is reproducible for a given run seed and shot sequence.
### Carrier shadows and wall self-shadow correction — September 18
Sark's carrier casts a moving, crisp hull silhouette onto the floor and a separate 1024-pixel shadow map onto maze walls and vehicles. Emissive vehicle trim stays lit. Only armor casts, avoiding redundant painted outlines and beacon meshes. The carrier atlas uses one tile; the static maze atlas remains cached after its first render. Slope-aware depth bias on maze/carrier receivers suppresses triangular self-shadow artifacts on bevelled wall faces without changing the exact floor silhouettes.
### Sentence-by-sentence closing tribute — September 18
The replacement tribute types three sentence cards at the same position. Each holds for three seconds, fades over 0.8 seconds, then clears before the next sentence. The initial three-second delay and human typing cadence remain. Reduced motion shows each sentence without typing/fading, retaining readable holds. The last sentence stays visible through the outro; the cursor waits beneath it for the song's final five seconds. The first character of the red END OF LINE sign-off triggers the supplied MCP dialog once; reset stops it and the sound toggle mutes it. The existing five-second final fade follows the completed sign-off.
### Clean screenshot export — September 18
F9 captures a PNG of the rendered scene during gameplay, the opening zoom, or pause. DOM overlays (including the guide, turbo, health, and gunner reticle) are excluded from the image. The HUD hides temporarily and restores after saving or cancellation. Browsers supporting the File System Access save picker let the user select a destination; other browsers download a timestamped Space-Paranoids PNG using their configured download behavior. Capturing renders immediately before reading the WebGL canvas, avoiding cleared/blank screenshots without permanently preserving the drawing buffer.
Wall shadow follow-up: the slope bias now covers the combined X/Y depth change of diagonal PCF samples, rather than just the larger axis. Matched close-up captures with shadows on/off isolate the previous triangular pattern and verify continuous shading after the correction.
### Drifting grid-cloud layer — September 18
The extracted cloud model is reused in a three-instance layer at 1,080 meters (three times the carrier altitude). All clouds drift along +X at 6 m/s across bounds enclosing all maze sites and the starting approach. Each cloud owns a separate Z lane with 700-meter edge margins, wider than its maximum half-extent, so their footprints cannot overlap even at a wrap. Each crossing deterministically varies its position within that lane and its size using the run seed. All clouds share one fixed orientation: their long axes and grid lines align with world +X and the ground grid, matching their shared direction of travel. Whole clouds fade to zero at map boundaries before wrapping to the opposite edge; distant clouds also fade between 4 and 6.5 km. Sky-only far-plane handling preserves distant visibility without changing the ground camera clipping. The single instanced mesh has no collision, shadow, sensor, or gameplay participation. Simulation time controls motion so pause and reset remain stable.
### Glyph-aligned CRT raster — September 18
Opening and closing terminal text now use the local Interface Raster color font derived from VT323. Dimmed raster bands are embedded in each glyph, with blue/red palettes, instead of a separate fixed screen-space mask. Bands retain 68% brightness rather than cutting transparent gaps. Credits use the same raster treatment, and the sign-off and cursor stay red. Existing typing, sentence replacement, final-line hold, and outro timing remain in place.
### Beam-base stability and grid-preserving carrier shadow — September 18
The beam base and expanding ground ring render in the floor-decoration pass, after the floor/shadows and before solid vehicles/walls. Their additive blending stays intact without an almost-coplanar floor depth comparison. The surrounding shafts and their contact damage are centered on the main beam, independent of CLU's stopping offset.
The carrier now shades the original floor material through its shadow atlas instead of painting a solid floor silhouette. Grid lines remain visible inside the shadow. Shader cache keys preserve each receiver's pre-existing material customizations when stacking shadow hooks.
### Directional blue horizon — September 18
A fixed world-space sunrise glow faces CLU's initial heading: blue haze fades upward, with a narrow violet accent at the horizon and a smooth angular falloff to darkness toward the sides/rear. A single background shader reconstructs world viewing rays, so the effect follows camera orientation without moving with the player or appearing as a nearby object. Existing floor, fog, and vehicle lighting remain unchanged. The terminal/reference studio hides the effect; scene disposal releases its geometry/material normally.
### Debris collisions and revised tribute — September 18
- [x] Give each intact Recognizer block and tank fragment an oriented box collider. Resolve floor, wall sides and roof contacts against nearby shared maze prisms, with bounded short steps for fast explosions, bounce, friction and sleep after settling.
- [x] Limit debris to five bursts / 160 pieces. Debris remains visual: it neither blocks vehicles nor collides with other debris. Existing fading, pause and disposal behavior remain.
- [x] Replace credits with the user's two-paragraph tribute, preserving explicit line breaks. Keep the collaboration paragraph visible through the outro and retain the red END OF LINE signoff.
- [x] Gently brighten the maze's blue-black wall faces and upright ledges for close-range readability.
### Rapier debris — September 18
- [x] Replace the provisional debris contact solver with Rapier rigid bodies, preserving the intact Recognizer blocks, existing blast velocities and per-vehicle gravity. Angular inertia and off-center contacts now produce natural tumbling, tipping and settling.
- [x] Use a fixed 120 Hz physics step with interpolated poses, continuous collision detection, sleeping bodies, nearby static maze prisms and the existing five-burst / 160-piece cap. Collision groups exclude debris/debris contacts and vehicles remain under their existing controls.
### Debris damage and shadow layering — September 18
- [x] Debris physically contacts CLU, enemy tanks, Recognizers and the carrier through coarse kinematic collision shapes. Damage uses the closing component of relative contact velocity, including spin, and estimated fragment mass (60 kg/m³): no damage below 5,000 J, then one health point per additional 50,000 J. Compound contacts are deduplicated; resting contacts cannot repeatedly damage a vehicle.
- [x] Run debris updates and apply damage during fixed gameplay steps. Fatal impacts use existing hit/destruction events, allowing subsequent explosions. Vehicle driving remains independently controlled.
- [x] Draw maze floor shadows before vehicle/debris ground shadows. Increase wall-face brightness again and reduce maze shadow darkening from 50% to 40%.
- [x] Update tribute wording to “FAN TRIBUTE GAME” and “LOVE AND GREAT APPRECIATION,” retaining the user's explicit line breaks and final collaboration paragraph.
Credits are authored in `docs/credits.txt` and imported as text by Vite. Explicit line breaks and blank-line paragraph changes are preserved; the timed red END OF LINE remains generated by the terminal sequence. Edit the text file to update the credits (rebuild for deployment).
- [x] Preserve the destroyed vehicle's full world velocity in each fragment's initial Rapier velocity, added to its explosion impulse. Sections with a delayed release continue translating at the vehicle velocity before detaching.
### Crisp shadows without triangle seams — September 18
- [x] Compare shadow depth against the receiver plane at the sampled texel center; handle tiny projected derivatives at distant coordinates. Use hard nearest-depth shadows rather than a nine-tap blur, with 512-pixel dynamic tiles.
- [x] Replace stochastic debris ground-shadow fading with a uniform minimum-blend fade. Overlapping projected triangles cannot accumulate darkness or reveal noisy/triangular fade patches; the established ground-shadow draw order is retained.
- [x] Use B as the displayed screenshot shortcut, retaining F9 as an alias for systems that deliver it to the browser. Brief save/cancel/download status makes capture feedback visible. The PNG remains canvas-only, without HUD or status overlays.
### Roof-edge self-shadow suppression — September 18
- [x] Give each slab and its decorative receivers a stable identity. Maze depth maps encode slab identity alongside depth; a slab ignores its own depth-map shadow, avoiding roof-edge sawtooth artifacts. Other slabs and vehicles still receive its shadows. This also suppresses self-occlusion within a single concave slab; inter-slab shadowing remains.
- [ ] Visually verify the roof-edge fix in-browser. Added `tests/roof-edge-shadows.mjs` for closed roof/wall corners and identities above 255; current execution permissions prevent launching the browser test.
Browser-launch follow-up: the roof-edge test now defaults to port 5173 and accepts `TRON_URL`. Both desktop Chrome and the matching Playwright headless shell fail before loading a page in the current restricted session; the headless shell explicitly reports macOS Mach service registration permission denied. Browser verification remains pending; run `node tests/roof-edge-shadows.mjs` from a normal Terminal with Vite running.
### Grid-preserving maze shadows — September 19
- [x] Shade the existing floor material with the cached maze shadow atlas instead of drawing opaque projected polygons. Grid lines remain visible under wall shadows, using the same material-shading approach as the carrier. Maze attenuation remains 40%, and vehicle/debris ground shadows still draw after the floor.
- [x] Verify grid visibility and tank-shadow layering in Chrome and complete a production build. The floor now shares the 1024-pixel-per-site map; unlike the former exact projected polygons, close-up floor edges have finite texel resolution.
Browser permission follow-up: the approved Chrome launch succeeded and `tests/roof-edge-shadows.mjs` passed all 30 closed-box poses with zero self-shadowed pixels. This supersedes the launch blocker above; exact visual matching to the user's roof-edge screenshot remains separate.
### Throttle-controlled turbo and crisp grid shadows — September 19
- [x] Turbo only raises speed and acceleration limits (both multipliers 2.5). Activation no longer changes velocity or supplies throttle. Forward/reverse input, coasting and braking retain control; duration, recharge and the existing reverse limit remain unchanged.
- [x] Replace the coarse maze floor-map edge with exact projected slab geometry. Multiply the existing floor color to preserve grid lines, using stencil bit 0 to darken each covered sample only once. Enable stencil attachments on the game renderer and multisampled scene target. Vehicle/debris shadows still draw afterward; wall/roof receiving retains the cached atlas.
This supersedes the finite-resolution floor-edge compromise in the preceding entry. Rendered blueprint and full-game captures were inspected, including grid retention and a clean diagonal floor-shadow boundary.
### Recognizer inertia and wall-shadow filtering — September 19
- [x] Persist yaw velocity with a 0.8 rad/s² acceleration/braking limit and retain the 0.62 rad/s turning limit. Heading changes, yielding, acquisition and attacks now decelerate rotation instead of freezing it. Horizontal forward thrust and drag remain momentum-based.
- [x] Persist vertical velocity with 14 m/s² controlled lift acceleration and a 22 m/s requested lift-speed limit. Hover changes, folding, recovery and aborted drops retain velocity. A drop uses its incoming vertical velocity and gravity; ground contact is the physical stop. Recovery completion uses small position/velocity tolerances rather than snapping to altitude.
- [x] Expose Recognizer turning and lift limits with units in the development tuning panel, including reset/export.
- [x] Increase the cached maze wall/roof atlas to 2048 pixels per site and add nine-sample, continuously weighted edge filtering. Each sample keeps receiver-plane depth correction and slab-identity exclusion. Dynamic aircraft/carrier maps retain their existing nearest-depth behavior; exact grid-preserving floor silhouettes remain separate.
Wall edges are now softened over a small texel neighborhood instead of making a binary step. This is still a finite-resolution shadow map, with greater texture memory than the former 1024-pixel maps, not exact geometric wall-on-wall shadowing. Human review of Recognizer weight and close wall edges remains appropriate.
### Exact wall-on-wall shadows and isolated development — September 19
- [x] Replace the softened maze wall/roof map with exact static geometry: clip each receiving triangle against caster triangles extruded along the fixed light direction. A light-plane spatial hash bounds the setup work; stencil bit 1 makes overlapping polygons a single shadow. Receiver slab identity still suppresses self-shadowing. Small depth bias keeps clipped polygons on their receivers, behind occluding vehicles. This supersedes the filtered wall-edge approach above.
- [x] Retain exact grid-preserving floor silhouettes. The initial WebGL context now explicitly requests stencil, as does the multisampled scene target. Moving vehicles still receive a separate cached 1024-pixel maze atlas.
- [x] Add an isolated Docker launcher with TRON as the only host-folder bind mount, separate Linux dependency/home volumes, an unprivileged user, read-only image filesystem, private IPC and no Docker socket. Codex approvals are disabled only inside that external boundary. Native Chrome remains the browser-test default; the container uses bundled Chromium.
Use `scripts/codex-container login` once, then `scripts/codex-container` for future container sessions. Current native session credentials/history are not mounted or copied. See README for build, test, port and authentication details.
- [x] Default Vite to manual refresh (`server.hmr: false`) so source edits do not interrupt play. `TRON_HMR=1 npm run dev` opts into automatic reload. Browser verification confirms a source edit preserves the running page and a manual refresh loads the update.
- [x] Verify exact wall edges against independent ray/box intersections and inspect the authored-maze capture. User confirms the wall shadows look right after this change.
September 20 teleporter flyover check: added a fixed-step moving-Recognizer regression at pursuit speed, with multiple headings and high altitude. All seven teleporter checks pass. Full-containment activation is unchanged: a 23.4 m wide aircraft has only 4.3 m of lateral center tolerance on an axis-aligned 32 m pad. A visually overlapping edge pass or an occupied destination can therefore produce no transfer; the reported flyover has not been reproduced as a trigger defect.
---
Source: /fun/TRON/docs/jev/
---
title: Jev enemy control
subtitle: Current architecture, input/output contract, and limits
---
## What Jev controls
Jev selects one maneuver for one enemy vehicle—or optionally Clu in autoplay—from a list generated by the game. The game supplies the vehicle's recorded knowledge, nearby allies, local wall geometry, and candidate destinations/routes. Jev returns a candidate ID and confidence. Local simulation executes the selected maneuver using inertial movement, collision checks, and existing weapon/attack rules.
This describes the implementation as of September 21, 2026. Jev does **not** currently invent routes, set throttle/yaw each frame, coordinate a whole squad in one request, or generate game content. Those would be extensions of this design.
The browser defaults to **Tactical + Jev** with the full enemy population. Shift+T provides Classic, Tactical — local, and Tactical + Jev modes. The optional small encounter is off by default. Tactical modes disable repeating pursuit reinforcements; Classic retains the 20-second reinforcement interval.
## Decision flow
September 22 flight correction: executing a selected maneuver no longer waits for near-perfect heading alignment before applying thrust. Recognizers turn while moving, reduce approach speed when the remaining distance cannot accommodate a broad arc, and pass through straight route segments without stopping at each waypoint. Arrival, confined descent and attack clearance still use local geometry and braking; this changes the local controller, not the JEV request/response contract.
1. Existing perception and radio systems update each vehicle's observations and target memory.
2. The local tactical planner generates feasible maneuvers and immediately starts its highest-scoring choice. Simulation never waits for an API call.
3. The browser scheduler selects an eligible unit and posts its planning snapshot to the local Vite relay.
4. The relay builds a typed choice question and calls TypeSafe's System One endpoint using the server-held key.
5. The relay validates the answer's ID and confidence range; the client checks whether it is still applicable.
6. An accepted answer replaces the local choice and restarts that maneuver's route progression. Otherwise the local planner continues.
7. The fixed-step simulation drives the vehicle and continuously checks movement against geometry.
There is no persistent provider conversation or shared Jev session. Each request contains its own snapshot, including recent acoustic observations. Persistent memory, maneuver revisions, and execution state belong to the game.
## Optional Clu autoplay
Press **U** or the top-left HUD button to enable autoplay (off by default). Autoplay stays selected until U or its HUD button is toggled. Held driving/turret inputs temporarily override their respective channels; Space and mouse firing combine with pilot firing while navigation continues. Gunner/camera controls remain available. Releasing held controls restores pilot input, while existing cruise and aim-lock modes retain their normal behavior. Pausing aborts pending requests and freezes simulation. Restart clears the current plan while retaining the explicitly selected autoplay mode. The button labels whether the current maneuver came from Jev or the local planner, and displays service warnings. Network errors, timeouts and service/limit failures disengage autoplay and clear its plan. It stays off until explicitly re-enabled; known provider cooldowns prevent enabling it. Enemy AI continues using its local fallback.
Player snapshots have `controller: "clu"`, `self` (pose, speed, health, turret yaw), `visibleEnemies`, known data-beam objectives, nearby wall polygons, recent positions, and up to eleven maneuver choices. Contacts are limited to 350 m with unobstructed line of sight from Clu; this initial sensor is omnidirectional, independent of camera direction. Enemy private state/memory/intent is excluded. Candidate routes include short swept evasion/exploration segments, a bounded ground route toward a data beam or maze opening, and hold. The player prompt explicitly asks Jev to turn every red data beam blue while surviving. `objective` contains remaining/total counts and activation rules (stop within 7 m, at most 0.3 m/s, wait approximately 12.667 seconds); each `objectives` entry includes red/transitioning/blue state and remaining transfer seconds. A transitioning beam still counts as uncollected. After all are blue, Clu continues exploring. The planner still provides only bounded local routes, not guaranteed multi-maze completion; it is separate from the enemy prompt. Local rankings are supplied, so this remains constrained maneuver selection rather than unrestricted motor control.
`src/simulation/autoplay.js` replans every three simulation seconds, before a temporary movement endpoint enters the braking zone, or after teleport. Local controls regulate speed for turns and required stops, carry momentum through straight waypoints, and aim at the visible target or along the route when no contact remains. Target-switch hysteresis reduces camera swings. They fire only when the existing cannon solution identifies that target. Obstacles are rechecked ahead of the tank and ordinary collisions remain authoritative. It automatically uses turbo on long clear aligned runs, reserving boosted travel plus braking distance before the next turn or stop. Rejected/late/uncertain responses retain the local choice. The one shared `JevClient` alternates player and enemy opportunities when both are eligible; player requests also work when enemies are in Classic/local mode. Existing quota/cooldown and pause/reset protections apply to both. This is an experimental observer mode, not a demonstrated optimal player.
Deploy the updated Worker player prompt together with any public client that exposes autoplay. Local Vite uses the shared protocol automatically.
## When a unit can request Jev
A unit must have a recorded Clu position no older than 38 simulation seconds, or an unknown sound report no older than 10 simulation seconds, within one maze length horizontally of its current position. A fresh target memory takes precedence over sound for this gate. This uses a sighting/radio memory or an estimated sound location, **never Clu's hidden live position**. `MAZE_LENGTH` is the larger transformed dimension of the base maze, not the distance across all maze sites. Its multiplier is `TACTICAL.jevRangeMazeLengths`, currently 1.
The unit must also be alive, outside materialization/teleport, not in a committed aircraft attack, and have a fresh maneuver revision that has not already been requested. The game must be running in Jev mode and Clu must not be destroyed. Distant or unaware enemies still move and make decisions using local tactics.
The scheduler scans Recognizers first, then ground tanks, taking the first eligible unit. It is currently **not round-robin**. Under load, later units may miss their request window and remain on local decisions.
## Input: game snapshot
The browser sends this object as JSON to `POST /api/jev/decision`. The values below are illustrative, not a recorded encounter. Routes/facts are shortened for readability; actual requests contain all generated candidates and their route poses.
```json
{
"time": 12,
"units": "meters, seconds, radians; x/s horizontal, y up; forward=(-sin(yaw),cos(yaw))",
"self": {
"id": 2,
"kind": "recognizer",
"x": -5000,
"s": -5000,
"y": 80,
"yaw": 0,
"vx": 0,
"vs": 30,
"vy": 0,
"yawVelocity": 0,
"fold": 0,
"health": 3,
"canSee": true
},
"target": {
"x": -5000,
"s": -4800,
"vx": 0,
"vs": 22,
"seenAt": 11.8,
"source": 2,
"age": 0.2
},
"allies": [],
"map": [],
"current": "pursue",
"options": [
{
"id": "m0",
"kind": "pursue",
"goal": {"x": -5000, "s": -4780, "y": 80, "yaw": 0},
"route": [{"x": -5000, "s": -4780, "y": 80, "yaw": 0}],
"facts": {
"travelMeters": 220,
"goalDistanceToLastKnownTarget": 15.6,
"occludedFromLastKnownTarget": false,
"nearbySupport": 0
},
"localScore": 88
},
{
"id": "m1",
"kind": "hold",
"goal": {"x": -5000, "s": -5000, "y": 80, "yaw": 0},
"route": [{"x": -5000, "s": -5000, "y": 80, "yaw": 0}],
"facts": {
"travelMeters": 0,
"goalDistanceToLastKnownTarget": 204.4,
"occludedFromLastKnownTarget": false,
"nearbySupport": 0
},
"localScore": 10
}
]
}
```
| Field | Meaning and source |
| --- | --- |
| `time` | Simulation time when the candidate set was generated, not necessarily the time the request is sent. |
| `self` | Own position, heading, velocity, angular velocity, leg fold, health, and current visual-contact flag. Ground tanks use `kind: "ground"`; undefined properties may be omitted by JSON serialization. |
| `target` | Copy of recorded target memory plus its age. Position/velocity refer to the observation, not current hidden Clu state. `source` identifies the observer when present. No target orientation is explicitly sent; velocity provides travel direction. |
| `sounds` | Recent acoustic reports, with sound type, noisy bearing and amplitude-derived distance. See Hearing input below. |
| `allies` | Active friendly units within one active-maze-width radius in three dimensions. Each entry includes `id`, `kind`, `x`, `s`, `y`, `yaw`, `vx`, `vs`, `health`, `state`, `intention`, and its own dated `memory` or null. |
| `map` | Up to 48 nearby wall polygons selected within a 240 m query radius. Each entry has `height` and `points: [{x,s}, ...]`. This is static map knowledge, not a camera image or the full world map. |
| `search` | Search origin, whether it has been checked, and inspected locations; null outside an active lost-contact search. |
| `replanReason` | Why this candidate revision was generated: sight loss/reacquisition, reported/expired contact, target maneuver/displacement, injury, or ordinary scheduling. |
| `current` | Locally selected maneuver kind at snapshot construction. |
| `options` | Candidate IDs, kinds, destinations, route poses, precomputed facts, and local scores. Search candidates also carry `searchPath`, a tank-passable corridor hypothesis from the search origin (distinct from the aircraft execution route). IDs such as `m0` are meaningful only within this candidate revision. |
The simulation uses horizontal coordinates `x/s`, altitude `y`, meters, seconds and radians. Its forward direction is `(-sin(yaw), cos(yaw))` in `x/s`; rendering converts to Three.js coordinates. Route poses are `{x,s,y,yaw}`. Ground candidates normally have `route: null`; the ground controller supplies its existing route handling.
Candidate facts are measurements, not predictions of success. `goalDistanceToLastKnownTarget` and occlusion use the planner's bounded extrapolation of recorded memory. `nearbySupport` counts known allies within 150 m horizontally of the destination. `travelMeters` sums route segments; for ground candidates without a route it is a direct-distance estimate. `localScore` is a hand-authored preference, not a probability.
Snapshots contain no screenshots, unrestricted live player object, or global enemy knowledge. Ally reports keep original observation ages. The aircraft planner can consult local static geometry beyond the subset sent in `map`; the supplied routes carry the resulting movement choices.
## Hearing input
`sounds` is an array on the snapshot, empty when nothing recent was audible. Each report is an observation made at `heardAt`, not a continuous tracker. The following example is illustrative:
```json
{
"type": "cannon fire",
"heardAt": 12,
"age": 0.5,
"bearing": -1.52,
"relativeBearing": -0.52,
"elevation": -0.2,
"amplitude": 0.03,
"estimatedDistance": 230,
"distanceUncertainty": 69,
"affiliation": "unknown",
"estimatedPosition": {"x": -4775, "s": -4988}
}
```
Bearing and elevation are radians. `bearing` is in world heading coordinates; `relativeBearing` uses the listener's heading **at hearing time**. `estimatedDistance` is a slant distance in meters; `estimatedPosition` projects that noisy range/bearing onto the horizontal plane from the receiver's position at hearing time. No exact emitting position, source ID, or target velocity is sent. `amplitude` is normalized perceived pressure, not calibrated decibels. `distanceUncertainty` is a heuristic radius, not a statistical confidence interval.
| Sound | Unobstructed starting range |
| --- | --- |
| Clu's engine | 35 m at rest, rising to 100 m at normal maximum speed |
| Friendly ground engine | 100 m |
| Aircraft engine | 150 m |
| Cannon fire | 650 m |
| Impact | 200 m |
| Explosion/destruction or data-wave release | 950 m |
Engines emit sensor samples once per simulation second. Impulses come from simulation events and are consumed once even if rendering retains the event array. Pressure falls with inverse distance. A blocked line between source and listener multiplies pressure by 0.25, reducing detectable range to one quarter and increasing the inferred distance. The estimator uses nominal engine loudness, so a quiet idle engine can seem farther away. Deterministic per-listener noise perturbs bearing/elevation and range without altering gameplay random seeds.
This is a lightweight gameplay approximation: no sound-speed travel delay, echoes, multiple-wall attenuation, diffraction/path tracing, frequency spectrum, Doppler, or background-noise masking is simulated. The sensor operates independently of Web Audio and the player's mute/volume setting.
Each unit keeps at most six reports, expiring after 10 simulation seconds. New reports replace earlier reports of the same type and affiliation. Known friendly vehicle engines and cannon fire are labeled `friendly`; their listener's own engine/shot is ignored. Destruction and impacts are unidentified acoustic events. Friendly sounds stay in the input but do not trigger investigation or qualify a unit for Jev by themselves.
The local controller investigates an unknown sound only when it lacks target memory. Ground units use a nearby free map opening when the estimate falls inside a wall. Hearing does not set `canSee`, populate visual `memory`, reveal Clu's identity, broadcast a false sighting, or authorize cannon/stomp attacks. A hearing-only Jev request has `target: null`, `sounds`, and an `investigate-sound` candidate. The prompt explicitly describes these as uncertain cues. Sound reports expire with simulation time; teleport departure clears them, and a fresh run has none.
Constants live in `src/game/hearing.js`; Shift+T exposes moving-engine, cannon and explosion range controls. Sound collection and estimation live in `src/simulation/hearing.js`. `tests/hearing.test.js` verifies audibility, muffling, estimation, expiry, event deduplication, information separation and investigation. `tests/hearing-browser.mjs` checks an actual player cannon event through local and mocked Jev selection without making a paid API call.
## Candidate maneuvers and execution
| Maneuver | Intended behavior |
| --- | --- |
| `pursue` | Close on recorded/predicted contact above the roof; offset supporting aircraft when another unit is preparing a nearby strike. |
| `search-track` | Move toward the bounded predicted last-known path after losing visual contact. |
| `search-branch` | Inspect a plausible nearby opening, preferring the last observed travel direction and avoiding recently checked locations. |
| `strike` | Reach an oriented overhead attack pose, then attempt the existing crush attack. |
| `low-approach` | Follow a checked low-altitude corridor route toward an attack pose. |
| `low-cover` | Descend into a reachable broad opening near the target memory. |
| `pressure` | Ground-tank movement toward recorded contact. |
| `retreat` | Move away from known danger; favored at health 1 or lower. |
| `regroup` | Move toward a known ally farther from the recorded target. |
| `ambush` | Move toward a nearby opening, preferring occlusion from the target memory. This is a positioning heuristic, not a full coordinated ambush state machine. |
| `investigate-sound` | Inspect the estimated location of an unknown sound without treating it as a sighting or authorizing an attack. |
| `patrol` | Move toward an opening in the unit's maze when there is no valid target memory. Normally handled locally unless an unknown sound enables a request. |
| `hold` | Remain near the current location at safe altitude. |
Only candidates valid for the unit and current geometry are included. Aircraft use conservative oriented hull checks and swept translation/rotation tests. The overhead planner generates climb/turn/travel/align stages; the bounded low-flight search includes heading in its state. Above the roof, execution skips obsolete stationary starting poses so momentum does not send a pursuing unit back to its route origin.
Candidate generation remains a substantial local decision layer: it chooses destinations, offsets, landing orientations, and the routes Jev can select. Jev can override the local ranking, but cannot choose a destination or tactic absent from the candidate set. It also sees that ranking through `current` and `localScore`, so this is not an independent, unbiased comparison of two planners.
The aircraft controller applies acceleration, drag, yaw acceleration and lift at the fixed simulation timestep. Swept collision validation remains authoritative after Jev selects a route. A strike still requires the attack system's own fresh visual/geometry conditions. Ground tanks retain local navigation and weapon logic; retreat/regroup suppresses their firing. Jev does not directly press the fire button.
## Provider request
The relay calls `POST https://api.typesafe.ai/v1/systemone` with server-side bearer authorization. The default model is `jev-latest`, overridable with `TYPESAFE_MODEL`.
The following is JavaScript describing the actual JSON envelope; `snapshot` is the object above:
```js
{
model: 'jev-latest',
state: JSON.stringify(snapshot),
questions: {
maneuver: {
type: 'choice',
instructions: '...tactical instructions...',
criteria: Object.fromEntries(snapshot.options.map(option => [
option.id,
JSON.stringify({
maneuver: option.kind,
destination: option.goal,
route: option.route,
localScore: option.localScore
})
]))
}
}
}
```
`state` and each criterion value are **strings containing JSON**, inside the outer request JSON. The instructions ask the unit to use recorded observations and their ages, favor survival/positioning/coordination, withdraw when wounded, exploit feasible low approaches, and commit rather than oscillate. The full prompt is in `server/jev.js`.
The relay accepts 1–12 candidates with unique IDs matching `m` followed by one or two digits, short maneuver names, and finite horizontal goal coordinates. This is deliberately a bounded choice interface, not a generated-code or arbitrary-action interface. It validates these required fields rather than applying an exhaustive schema to every snapshot property.
## Output and acceptance
An illustrative provider response is:
```json
{
"answers": {
"maneuver": {
"choice": "m0",
"confidence": 0.72,
"probabilities": {"m0": 0.72, "m1": 0.28}
}
},
"usage": null
}
```
The relay checks that `choice` belongs to this request and confidence is finite and between 0 and 1. It then returns:
```json
{
"id": "m0",
"confidence": 0.72,
"probabilities": {"m0": 0.72, "m1": 0.28},
"usage": null
}
```
`probabilities` and `usage` are passed through for diagnostics; they do not control movement and are not fully schema-validated. No explanation or free-form plan is requested. Confidence is provider-reported, not an established probability that the maneuver will succeed.
The browser accepts the choice only if its run/mode/request epoch is still current, the unit is active and still has nearby fresh knowledge, the maneuver revision matches, the request is at most 2.5 simulation seconds old, confidence meets `aiConfidence` (default 0.25), and the target memory has not moved more than 45 m from the planning target. Wounded units may accept only retreat, regroup, or ambush. Teleport clears the maneuver state; pause/reset/mode changes invalidate pending work.
An accepted response restarts the chosen route at index zero, marks its source `jev`, and extends its earliest replanning time to at least three simulation seconds after acceptance. A rejected response leaves local execution in place.
## Timing, limits, and fallback
| Setting | Current behavior |
| --- | --- |
| Simulation | Fixed 1/60-second steps; independent of network latency. |
| Local replanning | Nominally every 4 simulation seconds. Significant perception events bypass this gate with a 0.65-second event cooldown; injury also interrupts. With no such event, an unfinished route may be retained for up to 20 seconds. |
| Request eligibility | Candidate set less than 2.5 simulation seconds old; one request per unit/revision. |
| Browser scheduling | One request in flight across all units; at least 0.6 simulation seconds between starts. |
| Relay scheduling | One upstream request at a time; at least 600 wall-clock milliseconds between starts. |
| Timeout | Relay 2,200 ms; browser 2,500 ms, both wall-clock timers. |
| Request body | Maximum 65,536 bytes. Oversized snapshots are rejected rather than automatically shrunk. |
| Provider budget | $1 USD over the previous hour, shared by tabs of one local relay. Ledger persists across restarts. |
| Errors | Local control continues; network/HTTP/timeout failures delay the next client request by 10 simulation seconds. |
| Uncertain/stale answer | Local control continues; ordinary scheduling resumes without the error backoff. |
These limits mean Jev does not make a fresh decision for every unit every frame, or even necessarily at every local replan. The client does not retry the same candidate revision after a failed request. A snapshot may already be some simulation time old when scheduled; the response-age test uses send time, while revision and target-shift checks provide additional protection.
### Replanning when Clu changes behavior
Local replanning now reacts to meaningful observation changes. Perception updates roughly every 0.2 simulation seconds. A first qualifying change can replace the maneuver on the next planning update; subsequent event interruptions are limited to one per 0.65 simulation seconds. The planner compares current knowledge to the observations used for its last plan, so a change during the cooldown remains eligible afterward.
| Event | Response |
| --- | --- |
| Lose or regain visual contact | Generate fresh search or pursuit candidates. |
| Observe a turn, stop or acceleration changing the target velocity vector by at least 8 m/s | Recompute the maneuver from that observed velocity. |
| Recorded target position shifts at least 45 m from the planning observation | Recompute the approach without waiting for the normal four-second gate. |
| Gain contact through radio or lose it to expiry/confirmed destruction | Reconsider the plan from the new knowledge state. |
| No significant event, route still unfinished | May retain the maneuver for up to 20 seconds from planning time. |
| Health reaches the retreat threshold | Interrupt a non-retreat/non-regroup plan when the tactical planner next runs. |
| Movement fails swept collision validation | Stop invalid motion and clear the plan for replacement. |
| Hear an unknown sound without target memory | Clear the plan subject to the separate four-second hearing interruption cooldown. |
These changes preserve velocity and yaw momentum. An aircraft already executing its committed attack follows the existing attack state machine rather than switching tactics mid-drop. Ground units refresh their route when they receive a new tactical plan.
When a healthy unit loses sight, local scoring favors `search-track` and `search-branch` over waiting or regrouping. Track prediction uses the recorded velocity, a two-second search lead, and at most five seconds of total extrapolation. It stops at mapped obstructions. Once near that estimate, it marks the origin checked and does not reinstate the return-to-origin maneuver merely because the unit moves away. Candidate locations come from a bounded corridor graph: cardinal 8 m steps, a 160 m path-length budget and at most 1,800 nodes, with every edge swept against walls using the tank footprint. This excludes nearby but disconnected spaces. Choices favor nearby unchecked locations and the recorded travel direction, and penalize destinations already selected by nearby allies. Both aircraft and ground units record completed checks for this search episode. A new visual contact or materially displaced recorded contact resets the search; expired knowledge ends it.
Aircraft already above the roofs inspect these corridor locations from above without descending at every checkpoint. A settled low aircraft may use a feasible low route. The graph describes where Clu could travel; the aircraft execution route is separately checked against its own hull. `searchPath` exposes the hypothesis to Jev, while `route` remains the actual vehicle maneuver. Arrival advances the search without waiting for exact final yaw or a full stop. The planner omits stale attack-pose candidates when visual contact is absent and stops offering direct pursuit back to an already checked origin. No hidden Clu position enters this search, and searching alone never authorizes a stomp or cannon attack. Wounded units still favor withdrawal.
A new candidate revision becomes eligible for Jev under the existing range/rate rules. Replies are rejected if sight/contact state or the observed velocity/position has materially changed, even before the event cooldown allows another plan. Revision checks also reject answers to superseded plans. Network scheduling remains serial; local reactions do not wait for a provider reply.
## Relay, credentials, and deployment
The Vite dev/preview plugin reads `TYPESAFE_API_KEY`, including Vite's environment loading, or falls back to the raw key in `credentials/Typesafe.txt`. Credentials stay server-side; those files are ignored by Git and denied by Vite file serving. Restart the server after changing the key. No key is stored in browser preferences, snapshots, or the built JavaScript.
`GET /api/jev/status` returns `{configured, model, remaining}` without the key. `POST /api/jev/decision` accepts the snapshot. The relay rejects mismatched Origin hosts when that header is present; it is a local development service, not a complete authenticated public gateway.
Earlier direct-browser tests found TypeSafe rejecting localhost CORS preflights, so the client uses the same-machine relay. That is an observed integration constraint, not a claim about all future provider configurations. The public GitHub Pages build now uses the Cloudflare relay described below; local development retains the same-machine relay.
### Public Cloudflare relay
Public builds set `VITE_JEV_API_BASE=https://tron-jev.tron-canyon-run.workers.dev`. The browser sends the same snapshot contract to `/api/jev/decision` there. The fixed model and question are assembled on the server using shared `shared/jev-protocol.js`; the client cannot supply an upstream URL, credential or question template. The Worker bounds incoming JSON to 32 KiB and serialized provider input to 64 KiB, enforces a 2.2-second provider timeout, validates the returned candidate/confidence, and never returns the key. `/api/jev/status` exposes only configured/model. See README for deploy and key rotation commands.
`workers/jev/budget.js` uses a single globally named SQLite Durable Object and synchronous transactions to reserve requests before calling the provider. The initial limits are 1,000 calls and 16 MB of submitted input per UTC day globally; 300 calls/day and 60/minute per IP; 750 ms minimum spacing; one pending call per IP and four globally. Failed calls count. Reservations expire after ten seconds if release fails. Limits survive Worker restarts/deployments; the next UTC day starts a fresh allowance. Missing or failed budget storage denies the request without contacting Jev. Only counters, daily keyed IP hashes and short-lived leases are stored, not snapshots or raw IP addresses. These bounds limit request volume, not exact dollar spend.
CORS allows only the two website origins, but public clients are anonymous and Origin is not authentication. Non-browser abuse can still exhaust the shared budget; the persistent caps bound provider usage. Players behind the same IP share limits. Public clients space request starts by at least 1,200 ms of wall time. The browser honors the exposed Retry-After header using wall time, even across simulation resets, and continues local tactics. Provider failure also retains local control. Disable calls with `JEV_ENABLED=false` or adjust the named quota variables and redeploy. The game remains static on GitHub Pages; only Jev decisions use Cloudflare.
## Inspection, source files, and checks
The production HUD shows a small top-right warning when Jev is deliberately off, unavailable, or rate limited. Service notices persist during pause/reset/retry and clear on a successful reply, even if its choice is too uncertain to apply. Normal idle or low-confidence decisions do not produce a service warning. Local enemies continue running. The keyed warning area can also display other explicitly reported warnings/errors.
Shift+T shows AI mode, status and the latest decision details. The development state exposes up to 12 recent responses with unit ID, request snapshot, response, acceptance flag and measured wall-clock latency. This is in-memory diagnostic history, not a persistent replay log. Changing AI mode via Apply restarts the run.
| Source | Responsibility |
| --- | --- |
| `src/simulation/recognizers.js` | Perception, dated radio sharing, aircraft updates. |
| `src/simulation/tactical.js` | Candidate generation, local choice, snapshots, acceptance and aircraft execution. |
| `src/simulation/search-routes.js` | Connected corridor hypotheses for lost-contact search. |
| `src/simulation/maneuver-geometry.js` | Hull checks, swept clearance, overhead and corridor routes. |
| `src/simulation/ground-tanks.js` | Ground tactical goals and weapon behavior. |
| `src/ai/jev-client.js` | Decision coordination, round/request tickets, acceptance and diagnostic history. |
| `src/ai/jev-transport.js`, `jev-request-policy.js`, `jev-participants.js` | HTTP/deadlines, pacing/retries/cooldowns, and information-limited participant selection. |
| `shared/jev-errors.js`, `shared/jev-pricing.js` | Typed relay failures and common cost calculation. |
| `server/jev.js` | Local credential loading, relay and development service limits. |
| `shared/jev-protocol.js` | Shared validated provider question. |
| `workers/jev/index.js`, `workers/jev/budget.js` | Public proxy, CORS and persistent usage budget. |
| `src/game/tactical.js` | Named tactical/request tunables. |
| `src/app/game-app.js`, `development-tools.js` | Browser composition, preferences, lifecycle and detached development inspection. |
Run `node --test tests/tactical.test.js tests/jev.test.js` for geometry, pursuit, hidden-information invariants, range gating, answer acceptance and relay/client behavior. `tests/tactical-browser.mjs` covers mode switching, wall-side attack, fallback and lifecycle; its optional `--live` mode permits one provider request. See [Validation](../validation/index.html) for commands, recorded results and unresolved human review.
## Where to give Jev more authority
The current boundary gives Jev tactical selection but leaves much tactical creativity inside candidate generation. A useful next experiment is to let it request an intent plus a bounded destination/heading/altitude, then ask the local planner to find a feasible route and report failure. Squad-level assignments, explicit timing/coordination, and fair scheduling would be additional extensions. These are proposals, not implemented behavior.
Keep movement integration and collision enforcement local in those experiments. The opening-pursuit defect demonstrated why: a correct tactical choice cannot compensate for an executor that brakes and returns to an obsolete waypoint. Compare decision quality separately from route execution, and measure latency, rejection rates and per-unit service coverage before attributing behavior to the model.
Local client requests now have a 650 ms wall-clock minimum, retained across resets. Local 429 responses distinguish in-flight requests, spacing and the rolling-hour dollar budget and include Retry-After. Purchased JEV credits do not reset this game-owned budget. The status endpoint reports spent/remaining USD over the rolling hour.
Local spend accounting uses `usage.input_tokens` at $0.042 per million input tokens; output is free ([TypeSafe pricing](https://typesafe.ai/blog/introducing-system-one-models-and-jev)). The rate is named in `shared/jev-pricing.js` and must be updated if provider pricing changes. Before sending, reserve a conservative estimate of two tokens per serialized payload byte plus 4,096 overhead tokens. Replace the estimate with reported billable input usage on a parsed successful response, even if its decision is invalid. Unknown/failed outcomes retain reservations until expiry. This is local cost accounting, not an account-wide provider-enforced billing cap; unreported usage and tokenizer overhead are estimates. `.local/jev-spend.json` is written before dispatch and retained across restarts. Use one local relay process per ledger. Tracking begins with adoption; previous requests cannot be reconstructed. Public Worker quotas are unchanged. Configure `JEV_HOURLY_BUDGET_USD` (default 1); the old request-limit setting is no longer used.
### Beam navigation correction — September 21
Player objectives expose every beam’s red/transitioning/blue state, `distanceMeters` and hull-relative `bearingRadians` (zero ahead, positive right). `objective.activeBeamId` identifies the persistent destination. The prior 500 m routing cutoff is removed. A coarse graph around maze bounds links swept local A* routes across the open grid. The pilot retains the chosen beam and remaining route across replans, changes destination after collection, and retries unreachable routes after 15 seconds. In the absence of maneuver threats, the available choice is the active mission route (or hold during transfer); short exploration is a fallback only when no beam route can be found or all beams are blue. Combat still offers evasive maneuvers. This is intentional mission commitment in the local planner, not additional independent navigation intelligence demonstrated by JEV.
Autoplay continues through window blur and hidden-tab transitions. The hidden loop skips rendering and uses a 250 ms timer with fixed simulation steps and at most one second of catch-up per callback. Explicit pause is respected; if JEV failure disables autoplay while unattended, the simulation pauses. Browser/OS suspension can still limit progress.
The motor preserves waypoint progress when the current choice is confirmed, skips visible intermediate waypoints, brakes for tight turns, and creeps into blind intermediate corners at up to 2 m/s and retains steering down to 2 cm before requiring the next leg to be visible. Uncollected beams within 2 km supersede a distant commitment. Turbo state is exposed in `self.turbo`; local safe-runway checks trigger normal turbo input and regulate the boosted speed. Manual driving overrides suppress automatic turbo activation.
Post-combat correction: `visibleEnemies` remains all observed contacts; `threatIds` identifies contacts within 100 m or within 250 m moving toward Clu faster than 2 m/s. Only those threats enable escape choices. Threat appearance/disappearance triggers immediate replanning and clears failed-route cooldowns; late escape responses are rejected when no threat remains. While red beams remain, peaceful route failure allows maze-entry recovery or hold/retry instead of aimless exploration. These thresholds are local tactical heuristics and use observed velocity, never enemy private pursuit state.
Blueprint routing correction: browser play defaults to the blueprint maze, while historic Node fixtures default to the authored maze. Autoplay now first searches outward from the beam with 8 m cells, a 12,000-node cap and 4 km detour allowance, reversing the result for Clu. This avoids exhausting an inward search in the open exterior before entering the blueprint corridors. Swept wall clearance remains mandatory. `tests/autoplay-blueprint.test.js` explicitly selects the browser layout and verifies local beam capture from its opening position.
### Timeout recovery and round statistics — September 22
Timeouts now allow two retries before disengaging autoplay (three consecutive timeouts). Minimum backoff is 0.5 then 1 second, respecting existing request pacing. The local pilot continues during retry, and successful responses reset the streak. Budget/authentication/other service failures still disengage immediately. Pause/reset cancellation does not count as failure.
Top-right JEV statistics count every client decision attempt this round, including enemy, player and retry requests. Requests/sec uses a trailing ten-second window. Cost uses reported `usage.input_tokens` at the shared $0.042/million rate; pending or unreported outcomes retain conservative payload-based estimates and show a ~ prefix. Failed requests may therefore overestimate spend. Totals survive pause and mode toggles; a new run starts a new stats object so late responses from prior rounds cannot contaminate the new totals. This display is per client round, not the persistent server-wide rolling-hour ledger or the provider’s full account bill.
## Ownership and freshness after cleanup — September 22
The gameplay candidate/input/output contract remains unchanged. `AutoplayMission` builds player options while `RouteFollower` exclusively owns their execution cursor; JEV confirmation cannot restart an accepted route. Enemy and player request snapshots are detached from live state. A request ticket captures the round, unit identity, plan and teleport revisions, and observation time; obsolete responses cannot mutate a new round or replaced unit.
The client separates cancellation, scheduling reset and round reset. Provider cooldowns survive round reset; round statistics restart only for a new round, and late usage updates settle the original accounting object. Local and Worker error bodies carry typed timeout/cancelled/unavailable/limited/invalid codes; only timeouts receive the existing two retries. Legacy relay timeout text remains a compatibility fallback. The transport wraps browser fetch and timers to preserve their native calling convention, fixing the Safari “Window.fetch” receiver regression. No credentials enter shared modules or browser snapshots.
The current implementation/validation and ownership map are maintained in [Refactoring](../refactoring/index.html) and [Validation](../validation/index.html). Live-provider decision quality and the later-maze autoplay stall remain separate from transport correctness.
## Carrier duty and visible debris — September 22
Escorts with no fresh target or sound investigation now return to the carrier through a dedicated local controller in every AI mode. The tactical planner no longer replaces this duty with maze patrol. Air escorts climb to roof clearance and track the moving formation slot without treating it as a stop waypoint. Ground escorts commit to separated routes around the maze perimeter, release the detour on reaching the far side, and use up to 1.4× ordinary speed only while returning to escort formation. Fresh target observations still interrupt escorting and use the existing tactical/JEV selection.
Player snapshots now include `visibleDebris`: at most 24 nearest currently observed pieces within 160 m and unobstructed wall visibility. Each record contains `id`, `x`, `s`, `y`, `vx`, `vs`, `vy`, planar bounding `radius`, `halfHeight`, `gravity` (m/s²), and `sleeping`. These are detached physics observations, not enemy intent or hidden state. The player prompt asks JEV to avoid the predicted debris path and notes that supplied routes are wall-checked, not guaranteed safe from moving wreckage.
`src/simulation/debris-avoidance.js` guards the selected motor command every simulation tick. It predicts 2.5 seconds of tank acceleration, turbo, braking and steering against ballistic piece motion and ground-level wreckage. If the desired command intersects a hazard, it compares braking and left/right avoidance commands, rejects wall-crossing paths, and disables a new turbo request. It retains turret/fire inputs and uses the ordinary vehicle physics. At rest, braking does not become unintended reverse. Normal motion resumes when the path clears, including after debris removal. Manual controls still override their channels.
The prediction omits future contact impulses and changing debris orientation, so it is conservative about bounds but cannot guarantee immunity from debris. Local avoidance does not wait for JEV; model snapshots update at the existing plan cadence. No provider limit, request interval, or damage rule changed.
## Stable attack lead and expanded communication — September 22
`src/game/communication.js` defines a radio radius of one `MAZE_LENGTH` for the active world, rather than 304.8 m. The same radius gates target/neutralization broadcasts, newly materialized units’ awareness and tactical ally snapshots. Observation timestamps and message delays are unchanged. This is a radius, not a diameter; it scales per scenario and does not reveal hidden Clu movement.
Within 140 m of a recorded target, aircraft with radio contact and compatible memories reserve an attack lead for up to twelve seconds. The lead must have visual contact and be healthy and ready to strike; a committed attack retains ownership. Distance fluctuations alone do not replace a valid reservation. Blocked, destroyed, injured, cooling-down or visually disconnected leads yield; an expired reservation gives another eligible aircraft a turn.
The enemy snapshot’s `attackAssignment` records lead ID, lease times, heading and support slot. Supporters get separated flank goals, omit strike/low-approach candidates while supporting, and cannot initiate a stomp even if a delayed choice arrives. Assignment changes invalidate the previous tactical plan/revision. Close physical separation moves the supporter aside instead of displacing the lead. Distant pursuit still uses the existing intercept behavior. The automated two-aircraft encounter now allows one attacker to complete a stomp without simultaneous competing drops.
## Ground pursuit connectivity — September 22
Blueprint ground patrol spawns now require a swept route to the maze exterior. The blueprint contains enclosed free-floor pockets; checking only hull clearance previously allowed tanks to spawn where they could never join a pursuit. Checks are cached per world and hull radius.
Tank route execution retains a valid route while its destination remains close to the route's original target. If the normal 12 m search fails during pursuit, a bounded 8 m search allows larger detours. If no complete route is found, it can approach the closest explored reachable point that improves distance by at least 12 m. Every segment still has swept hull clearance; a genuinely inaccessible contact cannot authorize crossing a wall. These changes use recorded sightings/radio reports and static geometry, and apply in Classic, local and JEV modes without changing the API contract.
## Planning workload — September 22
Enemy ground A* now runs incrementally through `GroundRoutePlanner`, sharing 192 search steps per simulation tick in round-robin slices of 24. A unit follows its existing usable path while a replacement is pending; stale destinations and removed units discard pending work. Startup validation and player mission planning retain a synchronous adapter. Priority queues replace full frontier sorts in ground and aircraft route search.
Aircraft and tanks share a budget of one newly generated tactical candidate set per simulation tick. Pending units retain their current maneuver; their motors, perception, radio and collision updates still run at the fixed simulation rate. First plans and replans may therefore be delayed by a few ticks under load. The expensive lost-contact search map is constructed when that planning work is admitted. Budgets and pending searches are round-local and do not enter API snapshots. Provider scheduling, billing and the JEV input/output contract are unchanged.
## Approved landing tactics — September 22
Recognizers may deliberately land to block Clu’s path and create a crush opportunity for an ally. Landing to rest is also acceptable. These are approved future maneuver choices, not yet implemented options in the candidate list. A stationary grounded unit should be evaluated by its intent and progress, rather than automatically classified as stuck. Coordination must continue to use observed or communicated information.
## Confined stomp positioning — September 23
The game now generates strike destinations using nearby wall-edge headings and a bounded two-ring search in sixteen directions around the predicted contact. This gives JEV feasible choices in angled corridors and beside walls, without changing the input/output contract. Offsets stay inside the existing attack trigger with an arrival margin. Execution settles within 0.35 m and brakes yaw before descent, retaining full collision clearance, fresh visible observations, lead/support coordination and the existing damage radius. Local tactical control uses the same candidates and controller. Classic is unchanged. Landing searches run only for visible contacts and remain subject to the shared per-tick planning budget.
## Persistent attack support — September 23
The coordination layer recruits nearby informed aircraft and retains their assignments while they move to support positions, within radio contact and the existing target-memory lifetime. The lead retains exclusive stomp authority; satellites no longer abandon support merely because they have moved beyond 140 m or lost personal line of sight. One satellite is designated spotter and the others cover separated ground-connected branches near the recorded contact. If no branch is available, distinct overhead positions provide a fallback. Goals are cached until the formation or recorded target shifts; roles and goals appear in the existing `attackAssignment` snapshot. These are positioning roles, not new weapons or permission to see hidden Clu. Existing lead leases, blocked/dead lead handoff, fresh-sighting attack gates and wounded retreat remain active.
### Progress-aware lead renewal — September 23
A leader now renews its twelve-second lease after five meters of measurable combined horizontal closure and descent toward the recorded target. Previously the fixed deadline swapped two approaching aircraft between lead and support, repeatedly cancelling the approach. This renewal is local coordination shared by Tactical and JEV, not a change to the provider contract. Blocked, destroyed, ineligible and genuinely stalled leaders still hand off.
## Pursuit attack execution and runtime toggle — September 23
N switches between JEV and local tactical enemy control without restarting the round, aborting pending requests. Turning JEV off also disengages Clu autoplay; U explicitly enables JEV again when starting autoplay. The stats box shows the toggle and its state. Request/cost totals remain for the current round.
Pursuit now ends at an available wall-aligned stomp pose. Local execution can initiate a stomp during pursue, strike or low-approach when the actual pose is safe; it does not depend on the provider selecting strike. Leadership, healthy condition, fresh personal sight, cooldown, angular braking, predicted landing drift and full swept clearance still gate commitment. Supporting and retreating units cannot take this opportunity. The request/response schema is unchanged.
### Smarter stomps with original motion — September 23
- [x] Preserve the earlier film-like movement: thrust 24.2 m/s², yaw cap 0.62 rad/s, yaw acceleration 0.8 rad/s², ordinary drag/braking, 1.3-second fold, and 85 m/s² powered stomp descent. Remove all close-attack performance overrides. Cruise/max speeds are unchanged.
- [x] Improve decisions rather than physics: approach overhead before descending, retain a progressing attack lead, align for nearby walls, and allow a healthy pursuing lead to take a safe attack opportunity.
- [x] Brake as soon as the predicted stopping path provides a valid stomp, rather than insisting on an exact waypoint or redundant final heading. Keep fresh personal sight, cooldown, drift prediction and swept wall clearance checks.
- [x] Stomp drop/landing contact with debris cannot damage the attacker; normal flight and weapon hits remain vulnerable.
- [x] Double horizontal cannon scatter: Clu ±1.2°, enemies ±2.4°. Level aim has zero vertical scatter; gunner shots remain exact. Deliberate vertical auto-aim to airborne targets is retained.
- The user’s motion-fidelity correction supersedes both the universal four-second stomp deadline and the subsequent three-second full-rotation tuning. Regression cases check successful attacks from eight headings without exceeding original thrust/yaw limits, not an artificial deadline.
### Superseded turn-speed experiment — September 23
Historical experiment, now superseded by restoring original motion above: maximum yaw rate is 120°/s (one revolution per three seconds at full speed), with 240°/s² acceleration/braking and the normal smooth heading response. All flight uses the same limit, including close attacks. Close-attack speed reduces when a turn needs a tighter radius, avoiding repeated orbits. A turn from rest includes acceleration and settling time, so a backwards-facing attacker can exceed the four-second stomp target. Eight-heading tests verify bounded yaw rate/acceleration and successful stomps; aligned attacks retain the four-second check.
### Helicopter-style Recognizer translation — September 23
Recognizers can thrust sideways and backward independently of yaw, in local/JEV navigation, Classic navigation and carrier escort flight. The acceleration vector shares the original 24.2 m/s² budget across all directions (no diagonal bonus); drag, braking, speed caps and angular inertia remain unchanged. Tight-corridor planning includes lateral/reverse translations with swept hull clearance. A wall-aligned hull can slide into position without first turning broadside. Existing pursuit-to-stomp opportunity checks and support assignments remain active.
### Lead and intercept approaching Clu — September 23
- [x] Choose a reachable crossing ahead of observed motion, accounting for approach, normal acceleration/braking, folding and fall time. Bounded prediction uses recorded position/velocity and stops at mapped obstructions.
- [x] Hold the chosen crossing while sightings follow the predicted path, instead of continually moving it farther ahead. Changed velocity, lost sight, injury or an expired interception still permits replanning. JEV candidate facts include approach time and predicted impact time.
- [x] Keep the lead's attack plan when only supporting positions change. Supporting units still refresh their own routes.
- [x] Preserve a useful wall-aligned facing during overhead translation. Start folding while Clu approaches; commitment still requires fresh personal sight and a physically clear descent, and a committed drop cannot track an evasive maneuver.
- All existing speeds, accelerations, fold/drop timing and health rules remain unchanged.
## Light-cycle arena participants — September 30
Autonomous cycles on both teams now share the JEV client while the player is in the arena. The human cycle is excluded. Each eligible bike can request a corridor destination every four seconds, with fair oldest-request-first scheduling under the existing global rate limits. `controller: "cycle"` selects the light-cycle prompt in the shared local/public relay protocol.
Snapshots contain arena-local meter coordinates, self pose and reserves, nearby unobstructed cycle observations and up to three currently clear corridor destinations. Responses bias local direction scoring for up to six seconds. Blocked cells and cramped-space avoidance remain local; this is strategic guidance rather than frame-by-frame remote steering. Stale replies (over 2.5 simulation seconds), low confidence, death, escape and reset invalidate choices. Pause cancels requests; switching to local clears goals. Network failure leaves local driving active. Teammate turbo conservation and human controls are unchanged. Road-mode cycles still use local driving.
The updated Worker must be deployed with the client to enable the cycle-specific production prompt. Automated tests use mocked decisions; live JEV driving quality is not yet validated.
## Public usage cap removal — October 2
The user requested that TypeSafe balance control total public usage. `wrangler.jsonc` now sets `DAILY_REQUESTS`, `DAILY_INPUT_BYTES`, `IP_DAILY_REQUESTS` and `IP_MINUTE_REQUESTS` to `unlimited`. This supersedes the prior daily quotas. The relay retains only per-request size/timeout rules and short-term overlap/pacing controls. Local development's hourly dollar cap is separate and unchanged. A browser that received a previous daily-cap cooldown should reload to clear that in-memory cooldown after deployment.
---
Source: /fun/TRON/docs/models/
---
title: Models
subtitle: Imported vehicle sources and runtime adapters
---
Both files were supplied by the user on September 12, 2026 after downloading from Sketchfab. Embedded asset metadata identifies the following sources; original binaries remain unmodified.
| File | Source / author | Terms recorded |
| --- | --- | --- |
| tank_-_tron_1982.glb | [Tank — Tron (1982), arabinowitz](https://sketchfab.com/3d-models/tank-tron-1982-b730d118f54b4a868339a4ddf11e09a7) | Embedded SKETCHFAB Standard; creator description additionally says editorial purposes only. |
| tron_1982_recognizer.glb | [Tron 1982 Recognizer, Shriker1](https://sketchfab.com/3d-models/tron-1982-recognizer-ecb52608275c40a28d7d9dda1aed54f3) | Embedded SKETCHFAB Standard. |
License link from both files: [Sketchfab licenses](https://sketchfab.com/licenses). TRON is Disney's property. This records the supplied files' provenance and creator restrictions, not an independent grant of film rights.
## Runtime modifications
The adapter in src/rendering/models.js bakes the export transforms and uniformly scales both models. The tank is 6.5 meters wide (about 9.37 meters hull length); it faces local -Z. Original polySurface1–13 form the rotating cannon/canopy assembly around the circular base. The barrel remains level; recoil translates the assembly slightly. A muzzle flash is added, with simulation coordinates shared through src/game/tank.js.
Tank armor uses cool-blue specular highlights. Red trim is non-emissive, gold is limited to upward-facing shoulders, and lower side panels remain dark. The white insignia has only minimal emission. Hull and turret cast projected silhouettes onto the floor. The extra rectangular contact-shadow plane has been removed. Recognizer trim retains emission.
Recognizer geometry is shared between five aircraft, with separate materials for independent hit flashes. Uniform normalization gives 15 meters overall height and approximately 18.38 meters width at the existing 0.5 simulation scale. Collision remains a simplified independent volume.
Vite packages both GLBs into local dist/assets URLs. There is no Sketchfab runtime request or account requirement. Startup waits for both models and shows a reload message if either fails.
## Verification
node tests/models.mjs checks muzzle alignment across nine hull/turret combinations, Recognizer scale and independent materials, and writes test-results/imported-models.png for visual inspection. Browser controls and lifecycle checks are recorded in [Validation](../validation/index.html).
Recognizer articulation now splits the Base and Light triangle meshes into a body and two legs at the shoulder seam. Original normals, UVs and materials are retained. Each leg turns 180 degrees around its upright center while sliding inward to form the crush pose. Geometry is shared among aircraft; each has independent pose and hit-flash materials.
## Sark’s carrier
User supplied `TRON CARRIER DAE` on September 13, 2026. The included readme credits JIHS, with original design by Syd Mead, and identifies the model as Sark’s carrier from the 1982 film. Source: [JIHS / TurboSquid](https://www.turbosquid.com/3d-models/3d-tron-1982-carrier-model-1811129). The listing records a Standard license; the supplied folder has a technical readme but no separate license document. Preserve the purchase license with the source archive.
`scripts/prepare-carrier.mjs` converts the untouched Collada file into `tron_1982_carrier.glb` using Three.js ColladaLoader and GLTFExporter in Chrome, repairing relative texture URLs in memory. All seven referenced textures are embedded. Measured export: 77 meshes, 64,519 triangles, 6.86 MB; bounds approximately 1,224.65 × 201.96 × 277.01 meters. Source units and proportions are preserved.
`src/rendering/carrier.js` centers the model and adapts its materials to a dark hull with illuminated trim. It travels along world +X at 24 m/s, center altitude 360 m, starting three times the spawn-to-maze distance ahead of Clu (six maze half-widths), 9,000 m to the left of the initial heading. Motion follows simulation time, so standby freezes it and restart restores the transit. It is scenery with no combat or collision behavior. It continues along its line without wrapping. The camera far plane is 12 km, and the carrier is exempt from ground fog. The standalone `/carrier.html` preview provides orbit, pan and zoom with original materials.
## Localized Recognizer destruction
The nearest posed triangle to the reported projectile impact selects the body, left leg or right leg. Only that section is partitioned into 8–13 randomized surface fragments; both other sections detach as whole pieces with gentler spin and impulses. Folded poses, original trim and inherited velocity are preserved. Every source triangle belongs to one piece. This remains a surface breakup effect, not a solid-volume fracture simulation.
Carrier contrast correction: restored source hull colors and textures, reduced blue emission, and adjusted roughness for directional shading. Applied a small depth offset to the original illuminated trim so coplanar hull surfaces do not hide it. Inspected the revised in-game capture; `npm run test:browser -- --carrier` passed.
Carrier trim balance: softened red paint and emission, increased the original blue trim emission, and added muted blue lines at actual armor boundaries (25-degree edge threshold, excluding coplanar triangle edges). This makes the smaller hull panels readable without raising the hull fill.
Recognizer destruction refinement: the earlier rig grouped the complete upper assembly as one body, so one body hit could fragment crown, crossbar and shoulders together. Breakup now treats crown, crossbar, left shoulder, right shoulder and the two articulated legs as six sections. Only the struck section fragments; the remaining five detach whole. Section boundaries use the source triangle centers, preserving every original triangle.
Clu destruction now uses the same posed-surface breakup system: a stomp hides the intact tank and its projected shadows and scatters randomized tank fragments. Current turret yaw and tank momentum are captured by the destruction event. Shadow geometry and muzzle effects are excluded. Thin blue boundary seams outline tank fragments; red boundary seams outline fractured Recognizer sections. Interior triangle diagonals are excluded. Seams fade and dispose with debris. Restart restores the intact tank.
## Carrier escape shuttle
[Model notes and GLB](carrier-shuttle/index.html) · [Interactive preview](../../shuttle.html). Reference reconstruction from the supplied film stills and escape sequence, with provisional scale and hidden surfaces.
## Grid cloud
[cloud.glb](cloud.glb) contains one complete connected grid cloud extracted from `extra/tron_1982.glb`, with no neighboring cloud fragments. Original cyan emissive material and imported scale are preserved; source transforms are baked to Y-up and the cloud is centered at the origin. It contains 1,046 vertices and 846 triangles and spans approximately 735 × 695 meters in the XZ plane. [Preview](cloud-preview.png).
Source: **Tron 1982** by **jvouillon**, from the source file's embedded metadata: https://sketchfab.com/3d-models/tron-1982-d7b1e9a03bca4bb6aa636b56ae45ec88 — CC BY 4.0. Attribution and modification notes are retained in the exported GLB. Reproduce with `python3 scripts/extract-cloud.py`. The game reuses this asset for its decorative drifting cloud layer.
## Solar Sailer — September 22
Source: `extra/TRON_SUNSHIP DAE/TRON_SUNSHIP.dae`, supplied with seven vehicle textures and the JIHS readme. The original design credits Syd Mead, Jean Giraud (Moebius), and Peter Lloyd. The readme references TurboSquid but does not specify redistribution terms. The source is retained unchanged.
Run `node scripts/prepare-solar-sailer.mjs` with Vite on port 5173 to regenerate `tron_1982_solar_sailer.glb`. The browser conversion repairs the source `file://` texture paths in memory and embeds all seven textures. Source geometry has 136,380 triangles; the 162 m overall bound includes a short yellow beam. The adapter hides that beam, anchors the craft on its axis, preserves proportions at uniform 2.5× scale, and orients the sails to lead the +X transit. A fixed amber beam stops at the rear/front hull bounds and resumes on the other side. Materials retain textures with adjusted sky fill and emissive rigging.
Reference reviewed: `docs/references/videos/carrier and solar sailer.mp4`, 1:34–1:46. Film-derived scale and exact visual fidelity remain approximations; this is scenery, not a new combat unit. Lane placement and cadence are named settings in `src/game/solar-sailer.js`.
The first Solar Sailer pass starts at three simulation minutes and repeats every three minutes. Its beam eases in over the three seconds before each crossing and out over nine seconds afterward; both remain absent during the initial quiet period. Pause and restart use the same simulation clock.
## Extracted light cycle arena — September 23
[Standalone GLB](extra/tron_1982_light_cycle_arena.glb) · [Preview](extra/tron_1982_light_cycle_arena-preview.png)
Extracted from the user-supplied `extra/tron_1982.glb`: seven architecture meshes (arena walls, broken-wall interiors, upper surfaces, lights, signs and graffiti), 9,329 triangles. Vehicles, light trails and surrounding scenery are excluded. Original materials, embedded textures, scale and proportions are preserved; the arena is centered in XZ with its base at Y=0. Bounds are approximately 168.05 × 12.33 × 168.05 in imported units. No new interior floor was fabricated.
Original: **Tron 1982**, **jvouillon**, [Sketchfab source](https://sketchfab.com/3d-models/tron-1982-d7b1e9a03bca4bb6aa636b56ae45ec88), **CC BY 4.0**, as recorded in the source GLB metadata. Attribution is also embedded in the extracted root's extras. Reproduce with `node scripts/extract-light-cycle-arena.mjs` while Vite runs on port 5173. The script reloads the exported bytes and renders the preview. The original file is unchanged; this asset is not integrated into the game.
## Daniel Preti light cycle arena — September 23
[Converted GLB](preti_light_cycle_arena.glb) · [Overview](preti-light-cycle-arena-preview.png) · [Interior](preti-light-cycle-arena-interior.png)
Source: Daniel Preti's **TRON 1982 — Games Grid Lightcycles**, supplied locally in `TRON Game Sector+LightCycles-Obj`. This is a purchased model, not the CC-licensed jvouillon scene above; retain its seller license. The OBJ states meters. The isolated arena spans approximately 931.44 × 60 × 931.43 meters and is centered in XZ. It is an inspection asset, not yet integrated into gameplay.
The conversion retains Group2 arena walls and signage only, excluding Component_55’s modeled floor, exterior Group1 scenery, cycles and trails. The source uses large concave polygons: they are triangulated with Earcut while preserving winding. Original MTL diffuse colors are mapped to nonmetallic glTF materials; double-sided rendering retains SketchUp back faces. The modeled floor is omitted entirely. `src/rendering/arena-floor.js` renders a separate white-on-black grid on one plane, using the game’s 24-meter spacing, 0.10-meter line half-width, derivative antialiasing and distance fade. Other authored colors are retained. The absent `TRON___LIGHTCYCLE.png` texture belongs only to omitted cycles, so the arena has no missing texture dependencies.
Reproduce with `python3 scripts/prepare-preti-arena.py` (Python standard library plus the installed Three.js Earcut helper in Node), then `node scripts/preview-preti-arena.mjs` with Vite running. Chrome successfully reloads the standalone GLB and renders both views. The floor-free export contains 8,349 triangles and is 447,196 bytes, down from roughly 2.24 million triangles and 54 MB. The procedural grid is a runtime shader, intentionally separate from the GLB. Original OBJ/MTL files remain unchanged.
### Arena edge treatment
`src/rendering/arena-style.js` provides a reversible runtime material/edge adapter for the film reference. Walls use near-black fill and thin blue-gray outlines; authored `_23` symbols use deep purple fill and violet outlines. Geometry edges omit coplanar triangle diagonals. Depth-tested lines and a small polygon offset on the solid surfaces keep the outlines readable without showing through other walls. The adapter restores source materials and disposes its GPU resources when removed. Source GLB materials are unchanged.
`node scripts/preview-preti-arena.mjs` passed in Chrome with no page errors; overview and interior previews were inspected. It draws 6,329 edge segments over the 8,349-triangle arena. Reference frames live in `docs/references/images/cycle arena/`. This treatment is currently in the standalone preview, pending arena gameplay integration.
## Standalone Preti light cycles — September 24
The supplied Daniel Preti OBJ contains three complete bikes. Extracted [gold](preti_light_cycle_gold.glb), [blue](preti_light_cycle_blue.glb), and [red](preti_light_cycle_red.glb) GLBs retain their authored proportions and materials, with separate wheel details included and the old trails omitted. They are centered, grounded and face local -Z. Regenerate with `python3 scripts/prepare-preti-arena.py --cycle gold` (or `blue`, `red`). Original meter scale is retained in the files; the game adapter scales each bike uniformly to 3.6 m long.
These inherit the purchased arena asset's usage constraints and Daniel Preti attribution. The source references an absent `TRON___LIGHTCYCLE.png` texture on a small decal; that patch uses its source diffuse color. Gold and blue are loaded for the arena's two teams; red remains available as a separate model.
The cycle rendering adapter removes duplicate/degenerate faces and gently smooths export quantization while pinning material seams and hard edges. The tiny hub caps had centimeter-quantized coordinates and cracked sliver faces; they are reconstructed as smooth ellipsoids at measured source bounds. Source GLBs remain unchanged. Authored smooth normals are retained, blue is adjusted toward the film reference, and cycle-only ambient attenuation and a compact per-bike depth atlas provide directional shading and self-shadowing.
The original 158 MB arena OBJ was deleted at the user’s request on September 24 and is not stored in Git. The extracted GLBs, conversion scripts, material file and reference images are retained. Re-running the extraction requires restoring the purchased OBJ locally first.
## Arena signature archive — September 24
The four Daniel Preti signature/date inscriptions are preserved in `preti_arena_signatures.glb`, at their original arena coordinates. They are no longer in the runtime arena GLB. `python3 scripts/split-arena-signatures.py` reproduces the split from the converter output: 116 inscription meshes / 4,884 triangles archived; three architecture meshes / 3,465 triangles remain. Materials and author metadata are retained in both files.
## Double-turret boss tank
User-supplied `extra/double_turret_clu_light_tank.glb`: **Double turret Clu Light Tank** by **SpringSociety**, [Sketchfab source](https://sketchfab.com/3d-models/double-turret-clu-light-tank-83df27ea6c08441a809bb5376301d430), **CC BY 4.0**, from embedded metadata. Original GLB remains unchanged. Runtime adapter uniformly normalizes hull width to 6.5 m, turns forward to -Z, grounds the hull, replaces materials with dark blue-black armor and non-emissive red contour lines, articulates the twin-gun upper assembly, and adds paired muzzle flashes and projected shadows. Both projectile origins are measured from barrel-tip geometry. Four bosses patrol the central beam courtyard alongside the large-maze patrols; 12 health versus 3, with 3 damage per Clu shot, and 80% normal movement speed.
### Cycle inscription cleanup — October 5
The game and credits adapters omit the separate `DANIEL_PRETI` / `daniel_preti` signature/date meshes (29 per bike) from loaded light cycles. Original source GLBs, metadata and extended-credit attribution remain intact; no chassis or wheel meshes are filtered.
---
Source: /fun/TRON/docs/models/carrier-shuttle/
---
title: Carrier escape shuttle
topTitle: TRON
subtitle: Reference reconstruction of the carrier escape pod
---
[Open the interactive model studio](../../../shuttle.html) · [Download the GLB](carrier-escape-shuttle.glb)
## Model
A locally authored, faceted Three.js model based on the supplied TRON (1982) stills and the escape sequence in `MCP destroyed.mp4`, 2:34–2:57. The studio supports orbit, zoom, fixed detachment/reference/side/top/rear/underside views, a turntable and GLB export. This is a standalone asset, with no new game behavior attached.
The revised model uses an open asymmetric shell: two ribbed overhead surfaces, two downward-hanging end blades, a single outer side wall with a recessed green panel, and a narrow connecting saddle. The first solid-pontoon interpretation was rejected in comparison with the detachment still. Materials are assigned directly to geometry, avoiding dependence on external textures. Source: `src/rendering/carrier-shuttle.js`; preview: `shuttle.html`.
The 20 × 14 × 9 m envelope is provisional. Absolute dimensions, internal saddle details, side-wall thickness and forward-axis assignment remain inferred. This is a reference reconstruction, not a recovered original film model. These areas should be revisited as more views become available.
## Supplied references
- [Side / three-quarter](../../references/images/Carrier%20Escape%20Pod/Escapepodside.webp)
- [Top](../../references/images/Carrier%20Escape%20Pod/Escapepodtop.webp)
- [Back](../../references/images/Carrier%20Escape%20Pod/Escapepodback.webp)
- [Detachment](../../references/images/Carrier%20Escape%20Pod/Escapepoddetach.webp)
- [MCP destroyed clip](../../references/videos/MCP%20destroyed.mp4), 2:34–2:57
User-supplied film references; personal project. Geometry authored for this project from those references. The GLB embeds its source and provisional reconstruction notes in scene metadata.
September 18 orientation correction: the shell opens downward, and the ribbed surfaces are viewed from below. Reference and detachment cameras look upward; the principal light and hemisphere fill now come from below. The exported GLB includes the corrected orientation.
## Meshy replacement trials — October 2
The authored model above was rejected by the user for reference mismatch. Meshy candidates are being evaluated against the original film stills, not against the reconstructed `shuttle.png` or wireframe illustration.
- [First Meshy candidate](../../../shuttle.html?model=meshy): four original wide film frames, Meshy 7.1, 2K geometry and textures, approximately 30,000 target triangles. This trial incorporated unwanted carrier detail from the detachment frame and is rejected as a replacement.
- [Focused Meshy candidate](../../../shuttle.html?model=meshy-isolated): the large side view and existing tight film detachment crop only. This trial avoids the carrier but fills in important open sections; it is not an accepted replacement.
Generation uses `scripts/generate-meshy-shuttle.mjs` with `submit`, `status`, and `download`; `--isolated` selects the two-view trial; `--detail` selects only the tight film detachment crop. Each submission is explicit and refuses to overwrite an existing task ID. Credentials are read only from ignored `credentials/Meshy.txt`. No key or signed download URL is recorded in the model metadata. `task.json` records source-image hashes, generation settings, task ID and consumed credits. Models and previews are downloaded locally before the service links expire.
Source images are user-supplied TRON (1982) film references. Meshy supplies generated geometry/materials; this does not establish rights to the original film design. Hidden surfaces and absolute scale remain inferred. The viewer applies a uniform scale to a 20 m longest dimension and preserves generated proportions. No shuttle gameplay behavior is added.
- [Detachment-only candidate](../../../shuttle.html?model=meshy-detail): final trial using only the existing tight film detachment crop. The output is fragmented in both the remeshed and original meshes and is rejected.
All three requests completed at 35 credits each (105 total). Browser checks on macOS Chrome verified loading, six viewing angles and GLB export for the four-view and detachment-only trials, including the original detachment mesh. These technical checks do not establish visual fidelity: none is accepted as a replacement. The existing default model remains unchanged.
## Smoothed symmetric shuttle — October 2
The user accepted the two-view candidate's overall shape and requested smoother lines plus left/right and top/bottom symmetry, superseding the earlier rejection of that silhouette. The default studio now shows its cleaned variant: [open viewer](../../../shuttle.html?model=meshy-clean) · [download GLB](meshy-clean/shuttle.glb).
`src/rendering/shuttle-cleanup.js` welds coincident vertices for smoothing, flattens broad near-axis-aligned panels, clips to one quadrant and mirrors it across the model's X and Y center planes. Creased normals retain harder transitions. A neutral gray material replaces noisy generated textures for this geometry review. Small inferred details remain provisional; the original generated asset is preserved for comparison. The exported model contains 37,000–39,000 triangles. No additional API credits were used.
Chrome browser checks capture six views, verify a mirrored vertex exists across each axis, and export the cleaned GLB. The reference-angle capture was inspected. Geometry symmetry is tested independently of lighting, which can make opposite sides appear different in brightness.
## Flat panels and rounded front corners — October 2 correction
The global smoothing pass was rejected. The current default is an explicit panel reconstruction: [viewer](../../../shuttle.html?model=planar) · [GLB](planar/shuttle.glb). All surfaces and edge intersections are orthogonal except the two quarter-circle front corners. Those alone have smoothly varying surface normals. Width and height remain mirror-symmetric, with straight ribs, a rear center notch and a recessed green front panel. Proportions follow the accepted two-view silhouette; exact fidelity remains subject to visual review.
Source: `src/rendering/shuttle-planar.js`. This supersedes the smoothing approach; prior variants remain available only for comparison. The mesh has 1,532 triangles. A geometry test verifies world-space reflection symmetry and rejects non-axis-aligned faces outside the front corners. Chrome captures all six views and verifies GLB export; the reference-angle capture was inspected.
## Restore the detailed structure — October 2
The flat-panel reconstruction was rejected for removing too much structure. The default is now [the detailed Meshy-based variant](../../../shuttle.html?model=meshy-preserved), with a [separate GLB](meshy-preserved/shuttle.glb). It retains the two-view source's detailed surfaces and material maps rather than substituting simple boxes. Global relaxation is disabled. Only tightly clustered, nearly axis-aligned surface vertices are flattened (0.3% of the relevant dimension), with quadrant mirroring for width/height symmetry. Normal-map intensity is reduced to avoid amplifying generated noise.
The structural detail is restored, but this is a conservative correction—not a claim that every generated edge now has the exact requested hard-surface construction. The earlier whole-body rebuild and global smoothing variants remain available for comparison. Chrome checks pass for mirror correspondence, six-view captures and export; the reference and top captures were inspected. No API credits used.
---
Source: /fun/TRON/docs/multiplayer/
---
title: Multiplayer light cycles
topTitle: TRON
subtitle: Authoritative arena, bandwidth reduction, and staged low-latency hosting
---
## Selected low-latency hosting plan — October 14
Adopt the staged migration below, based on the hosting proposal review. Keep the static client on GitHub Pages and AI services on the existing Cloudflare Worker. Target one Google Cloud VM for the authoritative public arena, first over WebSockets and then with WebTransport plus WebSocket fallback. Preserve the shared JavaScript simulation, public queue, AI vacancy filling and current gameplay. This is the implementation plan, not authorization to purchase infrastructure or publish a deployment.
The current production arena uses the Cloudflare Durable Object. Protocol 2 bandwidth reductions are in the October 14 release: the compatible Worker and matching static client are deployed and production browser checks passed. The measured 93.2% reduction is a five-second twelve-cycle benchmark, not evidence of lower production latency. The historical browser-host/WebRTC proposal later on this page is superseded by this plan.
### Stage 1 — Establish the compact-protocol baseline
- [x] Release protocol 2 through the authorized October 14 deployment: compatible Worker first, then static client; retain protocol 1 for cached clients and verify baseline repair.
- [ ] Instrument round-trip time, jitter, snapshot age, server tick drift/catch-up, input-to-visible-turn latency, reconciliation distance/frequency, bytes per client and server CPU. Report median and tail values, distinguishing local predicted response from authoritative confirmation.
- [ ] Record browser/version, hardware, player locations, region, player/spectator counts, protocol, input sequence and network conditions for reproducible comparisons. Test two humans and a full twelve-cycle arena with spectators.
- [ ] Capture close cutoffs, turbo/braking, simultaneous turns and deaths in real two-person play. Set acceptable latency and correction thresholds from those observations before selecting a new transport.
### Stage 2 — Move the existing WebSocket authority to a VM
- [ ] Extract a reusable server host around the existing room/hub; keep simulation at **120 Hz** and normal snapshots at **20 Hz**, with urgent updates for applied control changes. Do not change simulation timestep or collision behavior as part of hosting migration.
- [ ] Benchmark candidate Google Cloud regions near actual players. Run the same recorded encounters over WebSockets on the VM and the Durable Object to distinguish hosting/scheduling gains from transport gains.
- [ ] Select a VM from measured worst-case tick time and sustained load, including all twelve cycles and the configured spectator limit. Do not assume a cheap shared-core instance gives consistent CPU scheduling.
- [ ] Set up TLS, automatic process restart, health checks, bounded connections/queues, monitoring and certificate renewal. Use a DNS-only arena record for direct access; allow TCP 443 and, for the next stage, UDP 443. Account for the origin being directly reachable.
- [ ] Keep queue/round state in memory initially. A server restart resets the arena; clients reconnect with backoff and rejoin as new participants. Do not promise seat recovery without implementing reconnect identity.
- [ ] Retain the existing production endpoint as a configurable rollback destination. Migrate between rounds; never let two servers independently referee the same match.
The proposed $25–60/month is a provisional base infrastructure budget, not a total-cost guarantee. Estimate VM, IPv4, disk, outbound bandwidth, logging and monitoring from measured player/spectator hours before provisioning. See [Compute pricing](https://cloud.google.com/products/compute/pricing/general-purpose) and [network pricing](https://cloud.google.com/vpc/network-pricing). No assumed future Cloudflare UDP release is a dependency.
### Stage 3 — Add a loss-tolerant WebTransport adapter
Keep the same authoritative room and simulation for both transports. Prove a maintained server implementation can host the shared JavaScript authority before committing to a transport library. Negotiate protocol capabilities and fall back to WebSockets when WebTransport is unsupported or UDP is blocked.
| Information | Delivery requirement |
| --- | --- |
| Replaceable motion state | Unreliable datagrams, independently decodable or based on an explicitly acknowledged baseline; discard stale updates |
| Turn/control inputs | Sequenced, bounded repetition and acknowledgments; retry until acknowledged or explicitly expired/rejected |
| Trail creation/removal | Reliable sequenced events, or repeated acknowledged events with equivalent recovery guarantees |
| Join, queue, round boundaries, confirmed deaths and scores | Reliable stream with round/event IDs and duplicate protection |
| Initial state and repairs | Reliable complete snapshots, with event/baseline IDs to coordinate subsequent updates |
- [ ] Replace the current previous-snapshot delta dependency before using datagrams. A lost packet must not invalidate all subsequent state. Add periodic keyframes/checksums and bounded full-state repair; never permanently lose collision geometry with a dropped trail update.
- [ ] Respect negotiated datagram size limits. Split or route larger recovery/history payloads through reliable streams rather than sending oversized datagrams. Bound backpressure and drop obsolete motion updates.
- [ ] Define input send cadence, retry deadline and rejection behavior. Acknowledge the highest consecutively resolved sequence, or use selective acknowledgments for gaps; highest-received sequence alone is unsafe under reordering. Deduplicate repeated inputs and prevent a later command from silently discarding an earlier turn.
- [ ] Synchronize server tick estimates and validate client timestamps within bounded windows. Keep the existing eight-tick input lead as a comparison baseline, then tune from measured jitter; a fixed 60 ms delay is not a fairness guarantee. Late inputs do not rewrite confirmed collisions without a separately designed rollback system.
- [ ] Restore authoritative local state and replay pending inputs for prediction. Reuse shared motion rules for speed, turbo, braking and acceleration; opponents do not move at constant speed. Reconcile predicted trails as well as positions and preserve authoritative eliminations/reward events.
- [ ] Define ordering across streams and datagrams using round/tick/event IDs. Handle death-before-position, round changes, delayed trail events and repairs without resurrecting a cycle, duplicating rewards or mixing rounds.
WebTransport provides unreliable datagrams and reliable streams; it does not remove geographic delay. Keep fallback for older browsers and blocked UDP. See [WebTransport datagrams](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/datagrams). The current [Workers protocol documentation](https://developers.cloudflare.com/workers/reference/protocols/) and [Containers architecture](https://developers.cloudflare.com/containers/concepts/architecture/) do not provide the direct inbound UDP game-server endpoint this plan needs; HTTP/3 support alone is not a WebTransport service.
### Stage 4 — Compare, validate and roll out
- [ ] Compare WebSocket and WebTransport on the **same VM, region and simulation**, using identical encounters and player counts. Report latency tails, corrections, bandwidth, CPU and tick drift; do not infer improvements solely from lower byte counts.
- [ ] Test 20/50/100/150/250 ms round trips, variable jitter, 0/1/3% packet loss, burst loss, duplication and reordering. Verify recovery when an input, trail event, baseline or round boundary is delayed or lost.
- [ ] Cover Chromium, Safari and Firefox, including unsupported WebTransport, blocked UDP, connection interruption, transport fallback, server restart, late join, solo Return restart and overflow queues. A reconnect must not create duplicate participants or simulation loops.
- [ ] Replay recorded inputs to verify unchanged authoritative collision outcomes. Test simultaneous cutoffs, turbo/braking and trail disappearance. Confirm bounded memory, queues and bandwidth under sustained twelve-cycle/spectator load.
- [ ] Record real two-person play and inspect visual corrections and apparent deaths; retain acceptable rendering performance and keep single-player independent of multiplayer networking.
- [ ] Enable WebTransport only if it improves the agreed latency/correction thresholds without correctness regressions. Publish measured costs and unresolved limitations; retain WebSocket fallback and a tested endpoint rollback path.
Planning validation: this update documents the selected architecture and acceptance gates only. VM, WebTransport, network-impairment and migration checks above remain unrun. Regenerate the HTML and indexes with `node docs/colvmn/static-gen.js docs`.
## Existing authoritative implementation — October 13
The user-selected design is one public, server-authoritative arena, reached through LIGHT CYCLES MULTIPLAYER. Six, nine or twelve cycles compete, with at most three cycles per team. Humans occupy available seats and AI fills the rest; additional humans spectate in a FIFO waiting queue. Surviving humans keep their seats, eliminated humans rejoin the back of the queue, and disconnects release seats. Teams are randomized between matches, with two, three or four teams; enough teams are added to seat up to twelve humans. Countdown expansion preserves the scheduled start time.
The local Vite server hosts the room at `/api/multiplayer`. Production support is prepared as one Cloudflare Durable Object named `public-arena-v1`, using the existing Worker and `MULTIPLAYER_ARENA` binding. The authoritative Worker is deployed and a production-origin WebSocket check confirms advancing snapshots and the three-cycle team cap. The static client has also been published; the protocol 2 client update was published in the October 14 release. Run `npm run dev`, choose the multiplayer menu, and open another browser window to join the same room. Single-player does not load the multiplayer client or start a multiplayer network loop.
The referee runs the shared simulation at 120 Hz and sends trail-delta snapshots at 20 Hz. The deployed baseline schedules sequenced inputs eight ticks ahead (about 67 ms). The October 14 local follow-up below removes that lead and applies accepted inputs on the next authority tick, retaining bounded timestamp acceptance. Clients extrapolate motion and predict their pending turns; collision and elimination remain authoritative. There is no rollback yet. A late command takes effect on a future server tick rather than rewriting a collision. This removes browser-host authority advantage but does not make differing network latency equal.
Human cycles carry a thick diagonal stripe, first half black and second half white, on the colored shell. Their roster circles mirror it; your circle has an extra outline. Eliminated circles retain their team outline with transparent interiors. Custom stripe patterns are a possible later addition.
The room stops when empty. Active rounds are bounded to three minutes or 2,048 trail segments, connections to 64, input messages to 512 bytes and 80 per second, with idle and slow-connection limits. An active simulation keeps its server instance awake: exact hosting cost needs measurement. No paid service was purchased. The deployment flag `MULTIPLAYER_ENABLED` can disable access.
Multiplayer does not pause when the window loses focus; held controls clear and input resumes immediately on focus. Escape still opens the return-home confirmation. The controls hint sits below multiplayer status.
### Verification and remaining work
- [x] Seven room tests cover capacity, queues, survivor priority, disconnected seats, team variation, input sequence/timing validation, trail deltas and control timeout.
- [x] Full simulation suite: 511 tests pass, plus a dedicated multiplayer audio regression. Production build and Worker dry run pass.
- [x] Headless Chrome on macOS: two rendered clients and twelve additional WebSocket clients share one twelve-cycle arena; overflow waits, local Return cannot reset the match, and single-player/home does not eagerly load the multiplayer client. Both renderers compile the human stripe without shader errors; inspected their captures.
- [ ] Real two-person play, delayed/jittery connections and close cutoff fairness.
- [x] Authoritative Worker deployed; live production-origin WebSocket snapshots verified.
- [ ] Measured server CPU/bandwidth costs, abuse protection beyond basic connection/input limits, and sustained load.
- [ ] Reconnect identity and seat recovery. Currently a new connection is a new participant; browser tabs are separate players and server restart resets the room.
The earlier browser-host proposal below is retained as an architecture comparison, not the selected implementation. Its private rooms, WebRTC and rollback milestones have not been implemented. Pricing references in that proposal were checked October 13, 2026.
## Why timing matters
The arena currently simulates at 120 Hz and cycles normally travel 38.4 meters per second. One simulation step moves a cycle 0.32 meters. A 50 ms delay means 1.92 meters of travel; 100 ms means 3.84 meters, longer than the 3.6-meter bike. Network delay cannot simply be hidden behind smooth animation when a new wall decides whether someone lives.
Those distances use actual elapsed delay, not automatically the displayed ping. Round-trip time includes both directions; half the ping only approximates one-way delay on a symmetric connection. Jitter buffers and input scheduling add more time. Rendering at 60 fps does not make a delayed turn timely.
Local steering should respond immediately through prediction. Remote steering will sometimes need correction. The difficult case is a late turn that creates a wall across another player's already-predicted path. Reconciliation must include trail geometry and collision outcomes, not just vehicle positions.
## Historical browser-host proposal — superseded
The remaining architecture comparison, prototype, budget and acceptance sections describe the earlier browser-host option. They are retained for reference and are not the selected migration steps.
### Architecture choices
| Approach | Strength | Main cost or limitation |
| --- | --- | --- |
| Browser host with WebRTC | Minimal server simulation cost; direct connection when possible | Host advantage, host cheating and suspension; TURN sometimes required |
| Authoritative server with WebSockets | Shared referee and simpler connection setup | Active server cost; delayed reliable packets can hold up newer data |
| Deterministic lockstep between peers | Very small input messages | Waiting for inputs delays everyone; exact reproducibility required |
| Peer rollback without a single referee | Immediate local prediction | Harder dispute handling and synchronized trail/death corrections |
Start with host authority, not full peer consensus. The host simulates all players and bots, accepts validated inputs, and broadcasts authoritative snapshots and events. Clients predict their own motion and reconcile against those snapshots. WebRTC is the transport; it does not itself solve fairness or synchronization.
Lockstep needs identical simulation results for identical inputs, and waiting for missing inputs can cause stalls. A fixed timestep alone does not prove determinism across browsers. See Glenn Fiedler's [deterministic lockstep explanation](https://gafferongames.com/post/deterministic_lockstep/).
### First playable prototype
1. Extract a headless arena session around the existing 120 Hz simulation. Preserve swept collision behavior, seeded randomness and stable cycle IDs. Version the simulation and arena data so incompatible clients cannot join.
2. Add private join codes and a two-human room. The host owns any bots. Keep JEV requests asynchronous and outside the synchronization path; record accepted bot decisions if replay needs them.
3. Send timestamped, sequenced turn and throttle inputs, initially batching at 30 Hz. Repeat unacknowledged critical inputs. Send state updates around 20–30 Hz while each client renders independently. Measure before increasing these rates.
4. Keep a short history of complete simulation state plus a trail creation journal. Rewind and replay all affected cycles and trails for accepted late inputs. Start with a bounded 100 ms history as an experiment, not a guaranteed fairness threshold.
5. Separate tentative collision feedback from confirmed elimination. Never award turbo, remove a cycle, or announce a result twice after replay. Define simultaneous collisions by simulation time and a consistent tie policy.
6. Validate input order, plausible timestamp age and legal turn rate. Drop inputs outside the history window rather than allowing arbitrary backdating. Show degraded connection status when corrections become frequent.
Use a reliable control channel for room state, starts and confirmed outcomes. A separate channel can carry disposable latest-state updates. Critical turns still need delivery or repetition and acknowledgments even if their transport is unreliable. Avoid resending the entire growing wall history each frame: send sequenced trail events, periodic checksums and occasional recovery snapshots.
The first prototype should end the room cleanly if its host leaves or suspends. Host migration, spectators, public matchmaking and competitive anti-cheat can wait. A malicious browser host remains trusted in this design; private casual rooms are the appropriate initial scope. Direct peer connections can expose network addresses to peers; relay-only mode trades that exposure for relay traffic.
### Hosting and budget
Use an isolated room/signaling endpoint alongside the existing Worker, with short room expiry and limited joins. Keep only ephemeral room metadata; do not persist every game tick. Issue short-lived TURN credentials on the server, never a permanent service key in the client. Rate-limit room creation and cap simultaneous rooms and relay usage independently of the existing AI endpoints.
Cloudflare lists TURN at $0.05 per outbound GB with a 1,000 GB free allowance; STUN is free. Verify allowance scope and account terms before enabling it. At a hypothetical total metered relay rate of 20 KB/s per room, one room-hour is about 0.072 GB. Actual traffic depends on players, packet overhead and how many connections require relaying. [TURN pricing FAQ](https://developers.cloudflare.com/realtime/turn/faq/).
For an authoritative Durable Object alternative, the paid plan includes 400,000 GB-seconds monthly and one million requests. Active objects are charged at 0.128 GB; incoming WebSocket messages use a 20:1 billing ratio. Extra duration is $12.50 per million GB-seconds, rounded up; the paid baseline is $5 monthly. An active simulation must not be budgeted as an idle hibernating lobby. [Durable Object pricing](https://developers.cloudflare.com/durable-objects/platform/pricing/).
Illustrative calculation: 100 room-hours consume 46,080 GB-seconds. Four clients each sending 30 messages/second produce 2.16 million billable request equivalents over those hours. This excludes joins, other Worker traffic, storage and existing account usage. These are planning calculations, not measured game costs. A small fixed-price server is another option if sustained usage grows, but adds maintenance and regional deployment work.
The cheapest practical first version should mostly pay for signaling and occasional relay traffic, with simulation on the host. Set application admission limits before any public launch; billing alerts alone do not enforce a spending ceiling.
### Acceptance gates
- [ ] Replay identical recorded inputs and compare complete state hashes, including trails, random state and confirmed deaths. Test Chromium, Safari and Firefox; if deterministic replay diverges, use authoritative state restoration rather than assuming lockstep safety.
- [ ] Test 20, 50, 100, 150 and 250 ms round trips, variable jitter, and 0%, 1% and 3% packet loss, in both direct and relayed connections.
- [ ] Test mutual cutoffs, simultaneous turns, trail crossings, disconnects, host suspension, late duplicate inputs and round restart.
- [ ] Measure input-to-visible-turn latency, rollback time, corrections per minute, reversals of apparent deaths and bandwidth. Review captures and actual two-person play; smooth rendering alone is insufficient.
- [ ] Keep 60 fps rendering on the target laptops while replay runs. Confirm the normal single-player path loads no multiplayer transport or background network loop.
- [ ] Decide an acceptable latency band from playtests. If even nearby play produces frequent unfair-looking deaths, try a small equal input delay and compare, or move authority to a nearby server before adding more players.
Expand to four humans only after two-player cutoffs feel convincing. The goal is satisfying casual play at low cost, not a promise that intercontinental close-contact racing can feel identical to local play.
## Snapshot bandwidth and transport follow-up — October 14
Protocol 2 transmits only presentation state and changes to that state. Coordinates are rounded to 0.0001 grid cells (0.48 mm); authority and collision calculations remain full precision. AI planning state, collision grids and render history stay server-side. The initial snapshot and new rounds are complete; later snapshots carry cycle/trail field patches, a baseline tick, and changed roster/crash data. Baseline mismatch requests a complete repair. Protocol 1 remains available for older cached clients; deploy the Worker before the client.
`node scripts/measure-multiplayer-bandwidth.mjs` compares both formats on the same five-second simulation. With twelve cycles, protocol 1 averages 197,499 bytes/sec per player and protocol 2 averages 13,360 bytes/sec, a 93.2% reduction before compression. Six cycles with one human drop 88.5%. Event-driven updates after applied human control changes supplement the usual 20 Hz updates. These are bandwidth results, not a measured reduction in live latency.
[WebTransport](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API) provides unreliable datagrams suited to replaceable position updates, with reliable streams for turns and game events. [WebRTC data channels](https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel) can also be unordered with limited retransmission. Both require a compatible peer/server; they are not raw UDP sockets exposed to browser JavaScript. The current [Cloudflare Workers protocol list](https://developers.cloudflare.com/workers/reference/protocols/) documents HTTP, WebSockets and TCP interfaces, but no direct WebTransport or WebRTC game-server API. HTTP/3 support alone does not supply the WebTransport session/datagram API. A future change would need a suitable separately hosted authoritative server or transport gateway, reliable event delivery, obsolete-update dropping, and WebSocket fallback. It may reduce stalls from retransmission under packet loss; it will not remove propagation latency. No new hosting has been provisioned.
## Remote motion smoothing — October 14 follow-up
Straight-line choppiness can occur without low rendering FPS: the old client restarted its extrapolation clock at each snapshot arrival, converting network jitter into position corrections. It also applied the local input lead to opponents whose future controls were unknown. Remote presentation now uses a continuous clock and a 75 ms snapshot buffer, interpolating through confirmed trail corners while keeping bike and trail endpoints together. The clock adjusts its rate within 10% to maintain the buffer and holds at the latest confirmed pose if updates stop. Local prediction remains immediate on its existing timeline; authoritative deaths and HUD state are not delayed.
The jitter regression at 144 Hz reduces worst normal-speed straight-line frame-step error from 1.344 m to 0.0267 m under the tested 15–50 ms delivery delays. A 50 ms buffer ran dry in that test. Jittered two-client Chrome checks and the build pass. These results do not establish real-player fairness or subjective smoothness: the remote presentation buffer adds delay, and long network stalls can still hold motion. Deployed October 14: TRON `b9b1948`, dekorte.com `c5a52e0`, Pages run `38608501930`. All 525 unit tests and the production build pass. Production Chrome confirmed the new playback code, 53 compact snapshots and solo restart with no page errors; the existing server is unchanged.
## Fixed-wall turn responsiveness — October 14 local follow-up
The next local change removes the fixed eight-tick lead for local presentation and input packets. Input timestamps still establish freshness, but no longer delay an accepted command: the server applies it on its next 120 Hz tick. Clients record a fractional keypress time privately, predict the turn at their displayed position immediately, and replay it until acknowledged. The preceding trail ends at the predicted corner. Remote cycles retain their separate 75 ms playback buffer.
With 1.8 m of clearance to the existing arena wall's collision boundary, tests at normal speed (38.4 m/s) and turbo (96 m/s) survive zero/two-tick input transit under next-tick handling, whereas the old eight-tick schedule crashes. Two ticks of transit move the accepted turn 0.64/1.6 m from the keypress position. Six ticks of transit (50 ms) is too late at that clearance for either approach. All 528 unit tests, production build and Worker dry run pass. These are controlled simulation results, not a guarantee of precise turning at arbitrary network latency.
No collision rollback or provisional death system is included. Inputs received after confirmed impact cannot restore the player. This follow-up requires both a server and client deployment; it is currently local only.
---
Source: /fun/TRON/docs/organization/
---
title: Project organization plan
topTitle: TRON
subtitle: Clear ownership for code, assets, references and generated output
---
## Scope
Implementation authorized October 14, including deletion of video references and removal from Git history. Preserve gameplay, existing public URLs, attribution, offline behavior and the current deployment process. Migrate one category per reviewed commit; avoid mixing folder moves with simulation or visual changes. The existing refactoring document covers object ownership; this plan covers repository structure and asset lifecycles.
## Current friction
- Runtime code already has useful boundaries in `src/simulation`, `src/rendering`, `src/audio`, `src/ui` and `src/app`. Keep these rather than introducing an engine framework.
- `src/game` mixes configuration and gameplay helpers. Multiplayer server code imports shared simulation from `src`, so the browser/server dependency boundary is less obvious than its actual responsibilities.
- `public` contains shipped audio, fonts and icons, while `docs/assets`, `docs/models` and `docs/references` contain a mixture of inputs, models, film references and supporting documentation. Some documentation assets are actual runtime dependencies.
- Preview HTML entry points and their source modules live at the root and under different source folders. Tests and scripts form broad flat lists; the package test command enumerates many individual files.
- Generated documentation HTML and indexes are committed beside authored Markdown. `dist` and `test-results` are ignored; other generated/intermediate assets need an explicit ownership map.
- Reference folders have ongoing user reorganizations. Inventory those working changes before any migration; do not infer that a missing file should be deleted from history or replaced.
## Proposed layout
| Location | Responsibility |
| --- | --- |
| `src/app/` | Browser startup, scene selection, lifecycle and input orchestration |
| `src/simulation/`, `src/game/`, `src/levels/` | Keep current layout initially; mark browser-free modules, then consider moving genuinely shared rules/configuration together into `shared/simulation/` |
| `src/rendering/`, `src/audio/`, `src/ui/` | Presentation only; no server or provider dependencies |
| `src/mcp/`, `src/multiplayer/` | Browser feature adapters and transport clients |
| `shared/` | Wire contracts, validation and headless authority; document allowed imports |
| `server/`, `workers/` | Local Node adapters and production Cloudflare adapters |
| `public/assets/{audio,models,fonts,images}/` | Shipped URL-addressable assets; adopt only with URL compatibility and cache migration |
| `assets/source/{models,audio,fonts,images}/` | Editable upstream inputs, conversion metadata and licenses; not copied into production by default |
| `references/{film,design}/` | Visual/sound study material, clearly separate from shipped assets; local-only heavyweight material remains outside normal deployment |
| `tools/{assets,validation,build}/` | Conversion, inspection and build tools; retain script wrappers while commands migrate |
| `tests/{unit,browser,fixtures}/` | Fast invariants, real-browser lifecycle checks and reproducible fixtures |
| `docs/` | Authored plans, source/license catalogs and validation reports, plus the current colvmn generated outputs |
| `dist/`, `test-results/`, `.local/` | Ignored reproducible output and local state |
Do not move `docs/colvmn`: it is an upstream submodule. Keep root Vite entry points until preview-route compatibility is explicitly tested. Leave encrypted credentials and ignored secret handling in place during structural work; never copy secrets into an asset inventory or public documentation.
## Asset catalog
Create one machine-readable manifest with a stable asset ID, category, source path, runtime URL, author, source URL, license/use constraints, credit ID, transformations, conversion command, output hash and deployment policy. Distinguish original source, approved runtime derivative, reference-only material and retired asset. Film-derived content must retain its actual provenance rather than being labeled open source.
Generate inventory/credit reports from the manifest only after reconciling it with current credits. An unused import search is not proof an asset is unused: inspect runtime fetches, CSS URLs, HTML, Worker requests, preview tools, PWA precache and IndexedDB keys. Keep original model backups until the user explicitly approves removing them.
## Migration stages and acceptance
1. **Inventory only.** List tracked/untracked assets, file sizes, duplicates by hash, import/fetch references and shipping rules. Record `npm run build` output size and the precache inventory. Acceptance: every shipped asset has a source and credit record; unresolved provenance stays visible.
2. **Document boundaries.** Add concise directory READMEs, mark generated outputs, and document browser-free shared modules. Acceptance: Node authority imports no DOM, WebGL, browser globals or provider keys; existing tests/build still pass.
3. **Organize tests and tooling.** Move one family at a time and update package scripts/import paths. Use deliberate test discovery that excludes browser scripts from unit runs. Acceptance: test count and coverage remain unchanged, CLI wrappers still work, and artifact paths remain reproducible.
4. **Separate references and source assets.** Resolve the user's in-progress reference moves first; move with Git history intact. Update catalog and converters together. Acceptance: source derivation works from documented inputs; production copy rules omit reference footage and editable masters.
5. **Organize runtime assets.** Move one asset category, retaining old URLs temporarily where necessary. Update hashed imports, fetches, preview pages, subdirectory base paths, IndexedDB versions and PWA cache manifests. Acceptance: no new 404s at `/fun/TRON/`, warm/offline loading works, and previously cached clients recover without losing unrelated data.
6. **Tighten shared code boundaries.** Move the minimum browser-free simulation/configuration needed by both server and client. Avoid a monolithic shared utilities folder. Acceptance: authoritative replay/unit checks, local multiplayer and Worker dry run pass; single-player still loads no multiplayer transport.
7. **Retire compatibility paths.** Remove only after deployed-client cache behavior and links are verified. Keep source/license history. Acceptance: production smoke checks, credits, documentation links and cache upgrades pass; rollback instructions identify the prior release.
For each stage, record moved paths, compatibility aliases, commands, browser/hardware, screenshots where relevant and remaining defects in the implementation plan. Deploy separately from unrelated behavior changes. Do not assume a successful build proves audio, camera feel, rendering or multiplayer latency are unchanged.
## First recommended slice
Start with the asset manifest and directory READMEs, without moving any runtime files. This gives us a trustworthy map and catches accidentally shipped source/reference material before a broader rename. Keep generated documentation beside Markdown for now because colvmn and the documentation copy scripts already depend on that arrangement.
## Implementation — October 14
Stages 1–6 are implemented. Tests now live in `tests/unit` and `tests/browser`; tools live in `tools/assets`, `tools/build` and `tools/validation`. Previous CLI paths forward to the new modules. The authority imports its browser-free simulation/configuration from `shared/simulation`; existing source paths remain forwarding modules.
Runtime imports live in `assets/runtime`, outside Vite public, to preserve content-hashed output filenames. Static URLs use `public/assets`. Original model inputs live in `assets/source`, and still-image references in `references`. All 100 existing user reference renames matched identical content before relocation. Untracked source collections remain untracked and are excluded from the committed catalog.
`assets/catalog.json` records provenance, hashes, conversion tools and unresolved attribution. `assets/path-map.json` retains old asset URLs through development middleware, production aliases and service-worker alias resolution. Production copies only authored/generated documentation and explicitly linked media, rather than recursively shipping source collections. The POV-Ray model license remains included.
At the user's request, all 16 local reference videos were deleted, video extensions are ignored case-insensitively, and video paths were removed from Git history. Extraction scripts retain expected ignored input paths for externally supplied footage; those inputs are no longer bundled. Existing audio derivatives remain unchanged. Hosted caches/LFS retention is outside the rewritten branch history.
Validation on macOS/headless Chromium: 531 unit checks, Worker dry run, PWA offline/range/update checks, two-client multiplayer under delivery jitter, and `/fun/TRON/` production-path smoke checks passed. Initial production output was 1,805,603,712 bytes; the reorganized build is approximately 289 MiB, with approximately 89.7 MiB precached. Final release validation is recorded in the implementation plan. Automated checks do not establish subjective driving or sound quality.
Stage 7 remains deferred: retain compatibility paths for this release until deployed cached clients and links have been reviewed. Never restore a pre-purge commit as a rollback: it would reintroduce video history. Use the rewritten history or revert code changes on the cleaned branch.
---
Source: /fun/TRON/docs/refactoring/
---
title: Refactoring review and design
subtitle: Explicit ownership, reproducible worlds and composed controllers
---
## Recommendation
Refactor incrementally. The game has useful module boundaries already, but several objects now own too many decisions and lifetimes. The best use of OOP here is to give stateful responsibilities clear owners and small public APIs, while retaining plain simulation records and pure geometry functions.
The findings below record the pre-refactor review on September 22, 2026. The implementation section describes the completed ownership extraction; it is not a claim that all runtime defects have been found. The authoritative implementation checklist is in the [project plan](../index.html). Existing behavior is the migration baseline; intentional gameplay changes should be separate commits.
## Findings grounded in the code
| Priority | Evidence | Consequence | Recommended boundary |
|---|---|---|---|
| First | `src/levels/maze.js` selects its layout from `location`, changes defaults between Node and browsers, and generates a separate browser layout seed at module load. `createRun(seed)` does not control that world seed. | The authored-layout tests passed while the actual blueprint opening could not find a route. A run seed alone cannot reproduce a browser encounter. | Explicit world/scenario construction shared by browser and tests. |
| First | `Autoplay.plan()` and `input()` combine perception, threat classification, objective selection, route caching, JEV options, waypoint advancement, braking, turret aim and turbo. `apply()` also owns waypoint progress. | Mission decisions, provider replies and motor execution can reset or override each other. The waypoint reset and corner-arrival stalls are concrete examples. | Separate mission planning from a persistent route follower and from request snapshots. |
| High | `src/main.js` owns startup, UI modes, manual controls, hidden-tab scheduling, fixed stepping, network updates, effects, audio, tuning and a large mutable debug surface. | Pause, restart, input takeover and error handling require coordinated edits across unrelated handlers. | An application coordinator composed from session, loop, input and HUD owners. |
| High | `createRun()` stores Clu's pose and controls on the same record as enemies, world events, projectiles, beams and session state. Many systems mutate that record. | Ownership is implicit; a presentation/controller change can accidentally affect unrelated simulation state. | A simulation session owns the record and ordered updates; gradually group state behind compatibility adapters. |
| High | `JevClient.update()` selects units, schedules calls, handles aborts/retries, applies decisions, tracks cost and directly disables autoplay. `reset()` clears some state while preserving quotas and round statistics through other fields. | Transport, game policy and lifetime rules are difficult to change independently. | Transport, decision coordination and request policy with explicit session/revision identifiers. |
| Medium | `View.render()` includes multiple camera modes, interpolation, vehicle synchronization, aiming, lighting, effects and render passes. `main.js` invokes `view.breakups.physics` to feed damage back into simulation. | Simulation ownership crosses into rendering; camera changes have broad regression risk. | Extract camera coordination first; move physical debris ownership to simulation through an adapter. |
| Medium | `Sound` owns sample loading, synthesis, spatial voices, listener updates, music selection and Web Audio lifecycle. `View.dispose()` combines subsystem disposal and scene traversal. | Resource ownership is harder to audit. Existing cleanup is substantial; this review does **not** establish a leak. | Explicit audio/visual owners with documented borrowed versus owned resources. |
| Medium | Browser stats import the request builder from `server/jev-protocol.js`; server budgeting imports pricing from `src/ai/jev-stats.js`. Local and Worker error mapping differ and timeout classification partly reads message text. | Dependency direction is confusing, and accounting/error policy can drift between deployments. | A small environment-neutral protocol/pricing module; keep deployment-specific quota policies separate. |
| Maintenance | Dense multi-statement lines, mutable global configuration and historical statements in `Goal.md` make current behavior harder to establish. For example, old beam/blast/teleport descriptions are superseded later. | Reviews and tests must infer both ownership and which description is current. | Format touched modules, add boundary contracts and distinguish current decisions from history. |
Do not start by converting every function to a class. `mergeAutoplayInput`, geometry intersections, projection math, trajectory calculations and snapshot construction remain good candidates for pure functions. Existing small objects such as `BeamCamera`, `JevStats`, `JevSpend` and `SystemWarnings` demonstrate useful bounded ownership.
## Design principles
- Prefer composition. No `GameObject → Vehicle → Tank → PlayerTank` hierarchy, custom ECS, service locator or general event framework.
- Use classes when there is meaningful private state, identity or a lifecycle. Use records for observations, poses, commands, routes and wire messages.
- Pass dependencies explicitly through constructors or factory parameters. Do not pass the whole application into every object.
- Keep simulation at 60 Hz. Distinguish simulation seconds, monotonic elapsed time for pacing, and persisted wall-clock timestamps for hourly budgets.
- Maintain the information boundary: enemy planning receives observations, aged radio/hearing reports and permitted map queries, never unseen live Clu coordinates.
- Keep rendering, audio, networking and DOM work outside the deterministic simulation step. Preserve the current ordered effects/damage semantics during extraction.
- Start with JSDoc contracts and targeted `// @ts-check` where useful. A TypeScript migration is not a prerequisite.
- Keep named units and configuration. Give a session a configuration snapshot; development tuning changes it through explicit commands rather than mutating unrelated module globals.
## Proposed ownership
These are responsibilities to extract as needed, not a requirement to create every named class immediately.
| Owner | Owns | Public responsibilities | Lifetime |
|---|---|---|---|
| `GameApp` | Composition, browser adapters, loading/error screens | Start, pause/resume, select autoplay, dispose | Page |
| `GameSession` | Run state, simulation RNG, event queue, controllers | Fixed-step advance, capture display state, restart from scenario | Round |
| `GameLoop` | One timer/animation-frame handle, accumulator | Schedule, suspend, bounded background catch-up, dispose | Page |
| `InputController` | Held keys, mouse state, queued actions | Produce manual command, clear transient input, dispose listeners | Page |
| `World` / `createWorld` | Layout geometry, derived sites, spatial queries | Clearance, visibility and map queries | Scenario |
| `AutoplayController` | Mission selection, current objective, threat transition state | Produce requests and commands, accept current decisions, enable/disable | Round |
| `RouteFollower` | Route identity, cursor, turn/arrival state | Advance a route using the observed pose; report progress/failure | Vehicle controller |
| `JevTransport` + decision coordinator | HTTP calls versus participant selection/application | Fetch a decision; coordinate fair requests and reject obsolete replies | Page, with round scopes |
| `CameraRig` | Follow/aerial/gunner/beam transitions | Resolve camera intent, update pose, reset | View |
| Visual/audio owners | GPU resources, voices, music | Consume display state/events, reset effects, dispose | Page with round reset |
| `HudPresenter` / debug adapter | DOM updates / development commands | Display immutable summaries; apply explicit fixture commands | Page |
A `World` can initially be the object already returned by `createMazeWorld`; it need not become a class just for naming symmetry. Extract pure candidate-building functions from `Autoplay` before inventing additional planner classes.
## Contracts that remove the current ambiguity
### Reproducible scenario
Introduce a plain `ScenarioSpec` containing `layout`, `layoutSeed`, `runSeed`, site count, spawn/encounter settings and relevant configuration. Browser URL/preferences choose it at the application boundary. Tests supply it directly without writing `globalThis.location`.
Derived pads, beams, carrier placement, patrol rosters and visual pool sizes must come from that same world. Merely injecting `World` into the route finder while leaving those as import-time constants would preserve the mismatch. Keep the existing module exports temporarily as adapters while migrating callers.
### Commands and state
Define `VehicleCommand` for throttle, steering, turret, elevation, fire/fire edge, mouse target and turbo. Both human input and autoplay produce this contract; the existing channel merge rule stays a pure function. Toggling views or autoplay is an application command, not a motor input.
Initially keep `createRun()`/`step()` callable to avoid changing every test. Wrap them in `GameSession` and extract responsibilities behind that seam. Later move Clu fields into a `player` record if it measurably improves ownership; do not combine that schema change with controller extraction.
Document the update order before moving it: current teleport/hearing checks, transfer locking, motors, enemy/carrier updates, weapons, beam completion, later teleport/hearing checks, debris impacts and event delivery. Apparently duplicate calls may be intentional same-tick boundaries.
Use explicit ordered event records for effects. Drain each batch once; listeners receive the batch rather than independently removing events. Do not create a global event bus. Decide and test same-tick versus next-tick debris damage before moving physics out of `View`.
### Mission, route and motor
Represent a route with an ID, world revision, objective ID and typed waypoints. Distinguish `passThrough`, `corner` and `stop` semantics; a beam stop is not a corner tolerance. Give `RouteFollower` sole ownership of its cursor and turn/creep state. Confirming the same JEV option must not restart it.
Return a result such as `following`, `arrived`, `blocked` or `needsReplan`, plus diagnostics: current waypoint, heading error, target speed, blocked segment and time without progress. Mission logic decides what to do with failure; motor code does not silently replace the objective or fall into endless hold/explore cycles.
Separate geometric route data sent to JEV from the mutable execution cursor. Keep plans versioned and snapshots detached from live state. Share clearance/search utilities between player and enemies, but do not unify their different sensing, attack and mission policies into one universal brain.
### Requests and lifetimes
Use a request ticket carrying `sessionId`, unit ID, plan revision, teleport revision and observation timestamp. A decision coordinator applies a reply only to the corresponding live participant. The transport returns a typed result, not direct mutations of autoplay or the run.
Separate operations currently folded into `reset()`: cancel pending request; pause scheduling; begin a new round; clear round history; dispose the client. Preserve provider cooldowns and the persistent spend ledger across round changes. Keep round stats across autoplay toggles and count attempts—including retries—according to the existing HUD definition.
Inject fetch, clock and timer scheduling for tests. Retry only typed timeout outcomes; preserve the two retries, explicit cancellation behavior, player/enemy fairness and user-selected autoplay state. Add a shared error code to local/Worker responses while retaining their different budget implementations. Never move credentials into shared/browser modules.
### Resources and presentation
Extract the camera state machine from `View` before reorganizing shaders. Give each effect an explicit owner for its unique geometry, materials, render targets and listeners. Shared model assets are borrowed and released by their asset owner. An optional small disposal helper is sufficient; avoid a general resource framework.
Keep shader composition order and program cache keys explicit. Materialization clipping, grid/pad shading, custom shadows and fog interact; a file move must not silently change their ordering. Preserve current visuals with captures before and after each extraction.
Split music policy from spatial voices only after the application/session boundaries are stable. Retain Safari audio unlock, pause/mute, source-ended cleanup and the existing sound character. No new audio engine is needed.
## Incremental delivery order
The checkbox status belongs only in the root project plan. Each item below should be a small series of reviewable changes, not one large rewrite.
### 0. Establish the baseline and contracts
Record both layouts, explicit seeds, runtime configuration and expected behavior. Add replay/diagnostic capture for route failure without credentials. Preserve the current third-maze route-failure report as an open defect; do not silently declare it solved by structural changes. Expand dense code only in the area being extracted, preferably in a separate mechanical commit.
Acceptance: reproduce the first-beam corner regression and two-beam success on the blueprint; authored comparisons still work. Save browser/device and relevant screenshots. Record failures honestly before implementation.
### 1. Make world construction explicit
Extract browser selection into bootstrap. Introduce the scenario factory and migrate world consumers gradually, including derived level data and view pool initialization. Keep a compatibility adapter until all imports can be moved safely.
Acceptance: two worlds with different seeds/layouts can coexist in one test process; equal scenario specifications produce equal placements; restart semantics match browser play; no core test needs browser globals just to choose the maze.
### 2. Extract route execution from autoplay
Introduce the route/waypoint contracts and `RouteFollower`; keep mission scoring and safety tuning unchanged initially. Add progress reporting and centralize route acceptance/invalidations. Preserve `Autoplay` as the public facade while moving its pieces. Only afterward use the clearer diagnostics to fix the separately documented third-maze failure.
Acceptance: same-choice replies preserve progress; short corners do not inherit final-stop tolerances; turbo reserves stopping space; destroyed threats trigger mission continuation; manual channel overrides and teleports remain correct. Test blueprint departure and second capture, plus the known later failure as a separately identified scenario.
### 3. Extract application/session lifecycle
Move scheduling into `GameLoop`, DOM/input into their adapters, and ordered fixed-step work into `GameSession`. Keep `main.js` as wiring. Use named application states and explicit transitions rather than a framework. Introduce the debris-physics seam without changing its time ordering.
Acceptance: one active loop after repeated starts; explicit pause freezes simulation; autoplay alone continues in the background; loss of JEV while unattended pauses; teleport interpolation does not smear; returning from the terminal resets exactly the intended state. Development inspection cannot mutate a session through an accidental shallow-copy getter.
### 4. Separate JEV coordination and transport
Move neutral request/response/cost contracts into a shared module. Extract transport and request policy without changing prompts, candidate choices, quotas or pricing. Give the coordinator small player/enemy participant adapters rather than importing entire navigation implementations.
Acceptance: fake-clock tests cover retries, cancellation, old-session replies, fairness, cooldown persistence and stats lifetimes. Local and Worker response-contract tests agree on error classification; their distinct quota tests still pass. Check browser builds for server-only imports and secrets. No paid requests are necessary for routine tests.
### 5. Extract camera and effect ownership
Move camera decisions into `CameraRig`; preserve `BeamCamera` as a composed controller. Then isolate vehicle visual synchronization/effect ownership where it reduces coupling. Keep the working shadow implementation unchanged during extraction.
Acceptance: compare follow/gunner/aerial/beam captures, reduced motion, pad entry/exit and horizon shots. Exercise repeated restart, reinforcement growth and disposal; compare GPU/resource counts without assuming identical counts prove visual correctness.
### 6. Separate audio policy and finish cleanup
Extract music direction and voice ownership, document lifecycle contracts, remove now-unused adapters, and consolidate current design documentation. Retain historical decisions as history. Add focused lint/format checks only after settling conventions; do not churn generated docs or third-party sources manually.
Acceptance: existing spatial/audio tests, pause/mute/reset and Safari unlock checks pass; listening review confirms no sound regression. Review import direction and public APIs. Each extracted object has a clear reason to exist and a small responsibility.
## Verification and stopping rules
Use the existing tests as characterization, not as proof the game is complete. Run targeted tests per extraction, then the full simulation suite and relevant browser checks at integration boundaries. Key coverage includes autoplay/blueprint, ground movement, tactical/hearing isolation, teleporters, beam sequencing, retries/stats/budgets, camera transitions and audio lifecycle.
A refactor stage is done when its ownership boundary is real, behavior is preserved or an intentional change is explicitly documented, and the old competing implementation is removed. Keep compatibility shims temporary and named. Avoid algorithm replacement, broad renaming, balance changes, deployment or a language/framework migration in the same change.
Recommended first implementation: stages 0 and 1, followed by `RouteFollower`. Those directly address the failures already encountered and provide the safest foundation for the remaining cleanup.
## Review validation
On September 22, 2026, `npm test` passed all 215 tests on the current macOS working tree. This establishes the automated baseline for this review; it does not resolve the separately recorded later-maze navigation defect. No new browser or visual checks were run for this documentation-only change. Runtime code was not changed as part of the review.
## Implemented ownership — September 22
`src/main.js` now creates the application and registers disposal. `src/app/game-app.js` composes `GameSession`, `GameLoop`, `InputController`, `HudPresenter`, the development adapter, view, sound, and JEV coordinator. It retains explicit loading/terminal/opening/running/paused/death transitions. The fixed simulation remains 60 Hz; hidden autoplay uses bounded timer catch-up.
`src/levels/scenario.js` constructs explicit worlds from layout, layout seed, run seed, and site count. Session configuration snapshots contain vehicle/encounter, flight, and hearing settings. Browser URL selection lives in `src/game/browser-scenario.js`. Pads, beams, rosters, carrier placement, collision, music visibility, and rendering pools derive from the same world. Restart uses the scenario run seed, including Classic, so a seeded URL is reproducible. Arbitrary spawn authoring is not a new supported API; existing fixture placement remains explicit. `levels/maze.js` and `DEFAULT_WORLD` remain deterministic authored-layout adapters for standalone tests, never a mutable current-world selector.
`GameSession` owns the run and settings. World/settings references are non-enumerable, keeping snapshots serializable. `snapshot()` and development state return detached copies. Tuning uses `configure(group, values)`; the single page view retains its presentation configuration. Ordered updates preserve teleport/hearing boundaries, transfer locking, motors, enemies/carrier, weapons, beam completion, debris contacts and one drained event batch. Physical debris now lives in `src/simulation/debris-physics.js`; presentation borrows the session's physics instance and does not advance or free it.
`Autoplay` composes `AutoplayMission`, perception utilities and `RouteFollower`. The follower alone owns its private route cursor, corner/stop/pass-through semantics, braking/turbo and turn state. Route identity includes world and plan revisions and teleport revision. Reconfirming an option preserves progress. Detached request snapshots and progress diagnostics separate JEV choices from execution. The existing later navigation stall is still open.
`JevClient` coordinates participants and request tickets. `JevTransport` owns HTTP/deadlines/cancellation; `JevRequestPolicy` owns pacing, cooldown and retry state; `JevStats` owns round accounting. `shared/jev-protocol.js`, `jev-errors.js`, and `jev-pricing.js` are browser-safe contracts shared by both relays. Credentials and persistent ledgers remain server-only. Tickets reject obsolete rounds, participants, plans, observations and teleport revisions. Native fetch and timer functions are called through wrappers, avoiding a browser receiver error found during migration. `cancelPending`, `resetScheduling`, `beginRound`, and `dispose` replace ambiguous reset behavior.
`CameraRig` owns camera transitions and composes `BeamCamera`; `View` retains visual synchronization and render passes. A deduplicated scene disposal helper releases GPU resources after subsystem targets. `Sound` owns the AudioContext and one-shots; `MusicDirector` owns the media element, cues and music bus; `RecognizerVoices` owns spatial voices. Borrowed resources are released by their owners. The application/session/audio disposal guards prevent duplicate release.
### Reproduction and verification
Use `/?maze=blueprint&layoutSeed=1982&runSeed=1982` or substitute `authored`. `node scripts/replay-autoplay.mjs` runs a credential-free, enemy-free fixed-step replay and writes `test-results/autoplay-replay.json`, including scenario, route and progress diagnostics. The current replay captures beams at approximately 110, 335 and 665 seconds, then stops after a 20-second hold at 706 seconds. This is diagnostic coverage, not successful completion of all mazes.
`npm test` covers world coexistence, settings isolation, fixed-step/event ownership, loop cancellation, input consumption, route progress, transport errors and receiver binding, policy lifetimes, plus existing gameplay contracts. `npm run test:browser -- --refactor` covers view modes, detached state, pause and repeated restarts. Three resets retained 97 geometries, 28 textures and one AudioContext in Chrome. Additional autoplay, beam camera, portal, music and spatial-audio regressions passed; see Validation for limitations. No pre-refactor pixel baseline or new Safari listening review was captured, so those human acceptance checks remain open.
Prettier is available for the extracted modules via `.prettierrc.json`; legacy modules and generated documentation were not globally reformatted. No ECS, inheritance hierarchy, language migration, or additional physics dependency was introduced.
---
Source: /fun/TRON/docs/references/
---
title: References
subtitle: Visual direction and source images
---
## Reference folders
Reference material is grouped by subject, with stills, clips and studies kept together: `cycles/`, `clu-and-tanks/`, `recognizers/`, `mazes/`, `carrier/`, `solar-sailer/`, `mcp/` and `terminals/`. Cycle markings and arena studies, Recognizer attacks and spotlights, and carrier escape-pod references have their own subject subfolders. New local screenshots live here; the previously archived collection remains under `references/film`, served through the subject paths recorded in `assets/path-map.json`. Older URLs remain compatibility aliases. Video files remain ignored by Git.
## Central labyrinth trace — September 23
The user supplied [Big labyrinth](mazes/Big%20labyrinth.png) as the fifth maze blueprint. [Trace overlay](mazes/labyrinth-trace.svg) shows the extracted contours against the source. Blue regions become walls; dark regions remain open. The image yields 116 wall islands and three interior holes, simplified to a one-pixel contour tolerance, then scaled at 2.4 m/pixel with 54 m walls. This keeps the finer passages usable by tanks while preserving the circular court.
`src/levels/labyrinth-outlines.js` contains the committed contour data. To retrace the PNG, run `scripts/trace-labyrinth.py` in a Python environment with `opencv-python-headless`. Normal builds require no Python: `npm run docs` regenerates the SVG from committed outlines. Both blueprint mazes share `src/levels/outline-maze.js` for collision and triangulation.
## Fixed reference studio
Open [Reference studio](http://127.0.0.1:5173/reference.html) with the development server running. Front tank, rear tank, maze overview, wall-detail and driving views place the supplied film frame beside the actual game renderer. An overlay slider helps compare framing. All views share the runtime materials, lighting, floor and maze; only fixed camera placement and overview fog scaling differ.
The Tank front preset now sets independent hull/turret yaw, with the cannon turned about 66 degrees across the hull. Its camera position and framing were adjusted against CLU Close Up, aligning the canopy, badge and lower-left barrel more closely. Other presets reset to their own poses.
The studio is a local development page. Use `node tests/reference.mjs` to capture all five views into test-results/reference. It is not included in the production game bundle.
Reviewed 2026-09-12. The [design conversation](https://chatgpt.com/share/6aa58280-6f74-83e9-a410-aa7c54032fe4) remains the source for the user's priorities. Its rejected generated images are not targets.
## Blueprint trace
[Play the traced maze](http://127.0.0.1:5173/?maze=blueprint) · [Compare top-down](http://127.0.0.1:5173/reference.html?maze=blueprint&shot=blueprint) · [Original maze](http://127.0.0.1:5173/?maze=authored)
The user-supplied Maze Blue Print.png is traced as 20 roof outlines, including concave branches and clipped image-edge shapes. The editable coordinates are in src/levels/blueprint-outlines.js. The image has no dimension labels: 0.8 meters per pixel and 54-meter wall height are provisional choices. Walls cut off by the image are closed at its boundary; unseen continuations are not invented. The blueprint loads by default in-game; the original remains available at /?maze=authored. The reference studio retains its original fixed-camera studies unless a layout is selected explicitly.
The roof uses triangulation of each complete outline. Driving uses the original polygon perimeter; projectile and sight rays use the same triangulated solid, preserving open notches. Both layouts share the existing tank, aircraft AI, materials and wall detailing.
[Open the trace overlay](mazes/maze-trace.svg): pale yellow outlines show the hand trace against the source. Regenerate it with `node scripts/trace-blueprint.mjs`.
## Inspected images
| Reference | Provenance | Observations used in this build |
| --- | --- | --- |
| [Tank side/three-quarter image](https://vgpavilion.com/mags/1982/fall/vgp/the-making-of-tron/050a-051a.jpg) | Published with *The Making of Tron*, Video Games Player, Fall 1982; magazine scan hosted by VideoGame Pavilion. Film timestamp not established. | Low, wide body; two large rounded drive pods; shallow upper dome; offset turret; long cannon; dark surfaces with broad blue highlights and restrained warm outlining. |
| [Recognizer frontal image](https://images.squarespace-cdn.com/content/v1/5fbc4a62c2150e62cfcb09aa/4172a3d7-ead3-4c6e-9c5b-bf25f1fa7691/tron%2B3.png) | Film image reproduced in [CultureSlate's article](https://www.cultureslate.com/explained/how-tron-changed-sci-fi-and-predicted-the-futurerozkjaa5ls2x4j3eeagsco95derz8g). Timestamp not established. | Very wide shoulder; central crown; inset trapezoidal front panels; two large legs with inward feet; a substantial empty opening beneath the body. Solid dark surfaces with red/orange edge detail. |
These images were downloaded to temporary storage for visual inspection. They are not bundled as game assets.
## User-supplied film frames — September 12 art pass
The user supplied 15 reference images, including the new Maze Details image, and renamed them descriptively. These are local study references; filenames do not establish film timestamps.
| Frame | Visual evidence |
| --- | --- |
| [Maze Blue Print](mazes/Maze%20Blue%20Print.png) | Top-down source for the optional traced layout. |
| [Maze Top 1](mazes/Maze%20Top%201.png) | Broad blue roofs, diagonal tips and dark channels. |
| [Maze Top 2](mazes/Maze%20Top%202.png) | Additional maze forms and wall faces. |
| [Maze Entrance 1](mazes/Maze%20Entrance%201.png) | Entry geometry and tank approach. |
| [Maze Entrance 2](mazes/Maze%20Entrance%202.png) | Grid-to-corridor transition. |
| [Maze Details](mazes/Maze%20Details.png) | Faint vertical seams, thin blue-gray edge highlights and stepped, angled ledges. |
| [CLU Close Up](clu-and-tanks/CLU%20Close%20Up.png) | Cannon, offset canopy, blue highlights and restrained red trim. |
| [CLU POV](clu-and-tanks/CLU%20POV.png) | Tank point of view and nearby geometry. |
| [CLU Firing](clu-and-tanks/CLU%20Firing.png) | Cannon firing reference. |
| [CLU Persued](clu-and-tanks/CLU%20Persued.png) | Pursuit and relative vehicle scale. |
| [Inside Maze](mazes/Inside%20Maze.png) | Rear/side tank proportions against dark walls and blue floor. |
| [CLU Wall Collision](clu-and-tanks/CLU%20Wall%20Collision.png) | Tank and wall contact reference. |
| [Recognizers Decend](recognizers/Recognizers%20Decend.png) | Recognizer approach and underside. |
| [Opening Shot 1](terminals/Opening%20Shot%201.png) | Terminal/opening reference. |
| [Opening Shot 2](terminals/Opening%20Shot%202.png) | Terminal/opening reference. |
## Applied changes
The tank and Recognizer now use user-supplied GLB models, with the scales and source credits recorded in [Models](../models/index.html). Tank red trim is subdued; Recognizer trim remains emissive.
The maze preserves the oblique branching slab layout. The new detail pass adds thin, non-glowing blue-gray perimeter/corner lines, sparse tall panel outlines, and shallow lower ledges with sloping highlight faces. Detail follows exposed wall faces; coplanar cell edges are joined to avoid drawing the underlying grid. Relief is cosmetic and at most 0.3 meters deep; the shared slab collision and sight volumes remain unchanged.
The home screen reproduces the two lines in the final terminal still with live blue text, character timing, a carriage-return pause and a faint perspective grid. Enter completes the text, then starts the game; reduced-motion preferences reveal it immediately. The local VT323 font approximates the lettering rather than reproducing an extracted film font.
## Remaining fidelity work
Compare the new browser build against these frames in motion. Materials, barrel supports, underside details, maze layout and camera composition can still be refined. User judgment of resemblance remains the acceptance gate. Exact clip timestamps have not been established.
Application screenshots are in `test-results/`; regenerate them with the browser checks described in the README.
## Open-ended simulation / diagonal slab correction
Maze Top 1 shows long blue masses, dark channels and diagonal shard-like ends. The initial square branching implementation was rejected as too blocky. The current maze keeps a connected topology but stretches and skews the two passage axes and clips exposed slab corners into diagonals. Roofs, wall faces, collision and occlusion share these polygons. The full map is inferred/authored; the supplied overview is perspective-limited and does not establish every hidden passage.
The user estimated upright Recognizer feet/legs at about 30 feet. The current half-scale adapter gives an approximately 9-meter upright leg, about 15-meter total height and 17.5-meter shoulder width. This is a user-guided scale choice, not a verified canonical measurement.
## Recognizer crush animation
The four user-supplied frames in recognizers/attack-animation show the legs turning and sliding inward until the stems meet, with the feet facing outward. The model adapter separates the original GLB triangles at the shoulder seams and animates those parts. The 1.3-second fold and accelerated drop are inferred timing, since these stills do not provide frame timestamps. The existing source model retains its simpler wedge-shaped feet.
Recognizer breakup study: inspected the local video at 106–116 s, including three frames per second across impact and separation. Visible traits are brief warm-white/yellow flashes, large angular body/leg sections separating, tumbling fragments and falling pieces rather than a sustained fireball. Runtime effects use randomized partitions of the actual GLB surfaces; fracture boundaries are procedural and not an exact reconstruction of film geometry.
## Carrier final-shot study
Source supplied by user: `carrier/carrier and solar sailer.mp4`, 120.604 seconds, 640 × 290, 30 fps. `carrier/carrier-tail-study.jpg` samples 112–119.5 seconds at 2 fps, row-major; `carrier/carrier-beacon-study.jpg` samples 114–115.9 at 10 fps. Red point lights at curved supports and tower corners visibly vary; continuous red outlines remain lit. White hanging lamp pairs appear steady. Cyan/green brightness variation is ambiguous because the viewpoint moves and the objects recede.
Runtime approximation: only small red lamp meshes pulse, about one second per cycle with a 0.34-second envelope (short attack and release); three offset groups avoid synchronized flashing. This is a visual estimate, not a frame-exact recovered animation. White, cyan, green and outline materials remain steady pending clearer evidence.
### Recognizer spotlight stills
User-supplied `recognizers/spotlight/IMG_3514.jpeg` through `IMG_3520.jpeg` show a crown-mounted white/cyan core and diffuse blue-violet halo. IMG_3520 provides the clearest close-up of the front projector. The first procedural rendering uses these colors and soft boundaries. Sweep timing and search behavior are inferred; no video was available to measure them.
---
Source: /fun/TRON/docs/sounds/
---
title: Sound library
subtitle: 1982 TRON sounds, experiments and future resources
---
Reviewed 2026-09-14. Personal-use resource shelf; retain attribution and source history; candidates are not automatically installed in gameplay.
[Game sound study](../../audio.html) · [Machine-readable catalog](catalog.json)
Start with the Syna-Max Recognizer and derezz recreations. Four public HQ MP3 previews are saved locally; full original downloads use the creator’s Freesound pages. The shelf also retains current game sounds, cleanup experiments, six stock-effect leads, and two complete vendor metadata catalogs. Auditory comparison is pending; file verification is not a claim of film fidelity.
## Recreations
### Recognizer — Syna-Max
**Local HQ MP3 preview; original download requires Freesound login** · Aircraft propulsion
Synthesized recreation of the 1982 Recognizer; creator describes 3xOSC plus flanging. Strong candidate for a clean flight loop.
[Open audio file](previews/syna-max-69757.mp3)
[Source](https://freesound.org/people/Syna-Max/sounds/69757/) · Syna-Max · [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/)
91.95 seconds · 2 channels · 2.55 MB · HQ MP3 preview, not the original master.
### Derezz — Syna-Max
**Local HQ MP3 preview; original download requires Freesound login** · Destruction
Recreated 1982 derezz effects; audition individual events as alternatives to mixed-film explosions.
[Open audio file](previews/syna-max-69966.mp3)
[Source](https://freesound.org/people/Syna-Max/sounds/69966/) · Syna-Max · [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/)
20.61 seconds · 2 channels · 0.24 MB · HQ MP3 preview, not the original master.
### Lightcycle — Syna-Max
**Local HQ MP3 preview; original download requires Freesound login** · Future vehicles
Recreated original-film lightcycle; retained for possible later vehicle experiments.
[Open audio file](previews/syna-max-69764.mp3)
[Source](https://freesound.org/people/Syna-Max/sounds/69764/) · Syna-Max · [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/)
48.82 seconds · 2 channels · 1.03 MB · HQ MP3 preview, not the original master.
### Synth ambience — Syna-Max
**Local HQ MP3 preview; original download requires Freesound login** · Environment / carrier interior
Original futuristic ambience in the same pack; not claimed to be a specific movie stem.
[Open audio file](previews/syna-max-69967.mp3)
[Source](https://freesound.org/people/Syna-Max/sounds/69967/) · Syna-Max · [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/)
27.19 seconds · 2 channels · 0.68 MB · HQ MP3 preview, not the original master.
## Current game sounds
### Tank Drive
**Active** · Vehicle / combat
Video time 10.750s; 0.580s prepared audio. Mixed film soundtrack; may include score or other effects.
[Open audio file](../../audio/tank-drive.wav)
[Source](../references/videos/1982 tron clu scene.mp4) · TRON (1982) film soundtrack; local edit · User-supplied film reference; personal project
### Recognizer Flight
**Active** · Vehicle / combat
Video time 124.000s; 1.880s prepared audio. Mixed film soundtrack; may include score or other effects.
[Open audio file](../../audio/recognizer-flight.wav)
[Source](../references/videos/1982 tron clu scene.m4a) · TRON (1982) film soundtrack; local edit · User-supplied film reference; personal project
### Recognizer Approach
**Active** · Vehicle / combat
Video time 126.200s; 2.080s prepared audio. Mixed film soundtrack; may include score or other effects.
[Open audio file](../../audio/recognizer-approach.wav)
[Source](../references/videos/1982 tron clu scene.m4a) · TRON (1982) film soundtrack; local edit · User-supplied film reference; personal project
### CLU cannon — supplied fire clip
**Active** · CLU cannon fire
User-supplied clue fire.mp3. Used unchanged at its original pitch; decoded during startup and reused for every shot. Stereo, approximately 0.57 seconds. Enemy tanks retain the previous cannon sample.
[Open audio file](../references/sounds/clue%20fire.mp3)
[Source](../references/sounds/clue%20fire.mp3) · User-supplied clip; original sound creator not identified · User-supplied reference for this project; redistribution rights not established
### Cannon
**Active** · Vehicle / combat
Video time 121.380s; 0.380s prepared audio. Mixed film soundtrack; may include score or other effects.
[Open audio file](../../audio/cannon.wav)
[Source](../references/videos/1982 tron clu scene.mp4) · TRON (1982) film soundtrack; local edit · User-supplied film reference; personal project
### Recognizer Explosion
**Active** · Vehicle / combat
Video time 107.840s; 0.850s prepared audio. Mixed film soundtrack; may include score or other effects.
[Open audio file](../../audio/recognizer-explosion.wav)
[Source](../references/videos/1982 tron clu scene.m4a) · TRON (1982) film soundtrack; local edit · User-supplied film reference; personal project
### Terminal Key 1
**Active** · Terminal UI
Video time 0.885s; 0.060s prepared audio. Mixed film soundtrack; may include score or other effects.
[Open audio file](../../audio/terminal-key-1.wav)
[Source](../references/videos/1982 tron clu scene.mp4) · TRON (1982) film soundtrack; local edit · User-supplied film reference; personal project
### Terminal Key 2
**Active** · Terminal UI
Video time 1.395s; 0.060s prepared audio. Mixed film soundtrack; may include score or other effects.
[Open audio file](../../audio/terminal-key-2.wav)
[Source](../references/videos/1982 tron clu scene.mp4) · TRON (1982) film soundtrack; local edit · User-supplied film reference; personal project
### Terminal Key 3
**Active** · Terminal UI
Video time 0.995s; 0.060s prepared audio. Mixed film soundtrack; may include score or other effects.
[Open audio file](../../audio/terminal-key-3.wav)
[Source](../references/videos/1982 tron clu scene.mp4) · TRON (1982) film soundtrack; local edit · User-supplied film reference; personal project
### Terminal Key 4
**Active** · Terminal UI
Video time 1.585s; 0.060s prepared audio. Mixed film soundtrack; may include score or other effects.
[Open audio file](../../audio/terminal-key-4.wav)
[Source](../references/videos/1982 tron clu scene.mp4) · TRON (1982) film soundtrack; local edit · User-supplied film reference; personal project
### Recognizer armor hit
**Active — nonfatal Recognizer hits** · Combat feedback
Original 0.48-second stereo pressure thump, saturated electrical crack and falling electronic tail; no sustained metallic ring. Tank impacts use a shorter 0.36-second variation. Three variants with spatial playback and subtle pitch variation; fatal hits use the explosion instead. Audition WAV is generated by scripts/generate-hit-sfx.mjs from the same synthesis used in game.
[Open audio file](../../audio/recognizer-hit.wav)
[Source](https://github.com/stevedekorte/TRON/blob/main/src/audio/recognizer-hit.js) · Project synthesis · Original project sound
### Tank destruction — layered electronic blast
**Active for CLU and enemy tanks** · Tank destruction
Original 1.65-second low shock, electrical rupture, noise blast and scattered inharmonic debris. Three procedural variants and slight playback-rate variation; stereo world placement and distance attenuation. Audition below uses the same synthesis as gameplay.
[Open audio file](../../audio/tank-explosion.wav)
[Source](../../src/audio/tank-explosion.js) · Project synthesis · Original project sound
### Carrier — steady machinery drone
**Active** · Carrier presence
Synthesized 16-second stereo loop: stable bass harmonics and quiet diffuse machinery texture. Whole-cycle frequencies avoid a crossfade dip; no amplitude modulation. Generated with scripts/generate-carrier-drone.mjs.
[Open audio file](../../audio/carrier-drone.wav)
[Source](undefined) · Project synthesis · Project-generated audio
### Data transfer ring — forming
**Film-derived adaptation; listening review pending** · CLU data transfer surround
Sark and MCP.mp4, 52.75–54.25 s. Filtered mixed soundtrack, stretched to about three seconds with soft fades. Opening cue reverses the extract. Rebuild: scripts/extract-transfer-sfx.py. Residual soundtrack may remain.
[Open audio file](../../audio/data-ring-close.wav)
[Source](../references/videos/Sark%20and%20MCP.mp4) · TRON (1982) film soundtrack / Disney · User-supplied film reference; unofficial personal fan project
### Data transfer ring — opening
**Film-derived adaptation; listening review pending** · CLU data transfer surround
Sark and MCP.mp4, 52.75–54.25 s. Filtered mixed soundtrack, stretched to about three seconds with soft fades. Opening cue reverses the extract. Rebuild: scripts/extract-transfer-sfx.py. Residual soundtrack may remain.
[Open audio file](../../audio/data-ring-open.wav)
[Source](../references/videos/Sark%20and%20MCP.mp4) · TRON (1982) film soundtrack / Disney · User-supplied film reference; unofficial personal fan project
### Light cycle startup
**Archived startup candidate; replaced by materialization cue** · Cycle startup
Extracted from the supplied TRON light cycles.mp4 using scripts/extract-cycle-sfx.py. Picture-selected timing; auditory fidelity review pending. Source times and filters: public/assets/audio/cycle-source.json. Stereo PCM, edge fades; driving loop crossfaded. May retain background soundtrack.
[Open audio file](../../public/assets/audio/cycle-startup.wav)
[Source](../references/videos/TRON%20light%20cycles.mp4) · TRON (1982), Walt Disney Productions · Film reference; no redistribution license supplied
### Light cycle launch
**Film-mix candidate; wired into gameplay** · Cycle launch
Extracted from the supplied TRON light cycles.mp4 using scripts/extract-cycle-sfx.py. Picture-selected timing; auditory fidelity review pending. Source times and filters: public/assets/audio/cycle-source.json. Stereo PCM, edge fades; driving loop crossfaded. May retain background soundtrack.
[Open audio file](../../public/assets/audio/cycle-launch.wav)
[Source](../references/videos/TRON%20light%20cycles.mp4) · TRON (1982), Walt Disney Productions · Film reference; no redistribution license supplied
### Light cycle driving loop
**Film-mix candidate; wired into gameplay** · Cycle driving loop
Extracted from the supplied TRON light cycles.mp4 using scripts/extract-cycle-sfx.py. Picture-selected timing; auditory fidelity review pending. Source times and filters: public/assets/audio/cycle-source.json. Stereo PCM, edge fades; driving loop crossfaded. May retain background soundtrack. User-selected source interval: 0:47–0:48; 0.1-second seam crossfade.
[Open audio file](../../public/assets/audio/cycle-drive.wav)
[Source](../references/videos/TRON%20light%20cycles.mp4) · TRON (1982), Walt Disney Productions · Film reference; no redistribution license supplied
### Light cycle turn
**Film-mix candidate; wired into gameplay** · Cycle turn
Extracted from the supplied TRON light cycles.mp4 using scripts/extract-cycle-sfx.py. Picture-selected timing; auditory fidelity review pending. Source times and filters: public/assets/audio/cycle-source.json. Stereo PCM, edge fades; driving loop crossfaded. May retain background soundtrack. User-selected source interval: 0:51–0:52; 12 ms edge fades.
[Open audio file](../../public/assets/audio/cycle-turn.wav)
[Source](../references/videos/TRON%20light%20cycles.mp4) · TRON (1982), Walt Disney Productions · Film reference; no redistribution license supplied
### Light cycle explosion
**Film-mix candidate; wired into gameplay** · Cycle explosion
Extracted from the supplied TRON light cycles.mp4 using scripts/extract-cycle-sfx.py. Picture-selected timing; auditory fidelity review pending. Source times and filters: public/assets/audio/cycle-source.json. Stereo PCM, edge fades; driving loop crossfaded. May retain background soundtrack. Extended to two seconds (1:15.85–1:17.85), 1.15 seconds longer than the initial extract.
[Open audio file](../../public/assets/audio/cycle-explosion.wav)
[Source](../references/videos/TRON%20light%20cycles.mp4) · TRON (1982), Walt Disney Productions · Film reference; no redistribution license supplied
### Light cycle trail deactivation
**Film-mix candidate; wired into gameplay** · Cycle trail deactivation
Extracted from the supplied TRON light cycles.mp4 using scripts/extract-cycle-sfx.py. Picture-selected timing; auditory fidelity review pending. Source times and filters: public/assets/audio/cycle-source.json. Stereo PCM, edge fades; driving loop crossfaded. May retain background soundtrack.
[Open audio file](../../public/assets/audio/cycle-wall-down.wav)
[Source](../references/videos/TRON%20light%20cycles.mp4) · TRON (1982), Walt Disney Productions · Film reference; no redistribution license supplied
### Light cycle player cabin loop
**Film-mix candidate; wired into gameplay** · Player cycle driving
Restored film waveform after the synthesized drone was rejected as harsh and irritating. Uses 1:36.7–1:37.8 of the user-selected 1:36–1:38 interval, with a 0.2-second equal-power crossfade and stereo-linked RMS leveling. Finished loop: 0.9 seconds. The recording’s pitch variation remains unresolved; volume smoothing does not fix it. Other cycles retain 0:47–0:48.
[Open audio file](../../public/assets/audio/cycle-drive-cabin.wav)
[Source](../references/videos/TRON%20light%20cycles.mp4) · TRON (1982), Walt Disney Productions · Film reference; no redistribution license supplied
### Light cycle materialization
**Film-mix candidate; wired into gameplay** · Fast cycle reveal before launch
Single materialization beat from 26.7–27.4 seconds in the supplied film clip; avoids repeating the full intercut team sequence. One team cue accompanies the 0.3-second reveal and its tail carries into launch. Film-mix candidate; auditory review pending. Reproduce with scripts/extract-cycle-sfx.py.
[Open audio file](../../public/assets/audio/cycle-materialize.wav)
[Source](../references/videos/TRON%20light%20cycles.mp4) · TRON (1982), Walt Disney Productions · Film reference; no redistribution license supplied
## Experiments
### Carrier rumble — archived film candidate
**Reserved for future use** · Unassigned
Carrier and solar sailer video, 116.2–120.2 seconds; filtered and looped to 3.6 seconds. Removed from carrier playback September 17; retained unchanged for future effects.
[Open audio file](../../audio/carrier-rumble.wav)
[Source](../references/videos/carrier%20and%20solar%20sailer.mp4) · TRON (1982) film soundtrack; local edit · User-supplied film reference; personal project
### Explosion cleanup — original
**Audition only** · Destruction
Supplied short edit aligned at 113.207s. Shared stereo tonal suppression; stronger processing also removes explosion texture. Overlapping cannon may remain.
[Open audio file](../../audio/studies/recognizer-explosion-original.wav)
[Source](../references/sounds/Recognizer%20Explosion.m4a) · Film soundtrack; project processing · User-supplied film reference; personal project
### Explosion cleanup — balanced
**Audition only** · Destruction
Supplied short edit aligned at 113.207s. Shared stereo tonal suppression; stronger processing also removes explosion texture. Overlapping cannon may remain.
[Open audio file](../../audio/studies/recognizer-explosion-balanced.wav)
[Source](../references/sounds/Recognizer%20Explosion.m4a) · Film soundtrack; project processing · User-supplied film reference; personal project
### Explosion cleanup — strong
**Audition only** · Destruction
Supplied short edit aligned at 113.207s. Shared stereo tonal suppression; stronger processing also removes explosion texture. Overlapping cannon may remain.
[Open audio file](../../audio/studies/recognizer-explosion-strong.wav)
[Source](../references/sounds/Recognizer%20Explosion.m4a) · Film soundtrack; project processing · User-supplied film reference; personal project
## Stock-library leads
### Heavy Caliber Lazer — synth piano
**Catalog verified; film match unconfirmed; audio not acquired** · Tank cannon
Publisher catalog verifies the effect ID. A community film-effects index associates it with TRON (1982); exact scene/match still needs listening comparison.
[Source](https://portal.sound-ideas.com/Files/SpecificationFile/Cartoon_Trax_Sound_Effects.pdf) · The Hollywood Edge / Sound Ideas · Commercial library; no purchase made
[Community film-match lead](https://soundeffects.fandom.com/wiki/Tron_(1982))
### Heavy Caliber Lazer — echo variant
**Catalog verified; film match unconfirmed; audio not acquired** · Distant cannon
Publisher catalog verifies the effect ID. A community film-effects index associates it with TRON (1982); exact scene/match still needs listening comparison.
[Source](https://portal.sound-ideas.com/Files/SpecificationFile/Cartoon_Trax_Sound_Effects.pdf) · The Hollywood Edge / Sound Ideas · Commercial library; no purchase made
[Community film-match lead](https://soundeffects.fandom.com/wiki/Tron_(1982))
### Heavy Caliber Lazer — scratchy variant
**Catalog verified; film match unconfirmed; audio not acquired** · Cannon impact
Publisher catalog verifies the effect ID. A community film-effects index associates it with TRON (1982); exact scene/match still needs listening comparison.
[Source](https://portal.sound-ideas.com/Files/SpecificationFile/Cartoon_Trax_Sound_Effects.pdf) · The Hollywood Edge / Sound Ideas · Commercial library; no purchase made
[Community film-match lead](https://soundeffects.fandom.com/wiki/Tron_(1982))
### Laser Blast Short
**Catalog verified; film match unconfirmed; audio not acquired** · Weapon accents
Publisher catalog verifies the effect ID. A community film-effects index associates it with TRON (1982); exact scene/match still needs listening comparison.
[Source](https://portal.sound-ideas.com/Files/SpecificationFile/Cartoon_Trax_Sound_Effects.pdf) · The Hollywood Edge / Sound Ideas · Commercial library; no purchase made
[Community film-match lead](https://soundeffects.fandom.com/wiki/Tron_(1982))
### Synth Energy Zaps 1 — tonal delay
**Catalog verified; film match unconfirmed; audio not acquired** · Recognizer power-up
Publisher catalog verifies the effect ID. A community film-effects index associates it with TRON (1982); exact scene/match still needs listening comparison.
[Source](https://portal.sound-ideas.com/Files/SpecificationFile/Premiere_Edition_Vol._1_Sound_Effects_Library.pdf) · The Hollywood Edge / Sound Ideas · Commercial library; no purchase made
[Community film-match lead](https://soundeffects.fandom.com/wiki/Tron_(1982))
### Descending multipitched synth laser
**Catalog verified; film match unconfirmed; audio not acquired** · Derezz / laser
Publisher catalog verifies the effect ID. A community film-effects index associates it with TRON (1982); exact scene/match still needs listening comparison.
[Source](https://portal.sound-ideas.com/Files/SpecificationFile/Premiere_Edition_Vol._1_Sound_Effects_Library.pdf) · The Hollywood Edge / Sound Ideas · Commercial library; no purchase made
[Community film-match lead](https://soundeffects.fandom.com/wiki/Tron_(1982))
## Library resources
### Serafine Sci-Fi I
**Catalog saved; full audio not acquired** · Future ambience, machinery, weapons and transitions
General Frank Serafine collection, not a verified original TRON stems release. Product variants were marked sold out when checked. Local CSV is metadata only.
[Source](https://sound-ideas.com/products/sci-fi-i-sound-effects-library-by-serafine) · Frank Serafine / Sound Ideas · Commercial library; no purchase made
[Local catalog: 420 rows](catalogs/serafine-scifi-i.csv) · [Publisher CSV](https://portal.sound-ideas.com/CSVexport/product=476) · [Publisher audition page](https://soundideas.sourceaudio.com/album/3304227)
### Serafine Sci-Fi II
**Catalog saved; full audio not acquired** · Future ambience, machinery, weapons and transitions
General Frank Serafine collection, not a verified original TRON stems release. Product variants were marked sold out when checked. Local CSV is metadata only.
[Source](https://sound-ideas.com/products/sci-fi-ii-sound-effects-library-by-serafine) · Frank Serafine / Sound Ideas · Commercial library; no purchase made
[Local catalog: 1028 rows](catalogs/serafine-scifi-ii.csv) · [Publisher CSV](https://portal.sound-ideas.com/CSVexport/product=477) · [Publisher audition page](https://soundideas.sourceaudio.com/album/3304306)
## Film-derived candidates
### Carrier derezz — film excerpt
**Extracted; awaiting auditory review** · Carrier materialization/dematerialization candidate
Second half of the original excerpt: 1.5 seconds from 2:38.5–2:40, high-pass 70 Hz / low-pass 9 kHz, normalized to peak 0.7 with 25 ms edge fades. Mixed film soundtrack, not an isolated effects stem. Not loaded by gameplay. Regenerate with scripts/extract-carrier-derez.py.
[Open audio file](../../audio/carrier-derez.wav)
[Source](../references/videos/Carrier%20derezed.mp4) · TRON (1982) film soundtrack; user-supplied reference · Film-derived reference; no standalone redistribution license supplied
### Carrier materialization — loop candidate
**Extracted; awaiting auditory review** · Carrier materialization/dematerialization candidate
1.38-second forward-playing loop with a 120 ms cosine crossfade, prepared from 2:38.5–2:40 (the second half of the original excerpt). Mixed film soundtrack, not an isolated effects stem. Not loaded by gameplay. Regenerate with scripts/extract-carrier-derez.py.
[Open audio file](../../audio/carrier-derez-loop.wav)
[Source](../references/videos/Carrier%20derezed.mp4) · TRON (1982) film soundtrack; user-supplied reference · Film-derived reference; no standalone redistribution license supplied
## Next auditions
1. Compare the Syna-Max Recognizer against the current film-derived flight loop.
2. Pick useful events from the derezz recording and compare with the supplied explosion edit.
3. Check CRT059908–10 and PE205401 against the film before treating them as matches.
4. Keep lightcycle and synth ambience available for later vehicle/environment experiments.
The Serafine collections provide further design material, but no exact Recognizer or tank stem has been verified there. No new library audio is loaded by the game.
---
Source: /fun/TRON/docs/validation/
---
title: Validation
subtitle: Browser checks, performance, and remaining work
---
## Carrier/maze ground-shadow overlap — September 24
`node tests/carrier-maze-overlap.mjs` passed in headless Chrome on macOS. With both shadow layers present, the corrected render matches the reference without coarse floor masking exactly (zero differing channels); restoring the old atlas cutout differs in 34,080 channels. Inspected the low-angle rendered overlap. `node tests/recognizer-shadow-occlusion.mjs` passed wall/debris occlusion checks; its floorless fixture initially exposed an unconditional floor access, now guarded. Production/documentation build passed. Safari at the reported gameplay location remains for user confirmation.
## Sequential stored-shot recharge — September 24
Changed idle recharge to one stored shot every ten seconds, capped at three. Firing resets progress toward the next shot without discarding existing reserves; full reserves do not bank extra recharge time. Seven focused cannon tests passed. `node tests/cannon-reserve.mjs` passed in headless Chrome on macOS, naturally waiting through the full recharge and checking one, two and three illuminated segments at 10, 20 and 30 simulation seconds, plus pause and firing behavior. Production/documentation build passed.
## Stored shots, extended credits and arena signatures — September 24
Seven cannon reserve simulation tests passed with the 1.14-second cooldown and 30-second idle refill. `node tests/cannon-reserve.mjs` passed in headless Chrome on macOS, checking actual key presses, segment counts, accessible meter values, held fire and pause/refill behavior. Its initial run missed startup readiness; the test now waits for the enabled start button.
`node tests/extended-credits.mjs` passed: normal tribute followed by extended pages, repository credit present, bounded 70-column/10-line pages, final signoff, and both split GLBs loaded through Three.js. All 8,349 original triangles are preserved across the two files: 3,465 in the arena and 4,884 in the archived inscriptions. Runtime arena has no signature nodes. Production/documentation build passed. Extended sequence verification advances a detached terminal fixture; full-duration playback remains for user review.
## Hit-centered Recognizer debris — September 24
`node --test tests/debris-inheritance.test.js tests/debris-physics.test.js`: all 13 tests passed, including radial impulses from multiple world-space hit locations on a rotated craft, inherited velocity, Earth gravity and cleanup. `node tests/breakup.mjs` passed in headless Chromium on macOS: fifteen connected sections, varied explosions, tumbling/falling, pause freeze and geometry release after expiry. The impulse is now fully radial vertically as well as horizontally; the new visual distribution remains for user review in play.
## Cycle explosion sequence — September 24
`node --test tests/light-cycles.test.js`: eight tests passed, including exact 24 fps effect sampling, staggered arc visibility, ray cutoff, debris lifetime, deterministic repeated updates and reset/pool reuse. `node tests/light-cycles.mjs` passed in headless Chromium on macOS with no page/console errors. Captured explosion frames 0, 5, 8, 12 and 20 and inspected the opening, expanding arc/starburst, and debris-only stages against the supplied film stills. Preview captures use direct rendering; the game's existing bloom adds glow in play. Screenshot filenames are capture times, not movie timecodes, so the assumed consecutive-frame timing remains an approximation. Production/documentation build passed.
## Grounded cycle trail connection — September 24
The latest film close-up corrected the trail shape: its leading edge has a tire-shaped recess, while its lower edge reaches the floor. Replaced the axle-height taper with a vertically subdivided, closed profile around the measured rear wheel, with 2.5 cm clearance. The top slopes toward the wheel; the bottom remains at arena floor height. `node tests/light-cycles.mjs` passed in headless Chromium on macOS, and the side-on wheel/trail capture was inspected. Seven focused cycle tests and production/documentation build passed.
## Cycle trails, wheel cleanup and self-shadowing — September 24
`node --test tests/light-cycles.test.js`: seven tests passed, including hold/flash/lower timing, releasing only a destroyed cycle's collision cells, opaque trail geometry lowering, and deterministic rounds. `node tests/light-cycles.mjs` passed in headless Chromium on macOS with the cycle shadow atlas active and no console/page errors. Inspected close-up renders for the smooth replacement hub caps, brighter blue, curved rear-wheel trail connection, visible thickness, fixed vertical bands and edges, and directional/self-shadow contrast. Cycles now use movement lanes independent of the floor grid. The floating scoreboard was removed after the visual check; it no longer allocates a canvas, texture or sign mesh.
Production/documentation build and whitespace validation passed. Original source assets are unchanged. Small details elsewhere on the purchased model still retain the source geometry; this is not a complete model rebuild. Safari and live visual preference remain for user review.
## Arena appearance and spectator safety — September 24
On macOS, headless Chromium checks `node tests/light-cycles.mjs` and `node tests/arena-inspection.mjs` passed. Inspected the close-up cycle render: opaque amber/blue trails with pale rear flares, darker blue bike bodies, thicker grid lines at 4.32 m spacing and a dark navy floor. Browser checks cover pause/resume, six rendered racers, model dimensions, matching enemy/Clu tank materials, and suppression of the death terminal during inspection. Safari appearance remains for user review.
The full simulation suite reported 283 passes and one outdated assertion that required a non-draw race after 180 seconds. With the smaller grid, that deterministic encounter draws; the test now verifies completed rounds, winner selection from surviving teams, score accounting and deterministic restart, including draws. All eight focused cycle/inspection tests then passed. Production build passed. Spectator damage tests cover bullets, debris and stomps, with normal damage restored on exit.
## Central labyrinth follow-up — September 23
`npm test`: 276 passed after fixed courtyard beam placement, eight interior tank spawns and six air patrol spawns. Four layout seeds verify counts, interior regions, beam coordinates and clearance. Central tank spawn connectivity now targets the courtyard with a 40,000-node offline budget; using the old outer-maze exit budget rejected valid deep-interior positions.
`node tests/wall-shadow-stability.mjs` passed in headless Chrome on macOS: zero dropped shadow pixels across 81 combinations of wall orientation, world offset and camera position/jitter, compared with the same clipped geometry without depth testing. `node tests/labyrinth.mjs` passed five-maze browser startup and geometry rendering. Safari at the user's reported viewpoint still needs visual confirmation.
## Central labyrinth — September 23
On the development Mac, `npm test` passed all 276 tests. Added checks compare roof triangle area against outlines minus holes, sample collision/occupancy agreement throughout the blueprint, verify an unobstructed central court, and check five-site separation, patrols, five beams and twenty transport pads across seeds 0, 42, 1982 and 99999. A ground route from the southern exterior to the central court succeeds with tank clearance (164 waypoints using 12 m search cells and a larger offline search budget); this establishes connectivity, not live autoplay performance through every passage.
`node tests/labyrinth.mjs` passed in headless Chromium on macOS at 1448×1086: normal game startup includes five beams/twenty pads, with no page errors. Inspected top-down, oblique and full-layout renders in `test-results/labyrinth-top.png`, `labyrinth-3d.png` and `five-mazes.png`. The contours match the supplied reference, roof holes remain open, and the center labyrinth fits without moving the outer mazes. Production/documentation build and `git diff --check` passed. Browser play feel and sustained large-maze AI performance remain for user review.
## Pursuit frame pacing — September 22
`scripts/benchmark-enemy-pursuit.mjs` reproduces twenty simulated seconds with five Recognizers and three maze tanks tracking recorded contact in blueprint/layoutSeed/runSeed 1982. Node CPU profiles on the development Mac identified synchronous route/replan bursts, repeated frontier sorting and redundant distant-pad footprint construction. In the measured profiled runs, worst simulation tick fell from 100.5 ms to 19.9 ms; the largest later replan spike fell from 33.3 ms to 8.8 ms. Total simulation CPU time fell from about 507 to 409 ms. These are one-machine simulation measurements, not guaranteed browser frame rates; budgeted work deliberately spreads smaller costs across more ticks.
`npm test` passed all 244 tests. New checks cover stable search priority/tie order, bounded and fair incremental route work, identical completed synchronous/incremental routes, cancellation, changed destinations, and independent tactical planning budgets. Existing maze radio pursuit, wall-side attacks, hidden-information invariants, teleport bounds and coordinated stomps also pass.
Chrome 153.0.8010.53 on macOS, 1280×720, local tactics without provider calls: twenty-second rendered checks with eight and then 25 active units returned median frame intervals of 16.7 ms and 95th-percentile intervals of 16.8 ms, with no page errors. Units tracked a recorded blueprint contact; these checks measure that encounter, not every viewpoint or live-provider workload. The rendered capture was inspected. Production/documentation build passed; generated HTML was produced by colvmn from Markdown, not edited manually.
## Maze tank pursuit connectivity — September 22
Reproduced on blueprint/layoutSeed/runSeed 1982: two of three maze-zero tanks had no route from their original spawn pockets to a recorded contact and remained stationary. Spawn exit validation and the expanded pursuit fallback allow all three replacement patrols to move; a 20-second local simulation measured approximately 36, 75 and 100 m displacement.
`node --test tests/ground-tanks.test.js` passed 21 tests. New checks verify swept exit routes for every blueprint patrol, more than 10 m of movement after an actual Recognizer radio report in Classic/local/JEV modes, retained report age and no direct sight of hidden Clu, and collision-safe partial progress from an unreachable pocket. `npm run test:browser -- --refactor` passed in Chrome 153.0.8010.53 on macOS: blueprint startup, snapshot isolation, pause, camera modes and three resets with stable 97 geometries, 28 textures and one audio context. No provider calls were required.
## Recognizer moving turns — September 22
The new navigation regression exercises 90° and 180° turns in Classic, local tactical, JEV-selected and escort controllers. Every fixture maintains more than 7 m/s throughout three seconds, travels more than 25 m and changes yaw by more than 0.7 radians without wall overlap. Tactical tests also retain wall-side stomp clearance and multi-location searching; the two-aircraft encounter completes a coordinated stomp. Straight waypoint lookahead and actual-pose attack clearance prevent the faster curved approaches from regressing arrival behavior.
`node tests/recognizer-momentum.mjs` passed in headless Chrome on macOS, including movement during yaw reversal, bounded angular/lift acceleration, pause, reset and tuning. The fixture explicitly selects Classic to avoid provider calls. The spotlight fixture now places a new contact ahead of the aircraft's changed heading; the older five-pursuer fixture now seeds its run and explicitly limits the roster to the five craft named by the check. Visual flight feel remains subject to play review.
Final `npm test`: 238 passed. Production/documentation build passed. `node tests/tactical-browser.mjs` passed mode switching, actual wall-side descent/crush, mocked unavailable-provider fallback, pause/reset and Classic isolation. The first attempts used an unused port and then mismatched the legacy authored wall fixture with the default blueprint world; the fixture now explicitly selects the authored maze and reproducible seeds on port 5173.
## Aircraft shadows blocked by maze geometry — September 22
Headless Chrome 153.0.8010.53 on macOS: `node tests/recognizer-shadow-occlusion.mjs` reproduced aircraft shadow leakage onto the far wall and covered floor, then verified the maze-depth mask. Exposed-roof shadow pixels remained 8,694; far-wall shadow pixels fell from 15,312 to zero and covered-floor pixels from 1,069 to zero. Detached-part wall leakage was zero. The separate projected ground silhouette fell from 941 leaked pixels to zero. The resulting fixture capture was inspected. These measurements cover the controlled geometry, not every possible viewing angle.
`node tests/recognizer-shadows.mjs` passed roof/wall coverage for intact aircraft and debris plus game shader compilation. Its debris capture now occurs at 0.2 seconds, before the blast can randomly scatter pieces beyond the small receivers. `node tests/materialization.mjs` passed wire/solid phases, two reinforcements, atlas growth, pause and reset. `npm run test:browser -- --refactor` passed combined game shaders and three restarts with stable counts of 97 geometries, 28 textures and one audio context. `npm test` passed all 237 tests. No provider calls were needed for these checks.
## Production gravity, aerial wheel and coordinated attacks — September 22
Final combined validation: `npm test` passed all 237 tests; production/documentation build and Worker dry-run packaging passed. Browser checks below used headless Chrome on macOS.
The previous free-fall test explicitly disabled damping and missed the production difference: at four seconds, vertical speed was 31.15 m/s rather than the undamped 39.24 m/s. Production now applies damping only to horizontal velocity. The revised test uses actual production settings, checks one through four seconds of velocity/displacement, and bounds the rendered interpolation lag. Sleeping-body position comparison allows floating-point epsilon.
`node tests/aerial-transition.mjs` passed in Chrome on macOS: aerial transition/heading, wheel zoom while running, wheel zoom while paused with frozen simulation, and return to the follow camera. Classic mode prevents provider calls. The bug was an obsolete `view.aerial` guard after camera ownership moved to `view.cameraRig`.
Additional simulation checks verify scenario-specific radio boundaries including vertical distance, a stable lead after relative distances cross, forbidden supporter strikes, destruction/blocked/expired-lease handoff, and a two-Recognizer tactical encounter where exactly one aircraft commits and completes a stomp. Existing opening pursuit and close-pursuer anti-spinning checks also pass. `node tests/breakup.mjs` passed rendered 15-block breakup, falling, pause and geometry cleanup in Chrome with Classic mode (no provider calls).
## Carrier escort and debris regression — September 22
On macOS/Node 22.22.0, `npm test` passed all 232 tests. `tests/escort-debris.test.js` exercises a 240-second blueprint crossing in JEV mode: both ground escorts stay clear of walls, take perimeter detours and return within 70 m of their formation slots; both airborne escorts return within 90 m. It also checks fresh versus stale target memory, visibility filtering, detached JEV debris snapshots, physics observation removal, settled/crossing/falling trajectories and turbo-aware stopping.
A real Rapier/session comparison reproduces lethal collisions against settled and falling debris with ordinary forward input. Applying the avoidance guard preserves full health in both fixtures. This establishes those reproducible cases, not invulnerability to tumbling debris or unavoidable close impacts.
`node tests/autoplay.mjs` passed in headless Chrome with mocked JEV responses: accepted decisions, movement, pause, manual overrides, background continuation and failure disengagement; no page errors or paid requests. The new simulation tests verify geometry/behavior rather than rendered escort aesthetics. Longer live encounters and avoidance feel remain subject to play review.
## Ownership cleanup, browser fetch, gravity and panels — September 22
Environment: macOS, Node 22.22.0, headless Chrome 153.0.8010.53 on this workstation. No new physical-device benchmark or Safari listening review was performed.
- `npm test`: all 224 tests passed after integrating the ownership/fetch, Earth-gravity and faster-panel changes. Timing assertions now use the configured transfer duration.
- `npm run test:browser -- --refactor`: passed seeded blueprint startup, detached debug state, pause, follow/aerial/gunner capture and three resets. Counts remained 97 geometries, 28 textures and one AudioContext. These counts do not prove complete leak freedom or visual fidelity.
- `node tests/autoplay.mjs`: passed mocked accepted decisions, movement, temporary manual override, pause/background lifecycle and service-failure disengagement, without paid calls. Native fetch/timer receiver regressions encountered during extraction were corrected and covered by a receiver contract test.
- `node tests/beam-camera.mjs`: passed again after faster panels: half-speed camera rise/orbit/return, pause and unchanged turret. Camera returns before opening.
- `node tests/teleporters.mjs`: passed partial entry, moving transfer, exit lock, pause and aircraft shaders; horizon/portal captures saved in ignored `test-results/`.
- `node tests/recognizer-audio.mjs`: passed offline sample metadata, stereo energy, hit/explosion routing and source cleanup.
- `node tests/maze-music.mjs`: passed exploration/approach/visibility/entry/afterglow/pursuit transitions and quiet fade, after updating the fixture to call the extracted music director.
- `node tests/debris-collision.mjs`: passed at the current 5173 dev server with 69 pieces, minimum visible bottom within floating-point epsilon of zero, 10 sleeping pieces after 4.5 seconds and zero remaining bodies after cleanup. The rendered capture was inspected.
- `npm run build`: passed production and generated documentation build.
- `npm run worker:check`: passed dry-run packaging; no deployment.
The enemy-free replay (`node scripts/replay-autoplay.mjs`, blueprint/layoutSeed/runSeed 1982) captured three beams at approximately 110, 335 and 665 seconds, then held until the 20-second stall cutoff at 706 seconds. This predates the panel speed change. It preserves the known later navigation defect; it is not a full-game success or a live-JEV quality test.
The gravity check measures one-second free-fall velocity and displacement of different-sized bodies at 9.81 m/s² with damping disabled for that measurement. Production keeps its existing mild damping, blast impulse and vehicle momentum. Recognizer parts previously used 14.7 m/s². Panel timing is now 0.5 s build + 11 s hold + 3.5/3 s retract; the faster sweep shares the same collision and display function.
Remaining human checks: Safari audio unlock/listening, driving feel and side-by-side visual comparison. Chrome regression results do not substitute for those checks. No new deployment was performed.
## Beam hold, autoplay objective and pad audio — September 21
Node checks passed: 30 across autoplay, maze sites, local/public Jev contracts; then 27 across teleporter, maze-site and autoplay checks after adding sound events. They verify the eleven-second hold, unchanged sweeps, sixteen-second completion, explicit player objective/progress, and exactly two spatial events per completed transfer with none during partial entry. `node tests/teleport-audio.mjs` passed offline Web Audio rendering in headless Chrome on macOS: departure/arrival/player cues produced finite, non-clipping samples; mute/reset rendered silence; all one-shot sources were released. The generated listening preview is `test-results/teleport-preview.wav`. Artistic sound balance still awaits the user's listening review.
## Autoplay cruise and turret continuity — September 21
`node --test tests/autoplay.test.js tests/jev.test.js` passed all 15 checks. Added regressions keep speed above 19 m/s throughout a clear 20-second cruise after acceleration, preserve speed through collinear route points while still stopping within the data-beam radius, and bring a turret initially pointing 90 degrees sideways back within 0.03 radians of travel direction. `node tests/autoplay.mjs` passed in headless Chrome with mocked Jev selections: samples over several route handoffs stayed above 19 m/s, turret direction recovered, pause and W/Shift+W takeover still worked, and no JavaScript errors occurred. No paid provider calls were made for this correction. These are clear-route checks; complex combat maneuver changes still intentionally slow/turn the tank and remain subject to play review.
## Spatial teleporters and Clu autoplay — September 21
`npm test` passed all 185 tests. On macOS/Node 22.22 with headless Chrome, `node tests/teleporters.mjs` passed driven partial entry, stationary wireframe clipping, pause, instantaneous moving transfer, whole exit/rearming and aircraft shader checks. Captures `teleporter-partial.png`, `teleporter-wire-arrival.png` and `teleporter-solid-exit.png` were inspected. The first arrival capture exposed interpolation across the world jump; preserving post-transfer interpolation history fixed it and the repeat check passed. Full-wireframe arrival no longer casts the solid tank floor shadow. Reinforcement `node tests/materialization.mjs` passed its rendered wire/solid phases, growing render/audio/shadow pools and reset after updating its stale fixture to Classic mode and the current 20-second reinforcement interval. Its initial old-mode run timed out; that timeout was not a successful regression check.
`node tests/autoplay.mjs` passed U/button switching, an accepted mocked player Jev response, actual movement, pause, and W/Shift+W takeover without JavaScript errors or paid browser requests. Node tests cover off-by-default behavior, real movement, line-of-sight information isolation, stale/disabled/pre-teleport rejection and shared serial player/enemy scheduling. One live player snapshot through the local Vite relay returned m0, confidence 0.64, accepted, in 456 ms (1,974-byte snapshot). This establishes the protocol integration, not strong autonomous play. Longer combat/maze exploration quality and wall/aircraft shadow clipping across all approach angles remain human review items. `npm run build` and the Worker dry-run succeeded. No public deployment of these features was performed in this change.
## HUD service warnings — September 21
`node --test tests/jev.test.js` passed all seven checks, including warning persistence through pause/reset and clearing after a successful but low-confidence answer. `node tests/system-warnings.mjs` passed in headless Chrome on macOS against local Vite: mocked outage shown in the HUD, retained while paused, Classic mode labeled JEV OFF, and successful service recovery hidden. No paid requests or JavaScript errors. Capture: `test-results/system-warning.png`.
## Public Cloudflare Jev relay — September 21
On macOS with Node 22.22 and headless Chrome, `npm test` passed 180 tests, including the new SQLite budget and public-client checks. `npm run worker:check` bundled successfully. A local `wrangler dev` instance with a dummy key and zero daily budget returned 429/Retry-After through the actual SQLite Durable Object without contacting Jev. The deployed Worker returned configured=true and a valid live choice. `node tests/public-relay.mjs --local` loaded the production build at the website origin, completed a browser CORS request and received m2 with confidence 0.72, with no asset/JavaScript errors. The initial run also produced a pacing 429; public clients now wait at least 1,200 ms between request starts against the Worker’s 750 ms minimum. The same opt-in smoke test without `--local` checks the live website and makes paid provider calls. Built files were scanned for the actual key with no matches. Quotas bound request/input volume, not exact dollar spend; long public load testing and cost tuning remain outstanding.
## Blueprint alternative — September 12
The optional `?maze=blueprint` layout hand-traces 20 roof islands from Maze Blue Print.png. A top-down reference preset and source/trace SVG overlay make the geometry reviewable. The existing authored maze remains the default.
- `npm test`: 16 checks pass. The three new checks verify triangulated area equals polygon area for all islands, concave notches and above-roof sight behavior, occupancy/ray agreement across thousands of sample points, a clear spawn/entry route, and clear AI search samples.
- `node tests/reference.mjs`: passes the original five comparison views, four documentation cards, the blueprint top-down/overlay render, and an eight-second drive into the traced maze. The tank enters at the cropped lower boundary, reaches a real wall without penetrating it, and all five aircraft remain present. No browser errors after correcting an integer-valued GLSL denominator in the new identity-axis layout.
- Visually inspected blueprint-overlay.png and blueprint-aerial.png against the supplied image. Coordinates follow roof boundaries rather than the image's small side shadows. No unseen wall continuation is implied.
- `npm run build`: succeeds and regenerates the trace overlay and five documentation pages. Blueprint geometry is bundled as local data, with no runtime need for the source image.
Scale (0.8 m/pixel), wall height (54 m), cropped-boundary closure and cosmetic ledge depths remain stated reconstruction choices. These checks do not establish an exact original-film layout.
## Renamed references and wall details — September 12
Updated the reference studio and documentation to the user's descriptive image filenames, including Maze Details.png. Added a fifth fixed camera for the new wall reference. Sparse blue-gray seams, tall panel outlines and shallow beveled ledges now follow exposed wall faces. These details are cosmetic; passage geometry and collision volumes are unchanged.
Validation: `npm run build` passed. `node tests/reference.mjs` passed all five rendered comparisons, verified reference images loaded, and checked the four documentation cards and destination pages. Inspected test-results/reference/details.png for the actual wall detailing. No browser errors.
## Fixed reference iteration and visibility — September 12 evening
Added a local reference studio at /reference.html with fixed front/rear tank, overhead maze and ground-level maze cameras, film images alongside the live renderer, and a reference overlay. Captured and visually inspected successive material, camera, floor and shadow passes.
The disappearing maze was a contrast regression: the walls and floor still rendered but became nearly black at driving height. Restored blue floor/wall separation. A view-height/grazing-angle wash keeps the ground readable during driving while preserving dark channels in overhead views. Maze geometry keeps the longer parallel branches and pointed diagonal ends the user preferred; no further topology changes were made after that feedback.
Tank red outlines are subdued and non-emissive. Blue highlights, dark lower side panels, gold upper shoulders, reduced insignia bloom and projected hull/turret shadows replace the earlier luminous trim and soft contact-only shadow. Imported geometry remains intact; its canopy and hull contours still differ from the film. The overview is an interpretation, not an exact traced map.
- `npm test`: all 13 checks pass. Wall fixtures now target a straight boundary section instead of a newly clipped corner; movement vectors derive from the oblique grid transform.
- `node tests/reference.mjs`: four reference views render without browser errors; all four documentation cards are present and their destinations return HTTP 200. Captures are in test-results/reference.
- `npm run test:browser`: Chrome 152.0.7977.84 passes actual maze entry/branch driving, level turret, shooting, visibility/memory, pause, aerial/survey and ten resets. 1440×900 sample: 44 geometries, 14 textures, 57 calls, median and 95th-percentile frame intervals about 16.7 ms. Inspected the final driving capture for visible walls and flooring.
- `npm run build`: succeeds; colvmn generates five documentation pages.
- Sandboxed Chrome launches aborted on this host and produced macOS crash dialogs. Subsequent launches explicitly used the required approved execution mode. One screenshot attempt timed out; the subsequent capture completed. This is not evidence that every host or browser is verified.
## Imported vehicles — September 12
Replaced both procedural vehicles with user-supplied GLBs: arabinowitz's tank and Shriker1's Recognizer. Inspected the actual rendered models together and the tank in the maze.
- `npm test`: all thirteen simulation checks pass after the muzzle coordinates changed.
- `node tests/models.mjs`: visible muzzle agrees with the simulated origin across nine hull/turret combinations (maximum error 3.7e-15 meters); Recognizer height is 15 meters, width 18.38 meters, and hit materials are independent per aircraft. Screenshot: test-results/imported-models.png.
- `npm run test:browser`: Chrome 152 passes controls, shooting/destruction, five agents, entry/branch driving, pause, map/aerial, ten resets and resize with no page/console errors. Sampled 1440×900 frame intervals: median 16.7 ms, 95th percentile 16.8 ms. Final renderer sample: 43 geometries, 14 textures, 52 draw calls.
- `npm run test:browser -- --lifecycle`: passes blur/input cleanup, mute, reduced motion/quality, context loss, unsupported WebGL, failed GLB loading/reload message, and production preview driving with no development hook.
- Production build includes both local GLBs; no external model host is contacted.
- Earlier 180-second traversal measurements below predate the imported vehicles. Safari remains unverified.
## Current simulation revision — September 12
The latest user direction replaces the timed chase with an open-ended simulation. Earlier route/capture/win evidence above is historical and does not describe current behavior.
The current maze is connected and branching, with loops, dead ends and four openings. Its topology is transformed onto oblique axes, and exposed slab tips are clipped diagonally. Shared convex prisms drive the roof/wall mesh, hull collision, projectile blocking, sight occlusion and camera clearance. This follows the supplied overhead frame's forms; the hidden layout is authored, not verified as the exact film maze.
Five Recognizers begin outside the maze and persist independently. They use a 370 m view range, approximately 148° horizontal field of view and 5 Hz sight sampling. Each stores its own observed position/velocity, time and source. Radio reports are delayed 0.45 seconds, range-limited to 560 m, and retain their original timestamp through relays. Prediction is capped at five seconds and stops at mapped walls; knowledge expires after 38 seconds. Navigation receives no live Clu state. Aircraft clear the roof physically and separate from one another; sound uses five spatial voices with wall muffling.
Recognizers are uniformly half the old scale: upright legs about 9 m (30 ft), total height about 15 m and shoulder width 17.5 m. Hit volumes and flight clearance use the same scale. This implements the user's estimate, not a canonical film measurement.
Checks completed so far:
- `npm test`: thirteen checks pass for connectivity/branches/cycles/dead ends, collision/sliding and pivot turns, roof and wall visibility, field of view, occlusion freezing memory, delayed radio copying/expiry, identical observer behavior under different unseen Clu locations, bounded prediction, level/manual gun azimuth with height assistance, wall-blocked shots, persistent agents and clean resets, fixed-step consistency, physically open diagonal cuts, and physical aircraft clearance during converging pursuit.
- `npm run test:browser`: Chrome 152.0.7977.84 passes five aircraft/audio sources, Q/E rotation and level barrel, actual entry and side-branch driving, pause freezing the aircraft, aerial/survey views, sighting/report memory, real projectile destruction, ten resets and resize. No page/console errors. At 1440×900, final sample: 243 uploaded geometries, 13 textures, 78 draw calls; approximately 16.7 ms median / 16.8 ms 95th-percentile frame intervals.
- Inspected actual browser screenshots of the entry, diagonal slabs and survey. A generated floor-shader constant initially used an integer where GLSL required a float; corrected and the full browser check rerun. The gun fixture now changes the live development settings through the existing module instance, avoiding a duplicate Vite import; projectile outcomes still use normal simulation collision.
The old structured-chase long-run report predates this revision. Current sustained traversal and production/lifecycle results are recorded below when completed. Human judgment of atmosphere, exact film matching and sound remains separate from these technical checks. External model acquisition was subsequently completed; see the imported-vehicles checks above.
The 180-second 1920×1080 traversal passed: 24 waypoints and approximately 1,299 meters through actual branches, no slab penetration, no page/console errors, and 242 uploaded geometries / 13 textures from the 60-second sample onward. Sampled frame intervals after warmup were about 16.7 ms median and 16.7–16.8 ms at the 95th percentile. Agents independently entered pursue/investigate states during the traversal. This run preceded the final aircraft-clearance adjustment; the resulting crowding seen from above was addressed with stronger avoidance and a 24-meter separation envelope. A focused 20-second convergence test passes after that adjustment.
`npm run test:browser -- --lifecycle` passed for blur/input cleanup, mute, lower quality, reduced camera motion, context-loss recovery, unsupported WebGL handling and production driving without development hooks. The production bundle and four colvmn pages build successfully; JavaScript is approximately 161 kB gzipped, with Vite's standard chunk-size advisory. Chrome is the tested browser; Safari/Firefox and subjective sound quality remain unverified for this revision.
The final browser smoke rerun also passed the converging-aircraft clearance scenario, real shooting and all previous control/reset checks, with zero page/console errors and approximately 16.7 ms median/95th-percentile frame intervals at 1440×900.
The final terminal check passed incremental typing, Enter skip/start, reduced-motion immediate reveal and the narrow-window layout. The narrow terminal screenshot was visually inspected.
## Historical validation reports
These entries describe earlier builds and are retained as a development record.
Date: 2026-09-12. This report distinguishes verified technical behavior from pending human playtesting and visual fidelity work.
## Environment
- MacBook Pro, Apple M2 Max, 96 GB RAM; macOS 26.6.2.
- Node 22.22.0; Three.js 0.186.0; Vite 8.3.0; Playwright 1.63.0.
- Chrome 152.0.7977.84, browser automation with actual WebGL rendering. Screenshots were opened and visually inspected.
- Reported GPU: `ANGLE (Apple, ANGLE Metal Renderer: Apple M2 Max, Unspecified Version)`.
- Standard smoke viewport: 1440×900, render scale 1, pixel ratio 1. Long-run viewport: 1920×1080.
## Passed checks
`npm test`: six meaningful simulation tests pass:
- Acceleration, braking/reverse, high-speed wall containment, and glancing wall movement.
- Matching simulation displacement under 30, 60, and 144 Hz render scheduling.
- Capture warning delay, exposure accumulation/decay, loss, and terminal-state priority.
- Real projectile hits and three-hit Recognizer destruction.
- A full authored route using steering/throttle/fire: 74.85 simulated seconds, three Recognizers destroyed, maximum centerline error about 1.29 meters.
- Independent clean state on restart.
`npm run build`: production bundle builds successfully. Vite reports the main JavaScript chunk is larger than its default 500 kB warning threshold; gzip is approximately 158 kB. This is primarily the Three.js renderer and addons, not remotely loaded game data.
`npm run test:browser`: Chrome checks pass with no page/console errors:
- Start and actual keyboard-driven movement.
- Pause/resume and restart.
- Cannon destruction using Space; scenario placement establishes a target, but actual projectiles and collision determine the result.
- Capture/loss and crossing the exit/win, both through real simulation rules.
- Ten restarts with one audio context and clean projectiles/kill count.
- Resize to 960×640.
The initial browser run exposed a GLSL reserved-word error in the capture-zone shader; that was corrected and the complete smoke check rerun successfully. Full-route simulation also exposed late enemy overtaking; approach timing and velocity-leading cannon aim were adjusted, and the route test now defeats all three encounters.
## Long-run / additional-browser checks
A ten-minute 1080p Chrome test drove eight complete winning runs using actual keyboard input, each taking 74.65–74.68 seconds and destroying all three Recognizers. No page/console errors occurred. After the first complete route, uploaded GPU resource counts stayed at 140 geometries and 13 textures through subsequent runs. Frame intervals after warmup were about 16.7 ms median and 16.8 ms at the 95th percentile.
The original harness ended with an assertion failure because its comparison baseline was taken at 30 seconds, before all canyon sections had appeared and uploaded their geometry. This was a test-baseline error: the log showed 131 geometries at 30 seconds, then 140 at 60 seconds and the same 140 for the rest of the run. The harness now compares after a complete route and saves its raw report before assertions. The targeted three-minute rerun passed with the corrected gate and the final pause-audio fix: two more winning runs, three kills each, stable 140 geometries / 13 textures, and no page/console errors. The raw report is saved in `test-results/soak.json`.
`npm run test:browser -- --lifecycle` passed: blur pauses and clears throttle, mute works, reduced-motion and low-quality modes render, graphics-context loss and unsupported WebGL 2 produce useful messages, and the production preview drives without development hooks. Local references in the built HTML and generated documentation also resolve.
Firefox/WebKit are not validated. The initial attempt using older cached builds stalled and was stopped. A bounded check using the current Playwright package's own paths reported that its matching Firefox 1543 and WebKit 2359 executables are not installed. The compatibility script now has a watchdog and accepts explicit browser-path overrides. Safari itself was not automated.
## Visual and sound review
Inspected screenshots show a clear chase view, solid faceted canyon walls, muted blue/purple surfaces, a rounded-pod tank, and a large solid Recognizer with warm edge details. The initial overly glossy tank highlights were reduced and antialiasing added to the postprocessing target. The vehicles remain procedural stand-ins; fuller film matching is still needed.
Audio graphs initialize from the Start gesture, synthesize all sounds locally, and maintain a single context over restarts. Positional sources follow the Recognizer and the listener follows the camera. Subjective loudness, engine weight, and the resemblance of the rotor/drone require human listening feedback; automated interaction does not establish sound quality.
## Remaining work and limits
- Human playtesting: tank feel, corner readability, camera motion, cannon aiming, capture fairness, and sound balance.
- Actual Safari testing. A Playwright WebKit result, if available, is reported separately and does not count as Safari validation.
- Final film-accurate assets. The 13 supplied frames now cover multiple angles; exact video timestamps remain unrecorded.
- No imported GLB asset has been exercised; the adapter exists for a later replacement.
- No mobile controls, backend, multiplayer, progression, or deployment.
- Performance claims apply to the recorded Mac/browser/quality setting; they do not establish low-end hardware performance.
Reproducible screenshots and raw reports are in the ignored `test-results/` directory. See the README for commands.
## September 12 — supplied-frame art and terminal pass
All 13 user-supplied PNGs were inspected. Rebuilt the tank deck, shoulders, canopy and cannon; rebuilt the Recognizer roof, body depth, end pods and leg panels; replaced jagged cliffs with flat-topped maze slabs, subtle wall panels and grid courts. Simplified Recognizer hit volumes were adjusted to the lower crown and deeper body. The route and vehicle dynamics are unchanged.
The home screen now uses local VT323 lettering, the two lines from the terminal still, timed character reveal with a line pause, a blinking cursor while typing, and a faint perspective grid. Enter skips typing and subsequently starts the game; reduced-motion preferences show all text immediately. Start instructions become visible after the sequence.
Verified on Chrome 152.0.7977.84:
- `npm test`: all six simulation tests pass after the hit-volume adjustment; complete route remains 74.85 simulated seconds with three kills.
- `npm run test:browser`: driving, pause/resume, shooting, loss, win, ten restarts and resize pass with no page/console errors. The smoke run recorded 153 uploaded geometries, 13 textures, 95 draw calls at its final sample and approximately 16.7 ms median/95th-percentile frame intervals at 1440×900.
- `npm run test:browser -- --terminal`: progressive partial text, hidden start controls during typing, Enter completion without accidental starting, a second Enter starting, reduced-motion immediate reveal and 640-pixel-wide layout pass.
- `npm run build`: production assets and all four colvmn documentation pages build successfully. Main JavaScript is approximately 161 kB gzipped; Vite's default chunk-size advisory remains.
The rendered terminal, chase and Recognizer screenshots were visually inspected. These establish a second reviewable interpretation, not final film fidelity. Sound is unchanged in this pass.
A fresh `npm run test:browser -- --soak --duration=180` passed at 1920×1080 with the final geometry and revised hit volumes: two completed wins (74.68 and 74.65 seconds), three kills each, no page/console errors, stable 152 uploaded geometries / 13 textures after warmup, and approximately 16.7 ms median and 95th-percentile frame intervals. Chrome required an approved launch outside the filesystem sandbox. The current raw report is `test-results/soak.json`; earlier figures above describe the first build.
## September 12 — manual turret and angular maze correction
Q/E now turns the turret independently of driving, including while stationary. Its angle is an offset relative to the hull and resets on restart. The rendered barrel remains level. Shot azimuth follows the manual turret bearing exactly; height assistance and vertical lead apply only to a horizontally aligned Recognizer. Unlocked shots stay level. Projectile origin follows the offset turret and muzzle.
The route now interpolates linearly between nodes. Wall buffers split at every route corner and use flat vertical faces, so neither the road nor the walls round the bends. Widths still taper linearly, and the layout remains an authored adaptation rather than the film's exact map.
`npm test`: all nine tests pass, including stationary turret rotation/reset, manual shot azimuth with vertical-only assistance, and straight spans/hard corners. Full-route simulation completes in 74.00 seconds with three kills and maximum centerline error 3.09 meters. A new assertion initially distinguished JavaScript negative zero from zero; it was corrected to compare heading magnitude.
`npm run test:browser`: Chrome 152 passes Q/E keyboard rotation without hull movement, level barrel with an elevated target, turret restart, movement, pause/resume, cannon destruction, loss, win, ten restarts and resize, with no page/console errors. Final smoke sample: 164 uploaded geometries, 13 textures, 101 draw calls; 1440×900 frame intervals approximately 16.7 ms median / 16.8 ms 95th percentile. Turret and encounter screenshots were visually inspected. The earlier 1080p soak predates this route/control correction and was not repeated for this update.
The asset inventory now includes four inspected internet model previews and metadata. No external model has been imported; Sketchfab's official source-download endpoint requires authentication.
## Default blueprint game entry
The normal game URL now selects the blueprint maze. `?maze=authored` retains the original; the reference studio keeps its original fixed-camera studies unless explicitly switched. `npm test` passed all 16 tests and `npm run build` succeeded. The Chrome reference check (`node tests/reference.mjs`) verified the plain `/` URL selects the blueprint, driving into its entrance and wall collision, five active Recognizers, and the explicit original-layout URL. Reference captures and documentation cards also passed with no browser errors.
## Recognizer crush attack — September 13
Read all four supplied attack frames. Added articulated leg closure and a sight-triggered, committed vertical strike, followed by recovery. `npm test` passed 19 tests, including autonomous approach/crush, escape after commitment, blocked descent, stale sight rejection, recovery and reset. `node tests/models.mjs` verified the unchanged 15 m model height and material independence and captured open, turning and closed poses. Inspected the rendered closed pose and in-game impact. `node tests/reference.mjs --attack` passed in installed headless Chrome with no browser errors: crush, escape, disabled wreck and R reset. `npm run build` succeeded; its existing bundle-size advisory remains.
Timing is inferred from four untimed stills. The imported wedge-shaped feet are simpler than the film's block soles. Descent clearance and weapon volumes remain conservative approximations rather than triangle-level rigid-body collision.
## Forward-thrust Recognizer flight — September 13
Replaced direct velocity targeting and lateral avoidance forces with bounded forward thrust, isotropic drag/braking, and heading-based avoidance. Momentum survives yaw changes; pitch and roll are zero. Crush preparation requires a slow approach and preserves decaying residual drift. `npm test` passed all 22 checks, including forward acceleration, turn momentum, avoidance direction, and crush approach/drift. Flight constants (meters, seconds, radians) live in src/simulation/flight.js. Physical overlap correction remains a collision constraint, independent of propulsion.
`node tests/reference.mjs --attack` also passed in Chrome: crush, escape, wreck controls and reset, with no browser errors. Production build succeeded.
## Attack approach damping
Increased passive drag from 0.45 to 0.7 per second and added braking drag from 1.15 to 2.4. Increased available forward acceleration to preserve cruising capability. When inside a visible target’s clear strike footprint, the aircraft holds its heading and brakes instead of turning after its overshoot. All 23 simulation tests passed, including a regression for settling into an attack without yaw changes. This tuning pass was checked through simulation tests; visual feel remains for user review.
## Close-range steering and aerial view corrections
Reproduced five aircraft spinning around a stationary target: 1.70–1.94 accumulated turns per craft over 20 simulated seconds. Close-range yielding and alignment reduced this to 0.45–0.77 turns in the same scenario, with a successful crush. Hover stabilization now works even where a roof blocks descent. All 25 simulation tests passed, including the crowded approach and blocked descent regressions. Chrome attack checks passed for the five-craft approach, crush, escape, reset and Space Paranoids page title with no browser errors.
V aerial view previously used the ground-camera grid cutoff (650 m), below much of its viewing distance, and reduced floor brightness to 16%. It now has its own floor visibility, grid fade at 1800–3000 m, and fog multiplier 0.06 (previously 0.35). The floor geometry remains at ground level. Ground cameras and reference-studio lighting retain their existing settings.
Aerial grid follow-up: reduced V-view line blend from 0.95 to 0.22 to soften the overly bright white lattice. Floor visibility and aerial fog remain as corrected; ground-view grid styling is unchanged. Production build passed.
Further aerial-grid adjustment from user review: reduced line blend from 0.22 to 0.045 for a faint lattice. Floor brightness is unchanged.
## Terminal-to-grid opening
Added a 5.5-second opening: terminal fade, grid reveal, camera descent to Clu. Simulation time remains frozen during it, and audio unlock still comes from the start gesture. Escape pauses/resumes the sequence; Enter or Space skips it. Reduced-motion mode begins gameplay immediately. R resets directly into gameplay.
`npm run test:browser -- --terminal` passed typing, Enter start, frozen simulation during camera movement, pause/resume, reduced-motion bypass and narrow viewport checks. Inspected fade and grid-reveal captures. Production build passed.
## Nearby Recognizer camera framing
Added a smooth upward tilt for nearby visible aircraft in the forward camera sector. A pullback of up to 32 m and up to 8 additional degrees of vertical FOV allow an overhead craft and the tank to share the frame. Pitch is capped against the tank top after camera collision adjustment. Aerial, opening and studio cameras retain their own framing. `node tests/reference.mjs --camera` passed projected tank-top and Recognizer-crown visibility at heights 77, 30 and 11 m with no browser errors; inspected captures. Build passed. The first test attempt was interrupted by a development reload during the concurrent build; the sequential rerun passed. Walls can limit the available pullback, so full-aircraft framing is not guaranteed in confined passages.
## Film-derived stereo audio
Four stereo samples extracted from the local video and decoded successfully in Chrome. Original-channel correlations are 0.33 (tank), 0.75 (Recognizer flight), 0.46 (approach), 0.96 (cannon), confirming preserved channel differences. Peak amplitude is 0.72; none clip. The stereo HRTF test measured left-source RMS L/R 0.380/0.280 and right-source RMS 0.279/0.379; moving the emitter farther reduced right RMS to 0.040, while wall filtering/attenuation reduced it to 0.155. Approaching/receding Doppler ratios were 1.045/0.958.
`node tests/reference.mjs --audio` passed left/right direction, distance, wall muffling, Doppler, four sample decodes, audition start/stop, pause, stable loop count (11 sources) across firing/resets, one AudioContext, and an individual missing-sample fallback. An initial test omitted the wall gain multiplier; corrected the fixture to exercise both filtering and attenuation. A development reload interrupted the next attempt; the stable rerun passed. These are signal and lifecycle checks, not a listening judgment. Auditory review of background soundtrack and loop quality remains pending on the audition page.
The final production build and `npm run test:browser -- --lifecycle` passed: mute, blur/pause, quality/reduced-motion settings, error paths and production driving.
Tank dialogue correction: replaced the reported spoken section with a short opening texture, retaining stereo and a smooth loop crossfade. Verified 0.58 s duration, two channels and peak 0.72. Hash checks confirm both Recognizer loops and cannon are untouched. Versioned the tank URL to avoid stale browser audio.
## Tank-loop tone and post-crush patrol
Reduced the tank loop’s end-of-loop 330 Hz component by 15.7 dB after normalization, using a −18 dB/Q=4 notch and a 650 Hz low-pass. Other sample SHA-256 hashes remain unchanged. Tank sample URL is revision 3. Listening review remains with the user.
Confirmed destruction now invalidates target memory, aborts redundant strikes into recovery and propagates through existing delayed/range-limited radio. Old sightings cannot restore pursuit. All 26 simulation tests passed, including return to wander after recovery, stale-message rejection and reset. The close-approach turning regression now stops measurement at the crush; subsequent legitimate patrol turns are no longer counted as attack circling.
Opening text polish: a CSS block cursor follows the typed text, stays visible during typing, then blinks after completion and fades with the opening transition. Removed maze-selection links from the terminal screen.
Home-page simplification: hide all terminal actions, controls, explanatory copy and links; only access text and cursor remain. Return completes typing or starts the opening once typing is complete. Browser scenarios now enter using Return.
## September 13 — opening, controls, distance and sound revision
- `npm test`: 29 passed, including immediate reverse after a blocked wall approach, tangential sliding, and 1,000-foot three-dimensional radio range.
- `npm run build`: passed; existing large-bundle advisory remains.
- Chrome via `npm run test:browser -- --terminal`: passed typing/start, no pre-start grid, live Recognizer motion, full-speed tank startup, camera position relative to the moving tank during descent, W release, paused simulation, reduced motion and narrow layout.
- Chrome lifecycle checks: passed minimal standby, held-W resume, consumed fire/reset/view keys, focus loss, failure states and production driving.
- Chrome camera checks: passed turret orbit at ±90°/180° and keeping tank/overhead Recognizer in frame.
- Chrome audio checks: passed stereo direction, distance attenuation, wall filtering, Doppler, sample decode, restart resource stability and missing-file fallback.
- Inspected `test-results/opening-clu.png`: distant maze silhouette visible from the exterior approach.
- Audio listener now follows camera position (superseding the earlier tank-anchored listener). Tank uses a stereo emitter with an 18 m reference distance. Terminal audio initializes before typing when browser autoplay policy permits; a user gesture is required when policy blocks playback. Cannon/terminal timbre still needs human listening approval.
Opening-pursuit follow-up: terminal browser check passed with exactly three initial pursuers; all three project inside the opening camera frame. Inspected `test-results/opening-fade.png` and confirmed the staggered formation is visible behind moving Clu. Unit tests remain 29/29 and production build passes.
Music/terminal follow-up: production build and Chrome audio regression passed, including music playback, pause/resume and restarting from the beginning. Typing-audio browser check passed: clicking the ready screen resumes audio and replays all 59 non-newline character tones, then Return starts normally. Click gain increased from 0.035 to 0.16 of master volume and duration from 18 to 35 ms. Browser autoplay permission still determines whether the first unprompted typing is audible.
## Formation, lead and destruction checks
- `npm test`: 33 passed. New checks cover a twenty-second advancing formation with preserved lateral spacing, speed coordination, 3D intercept solutions, and an actual simulated projectile hitting a crossing Recognizer.
- `npm run test:browser -- --terminal`: all three more distant pursuers remain inside the opening frame.
- `npm run test:browser -- --breakup`: passed model-fragment variation across two destructions, paused debris, falling pieces and GPU geometry cleanup after expiry. Inspected early and falling screenshots. Timing assertions follow effect age rather than wall-clock capture duration.
- Production build passed, with the existing bundle-size advisory. Fragments use surface partitions, not closed volumetric fracture meshes; wall/floor response is approximate and fragments are visual, not damaging simulation actors.
Latest checks: 34 unit tests passed, including dynamic lead/support reassignment and dissolution, shortest-path turret centering and manual interruption. Chrome opening-controls checks passed keyboard/mouse fire, driving and turret operation during zoom, and held-input continuity. Typing-audio check decoded four film-derived samples and replayed all character clicks. Lifecycle check passed F centering, pause/resume and production controls. Audio check measured nonzero output during the opening with zero shots fired. Keyboard tail revision verified all four PCM clips end at zero with final-10-ms RMS below 15% of their first-30-ms RMS. Human listening review remains necessary for timbre.
Smaller terminal/CRT revision: build and terminal browser checks passed. Inspected the 1440×900 capture: smaller text remains readable under the scanlines. Opening fade, reduced-motion entry and narrow viewport checks also passed.
Turbo and predictive-stomp revision: 37 unit tests pass, including full ten-second boost, one-minute recharge, braking, reset/crush state, predicted fold/drop timing, misses after changing course, and autonomous moving-target interceptions from three initial separations. Chrome terminal checks confirm all five initial pursuers remain in frame; inspected the rear-view capture after restoring roof-clear flight altitude. Chrome turbo/hints checks passed T activation, pause/expiry, recharge lockout and ready indicator, plus centered single-row hints after input inactivity. Production build passes.
Aerial zoom: build and Chrome aerial-zoom checks passed. Wheel scrolling reaches the 150–2,400 m height limits smoothly, works paused, preserves zoom across V toggles and ignores scrolling in driving view. Inspected the far overview capture; maze and floor render without clipping.
## September 13 — carrier and localized breakup
- `node scripts/prepare-carrier.mjs`: converted the DAE, confirmed all seven textures loaded, round-tripped GLB in Chrome and captured `test-results/carrier-preview.png`. Inspected the rendered silhouette and materials.
- `npm run test:browser -- --carrier`: passed in headless installed Chrome, outside the sandbox. Measured constant +X transit at 24 m/s, constant altitude/depth and frozen position during standby. Six direct surface hits (body and each leg, open and folded) selected the expected part, preserved two intact sections, and conserved every source triangle. No page errors. Inspected `test-results/carrier-game.png`.
- `npm run test:browser -- --breakup`: passed randomized breakup, falling, pause and geometry cleanup. Updated the fixture to cruising altitude: the earlier low-altitude fixture inherited climb velocity and invalidated its four-second falling expectation.
- `npm test`: all 37 simulation/blueprint checks passed.
- `npm run build`: passed; existing large JavaScript chunk advisory remains. Carrier adds 6.86 MB to packaged assets. No interactive human performance/fidelity sign-off is implied.
## BEGIN button and carrier visibility
`npm run test:browser -- --typing-audio` passed with Chrome user-activation autoplay policy: no terminal or grid before BEGIN, four decoded typing samples and 59 character clicks after activation, no restart from an incidental click, and Return starts the opening. `--terminal` passed typing, skip/start, opening, pause/resume, reduced motion and narrow viewport. `--carrier` passed transit/pause and section integrity checks. Inspected `test-results/begin-button.png` and `test-results/carrier-game.png`; the complete carrier hull is visible against the dark sky. Build passed with the existing chunk-size advisory.
Carrier contrast correction: restored source hull colors and textures, reduced blue emission, and adjusted roughness for directional shading. Applied a small depth offset to the original illuminated trim so coplanar hull surfaces do not hide it. Inspected the revised in-game capture; `npm run test:browser -- --carrier` passed.
Carrier red/blue trim pass: `npm run test:browser -- --carrier` passed. Inspected `test-results/carrier-game.png`: subdued red outline with visible smaller blue panel boundaries. Production build passed.
## Roaming map patrols
`npm test`: 38 checks passed, including separated/reproducible unalerted patrols, vision cutoff with altitude, wall occlusion and information-limited search. `npm run test:browser -- --patrols`: nine aircraft and sound voices initialized; four extra patrols moved autonomously and froze during pause, without page errors. Production build passed.
Carrier presence / section refinement: `npm run test:browser -- --carrier` passed audio sample decode (no load errors), animated beacon changes, straight transit/pause and triangle conservation with five unhit sections retained whole. `--breakup` passed randomized motion, falling, pause and geometry cleanup. Inspected final-shot contact sheets and updated in-game screenshot showing the more distant/left carrier and brighter maze seams. Build passed. Audio timbre and precise film blink phase are not claimed verified.
Clu breakup: all 38 simulation tests passed. `npm run test:browser -- --tank-breakup` passed actual autonomous stomp, fragment creation, intact tank/shadow hiding, paused debris, expiry and restart restoration. Inspected `test-results/clu-breakup.png`, including the new blue fracture boundaries. Build passed. The browser fixture was corrected to wait for BEGIN audio initialization before pressing Return.
Static terminal: `npm run test:browser -- --typing-audio` passed immediate full text, absence of BEGIN, zero typing clicks and a single Return starting the opening with audio unlocked. Build passed.
### Recognizer searchlights — September 13
`npm run test:browser -- --searchlights` passed in headless Google Chrome on macOS. A controlled search encounter produced a visible beam, inspected in `test-results/recognizer-searchlight.png`. Tests check active/inactive aircraft, frozen pause intensity, disabled attack/pursuit poses, and geometric wall/floor ray clipping; no browser or shader errors were reported. This does not establish film-exact motion. The camera-facing ribbon approximates a volume; surface illumination and matching a future video remain open visual refinements.
### Ground escorts and shared radio — September 14
The existing 38 simulation tests and eight ground-tank tests pass. New coverage includes carrier pace, sight range/facing/occlusion, bidirectional ground/air radio with unchanged sighting age and range, independently aimed rounds destroying Clu, Clu destroying enemies, independence from hidden Clu movement, swept route clearance around a slab, and ground formation leader replacement.
`npm run test:browser -- --ground-tanks` passed in headless Google Chrome on macOS. Checks cover eight units, turret movement with a level barrel, enemy fire, Clu and enemy tank fragmentation, pause and reset, with no browser/renderer errors. Rendered captures inspected: `test-results/ground-escort.png`, `test-results/enemy-tank-firing.png`, and `test-results/enemy-tank-breakup.png`. Build passed. Large-scale traffic through every blueprint passage and enemy sound balance remain human playtest items.
### Startup presentation — September 14
The HTML supplies a black background immediately and loads the stylesheet through a render-blocking link. Content is hidden until styles apply, avoiding an unformatted text flash. Controls appear at game entry and remain until ten simulated seconds after the first gameplay key press; the Return used to start does not count. Later keyboard activity re-enables the idle reminder. Pausing freezes the countdown, and resetting starts it over. `npm run test:browser -- --startup-ui` passed with deliberately delayed CSS, initial controls visibility, timed fade and subsequent idle reminder checks.
### September 14 follow-up checks
Simulation coverage now includes stabilized gunner yaw, manual shot elevation, pitch limits, zoom-sensitive aiming, alert decay/renewal and cross-unit reports, and reverse turbo direction/caps/easing. Browser checks `--gunner`, `--cruise`, and `--searchlights` passed in Chrome. `test-results/gunner-sight.png` was visually compared with the supplied reticle image. Controls/startup checks also cover delayed CSS and initial hint timing.
The final simulation suite passes all 52 tests. Gunner controls use J/L in both views, I/K for elevation, and O for zoom; Q/E rotation is disabled. Zoom sensitivity is tested against the field-of-view ratio.
Gunner inertia follow-up: all 53 simulation tests pass, including acceleration, release/deceleration, direction reversal and clearing motion on mode exit. The gunner Chrome check passed with settled aiming before stabilization/projection assertions.
`npm run test:browser -- --death-terminal` passed: a real fatal projectile triggers breakup, partial opacity during the fade, the exact two-line message with hidden game/HUD/cursor, and Return into a fresh run. The terminal capture is `test-results/death-terminal.png`.
Final sizing/weapon checks: all 55 simulation tests passed, plus the strengthened scale-dependent separation check. `node tests/models.mjs` verified Recognizer height 19.5 m, feet at ground height zero, correct muzzle transforms, and folded poses without errors. The imported-model capture was inspected. F leveling and the five-second Clu projectile lifetime are covered by focused tests; the gunner Chrome check passed after those updates.
Recognizer breakup pacing: `npm run test:browser -- --breakup` passed in headless Chrome on macOS. Verified one fragmented section and five intact detached sections, randomized outcomes, falling debris, pause freezing, and geometry cleanup. Inspected the early and four-second falling captures; debris has reached the floor by the latter capture.
Stronger blast follow-up: the Chrome `--breakup` check passed again; inspected the falling-debris capture for broader scattering. Anatomical fragmentation, pause and resource cleanup checks remain passing.
Fourth gunner zoom: all six focused gunner simulation tests and Chrome `--gunner` passed, covering reduced yaw/pitch sensitivity at all four levels and O cycling through the 8× label back to 1×.
Film effects/audio: `node tests/recognizer-audio.mjs` passed in headless Chrome on macOS: stereo WAV decoding, 1.88-second loop / one-second explosion, audible rendered output, left/right positional energy and one-shot source cleanup. The first attempt was interrupted by a development-server reload; the rerun passed. Direct listening remains for user review. `npm run test:browser -- --breakup` passed after adding optical effects and staged section release, including single-section fragmentation, pause and geometry cleanup. Inspected `recognizer-breakup-early.png` against extracted film frames: double yellow rings and sparks render around the separating sections in gunner view.
Muzzle/audio correction: `node tests/muzzle-flash.mjs` passed in Chrome: chase/gunner rendering, paused timing, actual firing and expiry, without console errors. Inspected both muzzle captures against the supplied still and extracted 1:51 frames. `node tests/recognizer-audio.mjs` passed for the final M4A-derived samples: 0.85-second stereo explosion, audible offline rendering, positional balance and cleanup. Direct auditory confirmation remains with the user. PCM comparison verified the M4A/video alignment exactly after 2112 audio frames.
Gunner flash correction: `node tests/muzzle-flash.mjs` passed in Chrome with hidden gunner flash, visible external flash, pause freezing and expiry. Cleanup candidates were generated with finite, unclipped stereo PCM and matching durations; waveform alignment was verified against the full scene. Audio quality/source isolation needs user listening review.
Sound library: `node tests/sound-library.mjs` passed in headless Chrome on macOS. Checks cover unique catalog IDs, existence of local media/catalogs, downloaded-preview SHA-256 hashes, documentation card navigation, 17 rendered players, all four downloaded previews loading valid durations, and successful playback. Inspected the library screenshot. Listening/film-match judgments remain for audition; commercial catalogs contain metadata only.
Recognizer armor audio: all 56 simulation tests pass, including target/fatality event tagging. The extended `node tests/recognizer-audio.mjs` check passed for audible spatial impact output, source cleanup and suppression on fatal hits. The audio harness disables Vite HMR to prevent dependency-optimization reloads interrupting offline rendering. Subjective sound review remains available through the audition page.
## Production subdirectory deployment — September 14
`npm run build` and `node tests/production-path.mjs` passed against a static production build mounted at `/fun/TRON/`: game starts, all three vehicle models and game audio load, and the sound study plays without HTTP or JavaScript errors. Vite uses a relative base and runtime audio resolves below the game path. The website workflow builds the pinned private submodule with a read-only deploy key and publishes its dist output.
## Tank speed and impact feedback — September 14
All 57 simulation tests passed, including enemy pursuit reaching normal Clu speed without inheriting turbo and escorts keeping pace with the carrier. `node tests/impact-shake.mjs` passed in Chrome: impact changes the rendered view, freezes on pause, leaves turret aim unchanged, and respects reduced motion. Collision feedback preserves a previous stronger hit, ignores low-speed wall pressure, and decays over approximately 0.4 seconds. Production build passed.
## Forward-and-level turret return — September 14
All 59 simulation tests and the gunner browser suite passed. F centers yaw and levels elevation, using the same movement rates and gunner acceleration as manual controls at all four zoom levels. Tests cover exact final alignment, short-path centering, turning hulls and manual override. Production build passed.
## Cannon shot reserve — September 14
All 63 simulation tests and `node tests/cannon-reserve.mjs` passed. Coverage includes one-per-second recharge capped at three, four rapid shots from a full bank, normal cooldown after exhaustion, held-fire behavior, refill after resting, pause, reset and destruction. Real Space presses consumed reserves in Chrome; pausing prevented recharge. Production build passed.
### Cannon reserve timing update
Reserve recharge now requires ten uninterrupted seconds without firing and restores all three extras at once. Simulation tests cover the exact boundary, no partial refill, partially spent reserves, timer restart on firing, and destruction. All 63 simulation tests passed; production build passed.
## Patrol mix and spotlight confirmation — September 15
All 67 simulation tests passed: roster, ground patrol movement/clearance, delayed spotlight confirmation and radio, bounded angular motion, beam range and wall occlusion, failed acquisition, hidden-target behavior, tracking and fade. `node tests/spotlight-acquisition.mjs` passed in Chrome on the default blueprint: three aircraft/audio voices and eleven ground tank models, three moving maze patrols, acquisition before pursuit, visible tracking, paused beam state, fade and continued pursuit. Reviewed `test-results/spotlight-lock.png` showing the beam on Clu. Corrected a blueprint spawn that passed hull clearance but failed swept route clearance. Production build passed.
### Spotlight reach and scanning fix — September 15
All 69 simulation tests passed. Added a 500 m target case verifying elevation above the scanning pitch, actual rendered beam reach and successful confirmation, plus bounded scanning after abrupt goal changes. Chrome acquisition/tracking/fade check passed at 500 m and the rendered capture was reviewed with the beam on Clu. Scanning still uses 260 m; target acquisition uses the sensor range so an observer cannot freeze forever trying to illuminate a visible tank beyond the old beam limit. Production build passed.
## Aerial transition and close patrol encounter — September 16
All 71 simulation tests passed. Chrome checks `tests/aerial-transition.mjs`, `tests/aerial-zoom.mjs`, and `tests/maze-tank-awareness.mjs` passed against an isolated local Vite server: smooth V transitions both directions, turret-aligned aerial view, wheel zoom including pause, and an actual blueprint patrol detecting Clu behind it and landing a shot. Unit tests cover close rear detection and reaction to a nonfatal shot from beyond near-awareness range. Production build passed.
## Patrol routing and radio response — September 16
Reproduced a blueprint patrol stopping permanently near (138, -252) after its steering entered the pathfinding clearance margin. All 73 tests now pass, including a two-minute blueprint patrol run checking movement every 20 seconds and clearance every step. A radio-only test verifies a Recognizer can send an occluded target report to a nearby ground patrol, trigger a route around the wall and move the tank without granting direct sight or fire permission. The close-range blueprint encounter also passed in Chrome after the navigation change. Production build passed.
## Turret stabilization motor limit — September 16
All 77 simulation tests passed. Coverage includes all Clu views and zoom, manual aim plus hull compensation within a single relative-to-hull motor speed budget, recovery after saturation, idle enemy turret world-heading hold, and abrupt externally imposed yaw changes including angle wrap and zero dt. The gunner Chrome suite passed for aiming, stabilization, zoom, F centering, firing, pause and exit. World-space motion caused by the hull itself is distinct from the turret motor: if the hull outruns compensation, aim drifts instead of exceeding motor speed. Production build passed.
### Mouse gunner verification — September 16
`npm test`: 81 passing. Includes target convergence and rate limits at all zoom levels, forward lock/manual release, minimum elevation, nearest visible surface raycast, occlusion, hidden-object exclusion and empty-sky fallback. Chrome headless on isolated port 5174: revised `tests/mouse-aim.mjs` verifies pointer capture, independent view/gun lag and convergence, firing, forward lock, mouse unlock and pause/view lifecycle. `tests/gunner.mjs` covers keyboard controls. Mouse feel remains for human playtesting.
### Visibility, turbo and spotlight checks — September 16
82 simulation tests pass, including sustained distant spotlight tracking, close-range fading and lost-observation behavior. Chrome checks on isolated Vite port 5174 passed: mouse aiming with the complete sight, `tank-visibility.mjs` material isolation plus live turbo pulse/return to normal, and `spotlight-acquisition.mjs` sustained tracking, pause and close-range fade. Reviewed low-angle enemy and turbo-trim renders under representative lighting; in-game brightness remains subject to user review.
### Smooth mouse tracking — September 16
83 simulation tests pass. Added measured angular velocity/acceleration checks at all four zoom levels with sudden target changes and reversals on a stationary hull, including arrival at the target. Chrome `tests/mouse-aim.mjs` passed with interpolated full-sight rendering, target convergence, firing, F lock and pointer lifecycle. Human smoothness assessment remains necessary; numerical limits alone do not establish feel.
Death music fade: Chrome `tests/death-terminal.mjs` verifies decreasing music gain while playback continues, zero gain and paused playback at the terminal, and normal gain restored on restart. Audio gain was measured; no subjective listening claim.
End-screen track: Chrome death-terminal check passed for continued gameplay fade, Only Solutions playback/time advancement on the terminal, and restoration of We've Got Company on restart.
Opening clip update: Chrome death-terminal check verifies restart selects “1 recognized 1.mp3” with looping disabled; terminal playback remains Only Solutions.
Recognition music: 83 simulation tests pass, including no cue before spotlight confirmation and one cue through continued tracking. Chrome `tests/recognition-music.mjs` passed: an actual new sighting selects a different recognition clip and starts playback after the one-shot opener.
### Action music selection and fades — September 16
86 unit/simulation tests pass. Filename-category tests cover additions, case matching, missing categories, repeat avoidance, pursuit state and close-range hysteresis. Browser `recognition-music.mjs` passed opening recognition → pursued → proximity-triggered gotcha, including measured fade-out before the source changes and fade-in afterward. Updated death-terminal regression passed end music and fresh-run restoration. Glob discovery is build-time for the static production site; future local files require publishing a new build to appear there.
Quiet music transition: 86 unit/simulation tests pass. Chrome recognition-music check verifies a 3-second fade after all pursuers are removed, audible gain and continuing playback after 1 second, then zero gain and paused playback with no unwanted restart. Build passed.
Gunner transitions: Chrome `tests/gunner-transition.mjs` passed entry/exit position and FOV interpolation, measured intermediate quaternion rotation, sight fading, mid-transition reversal and reduced-motion bypass. Existing `tests/gunner.mjs` controls regression also passed.
End cursor: Chrome death-terminal check verifies visible cursor and `terminal-blink` animation on the end screen, alongside music and restart behavior.
Gunner exit correction: Chrome transition check samples height and quaternion-derived pitch through exit and verifies no reversals beyond 0.01 total excess travel in the stationary fixture. Entry, exit, rapid reversal and reduced-motion checks pass. Aerial transition regression also passed.
## September 16 — carrier escorts, patrol variation and engine loop
- `npm test`: 88 tests passed, including carrier air escort following/return after stale contact, seed reproduction, varying patrol positions, and two-minute blueprint patrol traversal.
- `TRON_URL=http://127.0.0.1:5174 node tests/spotlight-acquisition.mjs`: Chrome passed; five Recognizers and five enemy tanks render, all three maze ground patrols move, aircraft audio sources exist, and spotlight acquisition/tracking/fade works without page errors.
- Engine-loop inspection found a louder boundary region (40 ms mono RMS approximately 0.21–0.24 versus 0.12–0.18 in the interior). A dedicated rebuild takes the steadier interior, gently levels its shared stereo envelope and crossfades the boundary. New stereo WAV is 0.28 seconds, RMS 0.160, peak 0.613. Subjective listening approval remains with the user; no claim of an auditory check.
### Engine transient correction
User listening found the shorter 0.28-second version repeated the clink faster. That version is superseded. The replacement averages the original engine sample’s frequency spectrum and reconstructs a 5.944-second periodic signal with randomized phases, discarding source impact timing rather than repeating a trimmed waveform. RMS is 0.140, peak 0.657; the wraparound sample step is 0.00435 versus maximum ordinary step 0.02104. This is a spectrum-derived sound, not an isolated film stem; listening approval remains outstanding.
### Tank destruction sound
`node tests/tank-explosion.mjs` passed: three synthesis variants at 44.1/48 kHz remain finite and below 0.841 peak, fade to silence, and contain stereo differences. Chrome OfflineAudioContext renders the actual Sound.effect path for both tank subjects; near RMS approximately 0.115 versus 0.030 at 180 m, and all sources release after playback. `npm run build` passed with the existing chunk-size advisory. Subjective listening/film fidelity remains for user review. `scripts/generate-tank-explosion.mjs` builds the library audition from the gameplay synthesis.
## September 17 — gunner zoom
`node tests/gunner-zoom.mjs` passed in Chrome: intermediate FOVs for all four zoom changes including wrap, exact final FOVs, enlarged top-center label bounds, and immediate reduced-motion changes. Screenshot `test-results/gunner-zoom.png` reviewed. `node tests/gunner-transition.mjs` passed for entry/exit, reversals and reduced motion. Production build passed with the existing chunk-size advisory.
The gunner zoom browser check additionally passed wheel zoom through every level in both directions, including clamping at 1×/8×. Wheel zoom uses the same eased field-of-view transition.
### Mouse gunner stability
A stationary-tank mouse test reproduced camera translation during turret catch-up before the fix. `tests/mouse-aim.mjs` now verifies fixed camera position and stable mouse-view orientation throughout catch-up, along with convergence, firing, F lock and pointer-lock lifecycle; Chrome passed after anchoring the camera at the turret pivot. All 88 simulation tests passed, including mouse motor limits.
### Turret servo audio
`node tests/turret-servo.mjs` passed: yaw/elevation motion drives sound, render-only frames retain the last motor measurement, hull-only rotation does not trigger it, and stop/pause/destruction/reset silence the tone. Chrome offline rendering measured moving RMS 0.0182 and silent conditions at zero. Build passed with the existing chunk advisory. The user confirmed playback; subsequent tuning lowers gain from 0.065 to 0.032 and pitch from 95–195 Hz to 55–110 Hz.
### Hull health and regeneration
All 89 simulation tests passed, including five-minute recharge rate/cap, reset to full, and no regeneration after destruction. `node tests/health-meter.mjs` passed in Chrome: fractional health matches elapsed simulation time, pause freezes it, HUD visibility in gunner view, and a fatal projectile leaves zero health. Production build passed with the existing chunk-size advisory. Health HUD screenshot: `test-results/health-meter.png`.
### Compact status bars
Chrome `tests/health-meter.mjs` passed the readiness checkmark, removal of visible health percentage, normal/orange/red/critical thresholds and critical pulse/reduced-motion override, alongside recharge/pause/death checks. Build passed with the existing chunk advisory.
### Enemy hit-part tracking
`npm test`: all 90 tests passed. Projectile integration tests cover every Recognizer zone at two yaw angles in open/folded poses, all four tank zones at two yaw angles, event identifiers, last-hit coordinates/time, per-part counts and fatal-event classification. No rendered geometry precision is claimed; tank armor subdivisions are approximate within existing collision volumes.
### Critical hits and component damage
All 92 simulation tests passed: one-shot crown kills, half-damage limb hits, repeated-leg stomp disable, aborting a committed drop, track mobility loss, and an immobilized tank retaining firing ability. The wall-only movement fixture now excludes randomized enemies so it tests wall geometry independently. The long-range projectile fixture now expects its crown hit to be fatal. Chrome `tests/part-damage.mjs` passed live crown/leg damage checks with no rendering errors; the browser also measured imported turret bounds used for classification. Build passed with the existing chunk-size advisory.
### Enemy firing cadence and spread
All 94 simulation tests passed. New checks cover varied/reproducible shot spread, horizontal/vertical angular bounds, unchanged projectile speed, 3.4–4.2-second cooldown samples, and actual shot spacing during a 20-second simulated encounter. The ground encounter fixture now supplies an explicit seed. Build passed with the existing chunk-size advisory.
## Edge smoothing and gunner targeting — September 17
- `npm test`: 96 tests passed, including moving-target interception, misses, destroyed targets, airborne critical regions, range and wall blocking.
- `node tests/antialiasing.mjs`: isolated headless Chrome passed pixel-density, 4-sample MSAA, SMAA, resize and render-scale checks without browser errors.
- `node tests/gunner-targeting.mjs`: isolated headless Chrome passed outline visibility, hit/miss cue changes, Recognizer crown critical indication and cleanup on leaving gunner mode. The airborne fixture is paused to prevent autonomous altitude changes from moving its crown away from the test ray.
- Inspected the exterior anti-aliasing capture and gunner tank-outline capture. The tank silhouette is readable with a thin cyan edge; predicted-hit feedback moves with the complete crosshair.
These checks do not measure frame rate across hardware or guarantee hits against accelerating targets. Supersampling and the outline pass add GPU work.
## Compact gunner feedback — September 17
Replaced targeting text and whole-crosshair color changes with a 220 ms transition of the central hash mark to half size and yellow glow. Misses restore its size and green color. Reduced-motion preferences disable the transition. The existing gunner browser check passes for tank and Recognizer hits, restoration on misses and exiting gunner mode; inspected the resulting screenshot to confirm the center stays aligned and the surrounding crosshairs stay green.
## Intact Recognizer explosions — September 17
`node tests/breakup.mjs` passes in isolated headless Chrome: exactly six distinct intact sections, randomized motion between explosions, falling, pause freezing and geometry cleanup after expiry. The resource comparison warms up a complete explosion before measuring the next cycle, avoiding unrelated lazy scene allocations. Inspected the early explosion capture with the camera transition settled: original block silhouettes remain recognizable around the flash/rings.
## Recognizer component correction — September 17
The six positional regions still crossed authored blocks. The loader now tags connected geometry across both source materials before rigging; explosions preserve those tags. `node tests/breakup.mjs` passes with 15 unique, unfragmented blocks, repeated random motion, falling, pause behavior and resource cleanup. The early rendered capture was inspected. `node tests/gunner-targeting.mjs` also passes after adding the synchronized 90-degree center-mark rotation.
## Four maze sites, collection and dynamic patrols — September 17
- `npm test`: 100 tests passed. Added transformed geometry/occlusion, per-site patrol ownership, beam clearance, swept one-shot collection/reset and exploration-memory tests. The blueprint patrol regression simulates all twelve ground patrols for two minutes; it measures distance traveled so intentional loops are not mistaken for stalls.
- `node tests/data-beams.mjs`: isolated headless Chrome passes all four beam sites, collection, independent fades, reset and error checks. Inspected the near beam, distant maze and shutdown-ring captures. A negative-angle shader interpolation error found during testing was fixed.
- `node tests/breakup.mjs`: passes after increasing blast impulse, preserving all 15 connected blocks, randomized motion, falling, pause and resource cleanup.
- The new hit samples are original synthesized sounds. Subjective impact quality and intermittent shadow stability still benefit from normal play on the user's display; automated checks cannot establish those.
`node tests/armor-hit.mjs` passes: bounded finite stereo samples at 44.1/48 kHz, separate tank/Recognizer durations, successful offline playback for all three hit subjects, distance attenuation and source cleanup. The offline audio check runs without the game renderer to avoid contention. `npm run build` passes with the existing large-bundle warning.
## Carrier searchlights — September 17
`npm test`: 103/103 passing, including smooth acquisition before radio, both enemy types receiving reports within half a maze width radius, outside-range exclusion, occlusion, destruction and initial detection footprint. `node tests/carrier-search.mjs`: passed in isolated headless Chrome at 1280×800 against Vite on port 5174; both beams render and acquire CLU, disappear after destruction, and produce no browser errors. Captured and inspected `test-results/carrier-search.png`. Subjective brightness and tracking feel remain available for in-game review.
## Stationary data transfer and carrier drone — September 17
Inspected a nine-frame contact sheet from 52.8–54.6 seconds of the supplied `Sark and MCP.mp4`. Added a sequential vertical light curtain around CLU with build/hold/reverse timing. `npm test`: 103/103 passed, including stationary acquisition, interruption by motion/death, delayed one-time collection and reset. `node tests/data-beams.mjs`: all four sites transferred and faded independently, then reset without browser errors; inspected `test-results/data-transfer.png` and reduced excessive glow.
`node scripts/generate-carrier-drone.mjs`: generated a 16-second stereo loop, peak 0.65, quarter-second RMS max/min ratio 1.007 and loop seam 0.00073. Chrome confirmed `carrier-drone` decoded, old `carrier-rumble` was not loaded, and no sample errors occurred. This verifies loading and numerical continuity, not subjective listening approval. Original rumble file remains untouched. `npm run build` passed.
## Carrier shuttle studio — September 17
Inspected all four supplied stills and contact sheets covering the 2:34–2:57 escape sequence. Rendered reference, side, top, rear and underside views; adjusted the channel width, flared ends, reference camera and lighting after inspecting captures. `node tests/shuttle-preview.mjs` passed in isolated headless Chrome: 552 triangles, 88,756-byte binary GLB, successful Three.js reimport, bounds approximately 14.22 × 9.17 × 20.19 m including trim, no browser errors. Geometry is an approximate reconstruction; scale and hidden structure remain inferred. The closing terminal now appends `END OF LINE` on its own line.
Shuttle revision after user comparison: replaced the solid pontoons with thin trays, standing end blades and a single side wall. Added an enlarged detachment crop to the studio and a corresponding camera preset. Model proportions are now provisionally 20 m wide × 14 m long × 9 m tall; final fidelity still requires reference review.
Final revised export check: `node tests/shuttle-preview.mjs` passed with six rendered views, 436 triangles, 65,884-byte GLB, reimported bounds 20 × 9 × 14 m, and no browser errors. Inspected the detachment capture with the film crop alongside it.
## Maze music, shadows and closing tribute — September 17
`npm test`: 106/106 passed. Replaced a flaky patrol test's final net-displacement check with accumulated travel over ten samples: returning near a starting point does not mean the unit failed to move. `tests/exploration-music.mjs` verified exploration plays once and resets with a new run. `tests/maze-music.mjs` verified all four cues using actual browser media playback, preservation of the entry cue after collection, combat interruption and quiet fade. No music errors occurred.
`tests/shadow-stability.mjs` renders the actual projected tank shadow at 24 moving positions near world coordinates (7000, -6000), at camera heights 3/12/150 m. Dark footprint pixel ranges were 4454–4457, 12953–12960 and 758–759 respectively; no large missing patches appeared. This is a targeted regression check, not a guarantee that every intermittent visual artifact is resolved. `tests/death-terminal.mjs` passed with END OF LINE, the bottom tribute visible only on the closing screen, music fade, cursor and unchanged Return-to-restart behavior. Inspected the closing-screen capture. C rear-camera code and both visible/hidden control hints were removed.
Closing tribute follow-up: `tests/death-terminal.mjs` passed with incremental typing, final credit/END OF LINE ordering, cursor at the tribute and clean restart. Inspected the completed screen capture.
### Transfer and human typing revision — September 17
- Full simulation suite: 107 tests passed; the subsequently added carrier-wave boundary test also passed with all six maze-site tests. Checks cover drive lock with working turret, gradual healing, one-time collection, cancellation on death, spherical ground/air contact timing, single carrier damage and out-of-range survival.
- `node tests/data-beams.mjs`: isolated headless Chrome passed all four transfers, persistent blue columns and clean reset, with no browser errors. Inspected surround and completion captures.
- `TRON_URL=http://127.0.0.1:5174 node tests/death-terminal.mjs`: isolated headless Chrome passed incremental human-paced tribute typing, final cursor, music and restart.
- `npm run build` and `git diff --check` passed. Actual timing/music feel remains subject to play review. Carrier receives damage and a visual response; no carrier destruction animation yet.
Transfer surround follow-up: shafts extend into the sky and appear individually at full height. Film-derived ring audio extracted from Sark and MCP at 52.75–54.25 seconds; mild noise filtering, pitch-preserving stretch, soft fades, reversed variant for opening at second nine. Six maze-site simulation checks passed. Clean isolation and sound fidelity require listening review.
September 18: `node tests/models.mjs` passed in isolated headless Chrome. Inspected normal and fully folded Recognizer ground-shadow captures; verified three rig-following shadow meshes are excluded from debris. Imported dimensions and muzzle alignment remain unchanged.
### Wall/debris shadows, shuttle orientation and outro return — September 18
- `node tests/recognizer-shadows.mjs`: isolated Chrome detected 5,758 roof and 13,308 wall pixels darkened by the Recognizer, plus 4,912 roof and 10,743 wall pixels darkened by a debris piece. Full game started with the maze, seam and floor shader hooks and no browser/shader errors.
- `node tests/shuttle-preview.mjs`: six camera presets rendered without errors; corrected underside-facing GLB exported and reimported at 20 × 9 × 14 m. Detachment capture inspected; geometry fidelity remains provisional.
- `TRON_URL=http://127.0.0.1:5174 node tests/death-terminal.mjs`: final-five-second red sign-off, non-looping music, gradual black fade, return to opening and subsequent restart passed.
- Production build and whitespace checks passed. Shadow maps use 256-pixel tiles; fine-detail aliasing and performance with many simultaneous explosions still need play review.
September 18 follow-up checks: full simulation suite passed 109 tests before the new ring-contact test; all seven maze-site tests then passed including that test. Gunner browser test passed the 2× minimum, keyboard cycling, disabled mouse firing/aiming/wheel input and five-pursuer roster. Recognizer shadow test passed at 1.5× pixel ratio using actual explosion blocks. Captured and inspected actual in-game debris shadows at 1.25× and the maze entrance's crisp ground silhouette. Production build passed. Static shadows received by walls/roofs still have finite map resolution.
September 18 shadow follow-up: isolated Chrome at 1.5 device pixel ratio verifies the actual carrier casts onto raised surfaces and the ground (`node tests/carrier-shadows.mjs`). `node tests/maze-shadow-receivers.mjs` still detects 40,393 shadowed tank pixels after slope bias. A running-game maze entrance capture shows continuous wall bevel shading and crisp floor shadows, with no shader errors. These captures cover the tested angles, not every wall/camera combination.
Closing tribute now presents replacement sentence cards, with the final MADE TOGETHER line retained under the detached-program message until the outro finishes. The browser lifecycle check exercises sentence replacement, persistence past the previous fade deadline, MCP dialog playback at the first sign-off character, red text/cursor, and reset after the final fade. Screenshot checks cover PNG content/dimensions, picker save, cancellation, HUD restoration, paused-state preservation, and normal-download fallback.
Grid clouds: `node tests/cloud-layer.mjs` loads the exported model, renders the instanced layer, verifies 1,080-meter altitude, 6 m/s drift, deterministic pause/reset transforms, changed lanes after a full crossing, and disabled shadow flags. It then boots the full game and checks for JavaScript/shader errors. The standalone sky capture was visually inspected. The layer is single-pass and instance GPU buffers are disposed with the view.
`node tests/terminal-raster.mjs` passes in isolated Chrome: the custom font loads, no terminal scanline pseudo-overlay remains, and rendered captures contain both bright and dim blue/red glyph layers at 28, 56, and 80 pixels. The capture was visually inspected. Font generation also verifies its COLRv0 table and both palettes; Safari rendering has not been separately tested.
Beam/carrier follow-up: all nine maze-site tests pass, including off-center CLU entry with beam-centered ring damage. `tests/beam-base.mjs` measures identical red-pool coverage over 24 small world-coordinate translations at each of three camera heights (3, 12, 150 m); a covering solid box completely occludes the pool. `tests/carrier-shadows.mjs` detects both floor/wall shadows and visible grid pixels inside the carrier shadow.
`node tests/horizon.mjs` passes: the forward horizon is brighter than the upper sky and rearward horizon, translating the camera 6 km leaves the gradient unchanged, and full-game startup produces no JavaScript/shader errors. The in-game capture was visually reviewed for the blue/violet directional horizon and foreground occlusion.
## Debris collision volumes — September 18
- `npm test`: 120 checks pass, including five new checks for tilted block floor clearance/sleep, fast thin-wall impact, roof landing, above-wall clearance and diagonal deflection.
- `node tests/debris-collision.mjs`: isolated headless Chrome, four Recognizer explosions plus a tank explosion (68 pieces in this randomized run). Lowest transformed mesh vertex stayed above the floor; 18 pieces were sleeping by 4.5 seconds. Physics-only update averaged 0.14 ms, p95 0.20 ms over 270 frames. This excludes rendering and is not a mobile performance claim. Rendered debris capture inspected; cleanup leaves zero bursts.
- `node tests/breakup.mjs`: actual game retains 15 intact Recognizer blocks per explosion, randomized motion, pause freeze and geometry disposal after expiry.
- Collision shapes are coarse oriented boxes, not triangle-mesh rigid bodies; no debris/debris or debris/vehicle contacts. Concave maze walls use their shared convex triangle prisms. Settled blocks retain their final orientation rather than computing a full torque/friction rigid-body solution.
- `TRON_URL=http://127.0.0.1:5174 node tests/death-terminal.mjs`: revised two-paragraph credits pass the end-screen lifecycle check, including keeping the collaboration paragraph visible, red signoff/audio and return to opening.
## Rapier debris migration — September 18
Supersedes the custom collision solver described above. Seven physics checks cover airborne angular momentum, off-center collision torque, tipping/settling, thin-wall CCD, roof support, above-wall clearance, collision filtering and rigid-body removal. The actual-game breakup browser check passes (15 intact blocks, pause and cleanup).
`node tests/debris-collision.mjs`: isolated headless Chrome, five simultaneous explosions / 73 pieces in the measured randomized run. Physics updates averaged 0.53 ms, p95 0.80 ms over 270 frames; six pieces sleeping at 4.5 seconds. Both burst count and Rapier body count return to zero on clear. Rendered capture inspected. Measurements exclude rendering, and do not establish mobile performance. Rapier contact compliance and interpolated rotations can briefly place a corner below the ground; a render-only floor correction prevents visible clipping without modifying physical velocity or spin.
## Debris damage / wall readability — September 18
- `npm test`: 125 checks pass. Additional damage coverage verifies the energy threshold, proportional damage, CLU/enemy/carrier health, fatal hit events, no duplicate kills and removal of destroyed vehicles from collision targets. Rapier checks cover actual vehicle contacts, low-energy bumps, equal-velocity impacts, faster impacts and fragment mass scaling; one collision produces one damage report.
- `node tests/breakup.mjs`: passes the actual game lifecycle after moving debris updates into the fixed gameplay loop.
- `node tests/maze-shadow-receivers.mjs`: passes; 29,928 tank pixels receive maze shadows and 23,877 pixels show the vehicle silhouette over the maze shadow in the fixture. Explicit draw-order check passes.
- `node tests/wall-shadow-acne.mjs`: passes after brightening wall faces and reducing shadow darkness (6,218 changed wall/bevel pixels under the 10,000 threshold). Brighter wall capture inspected.
- Vehicle collision shapes and fragment density are approximations for damage tuning, not measured film mass/geometry. Carrier contacts use its existing coarse hull bounds. Damage uses normal closing speed, not tangential scrape speed. Debris does not push the independently controlled vehicle bodies or collide with other debris.
## Inherited debris velocity — September 18
`node --test tests/debris-inheritance.test.js tests/debris-physics.test.js`: ten checks pass. Matched explosions verify that every piece receives the exact vehicle velocity difference, including vertical motion and world-Z conversion, and that Rapier receives that velocity. Delayed pieces move with the destroyed vehicle before activation rather than hanging motionless.
## Receiver-plane shadow correction — September 18
- `node tests/shadow-receiver-plane.mjs`: 30 triangulated wall poses at varied oblique angles and 0/7,000-meter offsets; zero self-shadowed pixels compared with shadows disabled.
- `node tests/debris-shadow-fade.mjs`: duplicate overlapping projected shadows produce exactly the same pixels as a single shadow; the interior has one uniform color during fading, without stochastic speckles.
- `node tests/recognizer-shadows.mjs`: hard shadows remain visible on roof/wall receivers from both intact aircraft and debris (5,660 / 13,063 intact pixels; 9,354 / 9,081 debris pixels in this randomized fixture).
- Isolated headless Chrome coverage; the user's Safari screenshot angle is not reproduced exactly, so these checks do not establish artifact-free shadows at every viewpoint.
- Final close-up check after limiting maze casters to solid wall faces/roofs and adding a conservative texel-footprint bias: 384 changed wall/bevel pixels; tightened regression threshold from 10,000 to 1,000. The ledge close-up no longer shows the alternating triangle patches on inspection.
## Screenshot shortcut compatibility — September 18
`node tests/screenshot.mjs` passes with B for capture/download and F9 for cancellation, checking the save-picker path, fallback download, pause preservation, nonblank PNG (1250 × 875), HUD restoration and status feedback. macOS may intercept F9 for a system action; B avoids that conflict. Tested in isolated Chrome; Safari itself was not automated. The final Recognizer shadow fixture and full-game startup check also pass; an earlier run interrupted during development reloads was rerun after edits stopped.
## Roof-edge slab identities — September 18
Launch retry: desktop Chrome 153.0.8010.48 aborts in macOS `_RegisterApplication`. Playwright headless shell 153.0.8010.12 also fails before page creation, explicitly reporting `bootstrap_check_in … Permission denied (1100)`. No rendering assertions ran. Vite starts successfully on 5173. The test now uses that port by default, with a `TRON_URL` override for other servers.
Production build passes. Node geometry checks validate all 1,208 authored-layout slab identities, matching caster position/identity counts, constant identity across each triangle, and generation of the packed depth/identity shader. Added a 30-pose closed-box roof-edge browser regression with slab ID 1208. Browser validation is **not completed**: Chrome launch aborted under current execution permissions. Earlier flat-plane checks do not cover this new roof-edge correction. The local Vite server was restarted on port 5173 and returned HTTP 200.
## Grid-preserving maze shadows — September 19
On the development Mac (Mac14,6, macOS 26.6.2), isolated headless Google Chrome now launches with approved execution outside the workspace sandbox.
- `node tests/maze-grid-shadows.mjs`: passes; 100,481 floor pixels darken and 11,337 grid pixels remain visible inside shadow. Captures of the regression fixture and actual blueprint wall/floor materials were inspected. No JavaScript or shader errors. Actual maze capture shows finite-resolution stepping on close floor-shadow edges; this replaces the old exact but opaque silhouette.
- `node tests/maze-shadow-receivers.mjs`: passes; 30,015 tank pixels receive maze shadows, 23,877 pixels retain the tank's own ground silhouette, and floor-before-vehicle ordering passes.
- `npm run build`: passes, with the existing large-bundle warning.
- Previous resumed run: `node tests/roof-edge-shadows.mjs` passed all 30 poses with zero self-shadowed pixels after browser-launch approval. The earlier permission blocker is resolved.
The production floor intentionally suppresses the grid within maze-site bounds independently of shadows; this change preserves grid lines wherever the original floor material draws them. Actual Safari verification remains unrun.
## Throttle-controlled turbo and exact floor-shadow edges — September 19
- `npm test`: all 128 checks pass. Turbo regression covers stopped activation, no impulse while moving, 2.5× forward/reverse acceleration, coasting to rest without restarting, unchanged braking, timer/cooldown, and expiry easing.
- `npm run test:browser -- --turbo-hints`: headless Chrome passes actual T/W input, stopped activation, accelerated driving, pause, expiry, cooldown, and existing hint behavior.
- `node tests/maze-grid-shadows.mjs`: 100,805 floor pixels darken and 11,357 grid pixels remain visible; duplicating the entire projected shadow produces zero changed pixels. Inspected blueprint-material and full-game captures: the coarse stair-step boundary is gone. Full game/composer starts and renders without JavaScript or shader errors.
- `node tests/maze-shadow-receivers.mjs`: 30,015 shadowed tank pixels and 23,877 tank ground-shadow pixels; ordering check passes.
Exact projected geometry now replaces the floor shadow map described above. Stencil attachments preserve uniform attenuation over overlapping triangles and slabs; normal frame clearing resets coverage. These checks use Chrome on the development Mac; Safari remains untested.
## Recognizer momentum and filtered wall edges — September 19
- `npm test`: 132 checks pass, including bounded yaw/lift acceleration, delayed reversals, settled heading/altitude, momentum through acquisition/folding/aborted drops, and 60/120 Hz agreement. Existing pursuit, crush/recovery, hidden-player independence and separation checks pass.
- `node tests/recognizer-momentum.mjs`: Chrome confirms rotational carry-through followed by reversal, bounded yaw/lift velocity changes, pause freeze and reset. Controlled scene captures provide a reproducible motion comparison. The development turning-acceleration control and reset are also exercised.
- `node tests/wall-shadow-edges.mjs`: matched close-up diagonal shadows compare the old 1024 nearest map with the 2048 filtered map. The original has zero intermediate edge pixels; the filtered result has 15,673 while retaining substantial fully lit and shadowed regions. Both captures inspected; the large binary teeth become a softer, finer transition. Finite-resolution edge variation remains possible, especially at grazing angles.
- `node tests/roof-edge-shadows.mjs`: filtered sampling retains zero self-shadowed pixels in all 30 closed-box poses, including distant coordinates and slab ID 1208.
- `node tests/maze-grid-shadows.mjs`: floor grid visibility and duplicate-shadow uniformity pass, including full-game startup with the larger cached atlas.
Browser evidence uses isolated headless Chrome on the development Mac. This does not substitute for the user's Safari view or final human approval of motion feel. Static shadow-map color/depth storage increases approximately fourfold; maps still render only once per scene.
## Exact wall shadows and manual refresh — September 19
### Two-pass materialization and recurring reinforcements — September 20
- `npm test`: all 137 simulation checks pass. Added coverage for a shared ten-second timer with three pursuers, repeated single spawns, loss-of-pursuit reset, Clu destruction, fresh-run reset, copied observations, hidden-player independence, inert/intangible materialization and ordered wire/solid phases. Updated the persistence test to track the original aircraft while allowing the newly requested reinforcements.
- `node tests/materialization.mjs`: native headless Chrome on the development Mac passes. Rendered captures show a transparent red edge model after pass one, partial solid fill during pass two, and the original solid model afterward. Wire-only capture has zero blue solid pixels; completed capture has 40,469. Inspected standalone phase captures and the full-game glowing rectangle capture in `test-results/materialization-*.png`.
- The browser check also triggers two consecutive reinforcements through the live simulation, verifies audio pool growth, freezes simulation/rez time on pause, renders fifteen aircraft with bounded wall-shadow atlas slots, and resets to the initial simulation roster without browser/shader errors. Visual/audio pools are reused after reset rather than appended again.
- `node tests/recognizer-shadows.mjs`: 5,660 roof / 13,063 wall shadow pixels, with debris receiving coverage retained. The test now uses port 5173 by default and accepts `TRON_URL`; the first attempt used its old hardcoded port 5174 and failed to import the current modules.
- `node tests/recognizer-audio.mjs`: sample playback, explosion/impact lifecycle and stereo energy checks pass. `npm run build` passes.
Follow-up: each pass now opens from a horizontal line over 0.6 seconds, with 6 m of world-space clearance at both ends of its sweep. The materialization animation now lasts 5.45 simulation seconds. All 137 simulation tests and `node tests/materialization.mjs` pass again, including the new opening-phase assertions. Inspected the initial line and half-expanded rectangle captures; both contain no solid vehicle pixels. Reinforcements are unbounded while pursuit continues, so long-session crowd/performance tuning and Safari-specific visual review remain separate from these checks. Distant aircraft beyond the nearest twelve still cast projected ground shadows but omit dynamic wall-shadow atlas tiles.
### Moving-target interception — September 20
`npm test` passes all 132 checks after adding rear-approach cases to the autonomous cruise interception regression. On the clear grid with seed 1982 and Clu driving at 22 m/s, aircraft starting 60, 160 and 300 m behind successfully crush Clu after approximately 11.1, 19.4 and 30.8 seconds. Existing committed-miss, stationary-target, momentum and hidden-player independence checks pass. Previously, rear approaches settled into speed matching about 40 m ahead without attacking.
`node tests/recognizer-intercept.mjs` passes in native headless Chrome on the development Mac: a Recognizer beginning 60 m behind overtakes, folds about 39.4 m ahead, and its braking drift carries the committed strike onto steadily moving Clu. Inspected rendered fold/impact captures in `test-results/recognizer-intercept-*.png`; no browser or shader errors. This establishes the controlled straight-line encounter, not guaranteed hits against turning or turbo movement.
The exact geometric wall overlay supersedes the filtered atlas above; moving vehicles retain a 1024-pixel maze atlas. `node tests/wall-shadow-edges.mjs` compares rendered pixels with independent ray/box intersections: native Chrome reports zero mismatches for geometric shadows versus 5,959 for the old nearest atlas. Docker Chromium/SwiftShader reports 17 versus 5,957 (raster-edge precision differences). The actual maze capture `test-results/wall-edge-maze.png` was inspected and shows straight, crisp inter-wall boundaries. The user also confirms the wall shadows look great. Native `tests/maze-shadow-receivers.mjs` preserves tank receiving and ground-shadow layering.
Vite now disables HMR by default. An isolated Chromium check changed an imported source module while keeping a page-state marker: the page and old module value remained intact, then manual refresh loaded the new value. `TRON_HMR=1` remains the opt-in native development setting. The Recognizer reset regression now reads velocities in the same browser task as reset, avoiding accidental measurement after subsequent simulation frames.
`scripts/codex-container test` passed on Docker Desktop on the development Mac: Linux ARM64, Node 22.23.2, Codex 0.155.1, Chromium 153.0.8010.12 and ANGLE/SwiftShader Vulkan. It verified unprivileged UID 1000, absent host home and Docker socket, denied root-filesystem writes, permitted project writes, WebGL 2 pixel output, all 132 simulation tests, 30 roof-edge poses, grid retention/overlap uniformity/full-game startup, Recognizer momentum/pause/reset/tuning and the production build. `test-results/container-check.json` records the run. Container software rendering is substantially slower than native GPU rendering; native driving feel and Safari-specific behavior are separate checks. Device login remains an interactive user step.
### Single-sweep materialization revision — September 20
The user replaced the two-pass sequence with one wireframe sweep and a 0.35-second whole-body solid fade. Opening and clearance are retained; activation now occurs at 2.75 seconds. `node tests/materialization.mjs` passes in native headless Chrome, including zero solid pixels at the end of the wireframe sweep, whole-body visibility during the fade, and increased blue-channel energy at full opacity. Inspected the mid-fade capture. Repeated reinforcements, the fifteen-aircraft render, pause and reset also pass without browser/shader errors. Temporary transparency/depth-writing settings return to their original values when the fade completes, and dynamic shadow attenuation follows solid opacity.
### Phantom shadow tiles after reinforcement spawning — September 20
Reproduced square shadow-map footprints after replacing an aircraft atlas with one using the same shader key. Receiver material program caches retained stale atlas/matrix uniforms; evicting those bindings before recompilation fixes the ghost patches. The materialization rectangle itself was already excluded from shadow casting.
`node tests/materialization-shadows.mjs` passes in native headless Chrome with fifteen aircraft and twelve active atlas slots: ordinary and fully materialized aircraft produce identical roof/ground pixels after an atlas rebuild (zero differences), the wire-only phase produces zero ghost-shadow pixels, and 10,122 real silhouette pixels remain. Inspected the oblique roof/ground capture. The initial ground-only reproduction differed at 128,488 pixels before the fix and showed square shadow-tile footprints. `node tests/materialization.mjs` also passes repeated live spawning, fifteen-aircraft rendering, phase captures, pause and reset without browser/shader errors.
### Exterior teleport pads — September 20
`npm test` passes all 143 simulation checks, including six new teleporter checks: exterior pad clearance and cross-maze links, whole-footprint entry with rotated turret, complete departure/arrival and exit-before-reentry, ground/aircraft altitude and heading preservation, occupied destinations and reset. `npm run build` completes; the existing large-chunk advisory remains.
Native macOS headless Google Chrome via `node tests/teleporters.mjs` passes actual W-key entry, all 16 pads, reverse and forward materialization, pause, arrival lock, an existing Recognizer and ground tank transferring, and reset with no console/page errors. Inspected `test-results/teleporter-dematerialize.png` and `teleporter-arrived.png`: glowing square, receding tank wireframe/rectangle, and solid Clu at the destination are visible. Other captures cover the approach, incoming sweep and aircraft. The first native browser attempt was blocked by the workspace sandbox; the approved external launch succeeded. Safari and human driving review remain unrun for this feature.
Recognizer flyover follow-up: `node --test tests/teleporters.test.js` passes all seven checks. The added fixed-step flight regression crosses pads at 34.155 m/s from multiple headings, including a high-altitude diagonal approach. Centered and 4 m offset approaches trigger; a 6 m offset approach correctly does not. An axis-aligned Recognizer footprint is 23.4 m wide on a 32 m pad, leaving only 4.3 m of center offset on either side for full containment. Occupied/reserved destinations can also defer departure. The reported play-session flyover has not been reproduced as a trigger failure; no activation rule changed.
Pad-size follow-up: doubled each side from 32 m to 64 m and removed the surface fill, retaining the glowing border at its previous world-space thickness. All seven targeted teleporter simulation checks pass; moving Recognizers now enter successfully at 20 m lateral offset, while 22 m remains outside full containment. No further browser reproduction was run, at the user's request.
Grid-alignment follow-up: pads now span exactly 2×2 floor-grid cells (48×48 m), with all four edges on multiples of the shared 24 m grid spacing. Outward snapping preserves wall clearance. All seven targeted teleporter tests pass, including edge alignment and moving-aircraft containment at the revised size. The border also has a small polygon depth bias and derivative-based smoothing to address reported flicker; JavaScript syntax checks pass, but no browser reproduction/visual confirmation was run for these follow-ups.
Pad-flicker follow-up: removed the separate pad planes and paint red borders directly into the existing floor shader. There is no independent pad depth surface; the border uses the same 0.10 m half-width and 1.2× derivative antialiasing as the grid. Syntax checks and shader-injection checks pass for all 16 borders and preview visibility. This supersedes the polygon-offset attempt. Browser/human confirmation of the reported flicker remains unrun for this revision.
September 21 invisible-pad correction: the floor border was changing `diffuseColor` after Three had already computed `outgoingLight`. Mix the border into outgoing light before the opaque output instead. `node tests/teleporters.mjs` passes in native headless Chrome with no browser errors; inspected the newly generated arrival capture and confirmed visible thin perimeter lines, unfilled interior, grid alignment and vehicle occlusion. Transfer, pause, ground/aircraft and reset checks also pass. This confirms restored visibility; it does not establish Safari motion/flicker behavior.
September 21 maze grid alignment: `npm test` passes all 144 checks after snapping site origins to 24 m and restricting seeded rotations to quarter turns. Site tests assert aligned coordinates and allowed angles while retaining collision, clear-space and transform round-trip checks; teleporter clearance and links also pass. No browser visual check was run for this placement-only revision.
September 21 materialization awareness: the full simulation suite passed 146 checks; after adding a destination-arrival integration case, all 15 targeted reinforcement/teleporter checks pass. Coverage confirms fresh pursuit memory suppresses the arrival search beam, ground/air knowledge sharing, copied rather than aliased reports, three-dimensional range limits, confirmed neutralization and destination-side knowledge on enemy teleport completion. No browser/audio visual check was run for this simulation-only correction.
### Optional tactical enemies and live Jev — September 21
`npm test` passes all **159** checks. New checks cover oriented versus circular wall clearance, swept rotation/translation, bounded low-corridor routing, inertial low-altitude trajectory following, a real maze wall-side stomp, wounded-unit withdrawal selection, hidden-Clu counterfactuals, mode/roster isolation, stale/invalid/uncertain decisions, server limits/timeouts/key isolation, and client pause/reset cancellation. `npm run build` passes with the existing chunk-size advisory. A scan confirms that the private credential is absent from built JavaScript, HTML, JSON and text outputs; Git ignores the credentials directory.
Native macOS headless Chrome: `node tests/tactical-browser.mjs --live` passes UI switching, the 2-aircraft/1-tank roster, the actual wall-side descent/crush, missing-provider fallback, pause, reset and return to Classic without further requests. Inspected `test-results/tactical-controls.png`, `tactical-wall-descent.png` and `tactical-live-jev.png`. The aerial capture shows the aircraft descending beside the slab; the final panel shows a real accepted Jev decision. The credential-file URL returns HTTP 403. Each `--live` invocation allows at most one real model call; normal browser runs mock the unavailable-provider branch.
Three bounded live calls were made in total: a server smoke at **317 ms** with confidence **0.59**; a browser call at **292 ms** with confidence **0.36**, correctly rejected under the initial 0.55 threshold; and a final browser call at **467 ms** with confidence **0.46**, accepted with the now-adjustable default threshold **0.25**. Calls used roughly 4,000 input tokens each. These are individual smoke measurements, not a latency distribution or evidence of better gameplay. A native browser fetch binding issue found by the first browser test was corrected before the passing checks.
Direct browser preflights to TypeSafe returned HTTP 400 without `Access-Control-Allow-Origin` for `http://127.0.0.1:5175` and `http://localhost:5173`. The game therefore uses the same-machine relay; disabling browser security is not part of setup. The main server was restarted on port 5173 with the credential connected. The local planner remains usable without the service; Classic is still the default. Human driving/encounter review, Safari behavior and long-session Jev quality/performance remain open.
### September 21 — Jev startup and reinforcement interval
Browser startup now selects Jev with the small encounter, replacing earlier experimental saved preferences once; future explicit mode choices persist. Headless simulation fixtures retain their Classic baseline. Classic reinforcements now materialize once per 20 continuous pursuit seconds; tactical modes still suppress them. `node --test tests/reinforcements.test.js tests/tactical.test.js tests/jev.test.js` passed all 19 checks, including threshold and repeat timing. `node --check src/main.js` and `npm run build` passed (existing bundle-size advisory). Browser startup was not rerun for this default/timing change.
### September 21 — full roster and nearby Jev decisions
Restored the full enemy population by disabling the optional small encounter at startup. Version-2 preferences retain their AI mode but migrate to the full roster; subsequent explicit small-encounter choices persist. Verified Jev and Classic produce identical initial vehicle populations. Jev requests now require a recorded target position within one maze length horizontally and a memory at most 38 seconds old. Unaware and distant units use local tactics, without inspecting live hidden Clu coordinates; replies are rejected if the unit has since left that range or lost valid knowledge. `npm test` passed all 160 tests, including nearby/distant/stale/unknown contact and hidden-position checks. `npm run build` passed with the existing bundle-size advisory; `git diff --check` passed. Browser play and live API calls were not rerun for these changes.
### September 21 — opening pursuit executor correction
Reproduced four opening Recognizers braking and returning toward initial route poses without any Jev requests. Above-roof travel now bypasses those stationary preparation poses; healthy units have pursuit/support candidates, and distant allies no longer trigger premature regroup/hold scoring. After 12 seconds chasing a moving Clu in the seeded simulation, all five aircraft close at least 40 m and maintain speed above Clu's normal maximum. `npm test`: 161 passed. `npm run build`: passed (existing size advisory). Native headless Chrome, `TRON_URL=http://127.0.0.1:5173 node tests/tactical-browser.mjs`: first attempt lost `__tron` after a wall-side descent; rerun passed mode switching, wall-side drop/crush, fallback, pause and Classic isolation. No live Jev call was made. Human review of pursuit feel remains pending.
### September 21 — enemy hearing
Added simulation-owned acoustic observations for ground/air units, independent of player audio mute. Engine samples use short ranges; cannon fire, impacts and destruction/data-wave events use larger ranges. Walls reduce pressure to 0.25; deterministic estimation noise and nominal engine loudness keep reports approximate. Reports expire after 10 seconds and remain separate from visual memory, radio target fixes and attack permissions. Local investigation and Jev now accept hearing-only context.
`npm test`: all 167 tests passed. After the final nominal-engine-range refinement and tuning controls, `node --test tests/hearing.test.js tests/jev.test.js tests/tactical.test.js` passed all 20 targeted checks. `npm run build` passed with the existing bundle-size advisory; `git diff --check` passed. Native headless Chrome via `node tests/hearing-browser.mjs` passed: an actual cannon shot outside an aircraft's view produced a sound report, local investigation and a mocked accepted Jev choice with `target: null`; pause froze simulation and no page errors occurred. No paid API calls were made. Human review of stealth balance and range tuning remains pending.
### September 21 — carrier shadow edge filtering
The carrier's 1024-pixel map covered 1500 m, producing roughly 1.46 m steps. Its map is now 4096 pixels with four-tap bilinear comparison filtering and a smaller carrier-specific depth bias. Receiver-plane slope correction applies independently to every sample; packed depth stays nearest-sampled. Other shadow maps retain their sizes/default sampling.
Native headless Chrome checks: `node tests/carrier-shadows.mjs` passed actual hull shadows on walls (15,954 changed pixels), floor shadows (17,884) and visible grid under the shadow (1,934). Independent ray/box edge tests, both stationary and translated, require the new incorrect-pixel count below 45% of the old count and nonzero filtered edge coverage; both passed. Inspected `test-results/carrier-edge-old.png` and `carrier-edge-filtered.png`: large sawtooth steps are substantially reduced. Finite-resolution edge texture remains under close magnification.
`node tests/maze-shadow-receivers.mjs` passed (30,015 changed tank pixels). `node tests/recognizer-shadows.mjs` passed roof, wall and debris shadows after making that fixture explicitly use Classic mode; its initial run passed rendering assertions but failed on an unrelated Jev HTTP 429 console error. `npm run build` passed with the existing bundle-size advisory. Safari and GPU memory/frame-time impact have not been profiled; the single carrier map now has sixteen times the texel count.
### September 21 — active lost-contact search and event replanning
Added observation-driven tactical interruptions for sight loss/reacquisition, contact acquisition/expiry, velocity-vector changes of at least 8 m/s and recorded position shifts of at least 45 m. A 0.65-second event cooldown limits oscillation while bypassing the older four-second commitment gate. Obsolete Jev answers are rejected even during the cooldown. Momentum is retained, ground routes refresh, and committed aircraft attacks retain their state machine. Healthy units prioritize bounded last-known-track investigation and nearby branch inspection over passive ambush; wounded withdrawal is preserved. Prediction stops at mapped walls and never reads hidden Clu state.
`npm test`: 171 tests passed. New checks cover sight interruption/debounce, stale reply rejection, observed turns/stops/displacement, radio contact, continued lost-contact movement without inventing sightings or attacks, nearby branch selection and wounded retreat. Existing opening pursuit, wall-side crush, collision, hearing and hidden-information checks also passed. `TRON_URL=http://127.0.0.1:5173 node tests/tactical-browser.mjs` passed native headless Chrome mode switching, wall-side drop/crush, mocked-provider fallback, pause/reset and Classic isolation. `npm run build` passed with the existing bundle-size advisory. No live Jev requests were used in this verification. Human assessment of aggression and long-run search quality remains pending.
### September 21 — connected search after reaching last contact
Corrected repeated return-to-contact behavior: search origin completion and visited checkpoints now persist across maneuver choices for both aircraft and tanks. Search candidates derive from a bounded, swept tank-corridor graph rather than spatially nearby open cells. The graph can turn corners while excluding disconnected pockets. Jev receives the checked locations and candidate corridor hypotheses; no new target fix is fabricated. High aircraft inspect from above without repeatedly descending at each checkpoint, preserving low routes for settled low craft.
`npm test`: all 174 tests passed. New regressions cover an L-shaped corridor and disconnected pocket, a 35-second aircraft search that checks multiple destinations without returning to `search-track`, and ground-unit arrival advancing the search. The inspected seeded aircraft run checked eight locations in 35 seconds. `TRON_URL=http://127.0.0.1:5173 node tests/tactical-browser.mjs` passed native headless Chrome attack/lifecycle/fallback checks. `npm run build` passed with the existing bundle-size advisory; `git diff --check` passed. No live Jev calls were made. Human review of corner-search behavior remains pending; the search graph is bounded and does not cover the entire maze.
### September 21 — remaining carrier shadow roughness
Refined only the carrier to a 6×6 Gaussian-weighted coverage filter, retaining the 4096-pixel map and receiver-plane comparison per tap. Texture allocation is unchanged; this trades additional texture samples and a slightly softer boundary for less edge variation. Native headless Chrome `node tests/carrier-shadows.mjs` passed the real carrier wall/floor/grid checks. On a straight edge, fitted contour roughness fell from about 1.37 pixels with the previous four-tap filter to 0.80 pixels, both before and after translating the caster (about 42% lower). The softened contour differs more from a binary ray mask inside the transition, so the new acceptance check bounds that difference to a 1.8 m boundary band; no mismatches remained outside it. Before/after captures were inspected. `npm run build` passed with the existing size advisory. Safari and frame-time impact remain unmeasured.
### Beam camera and persistent autoplay — September 21
- `node --test tests/beam-camera.test.js tests/autoplay-input.test.js tests/autoplay.test.js`: 14 passed. Half-speed timing, pause stability, interruption/reduced motion, channel overrides and existing planner invariants.
- `node tests/beam-camera.mjs`: passed in headless Chrome on macOS, local Vite. Aerial orbit exceeds 590 m, turret unchanged, camera freezes on pause and returns below 15 m by ring opening. Captures: `test-results/beam-camera-orbit.png`, `test-results/beam-camera-return.png`; return capture inspected.
- `node tests/autoplay.mjs`: passed with mocked Jev, no paid requests. Manual Space and braking preserve autoplay, release restores acceleration, U/button toggle still works; no page errors. Camera feel still needs human play review.
### Autoplay service loss — September 21
- `node --test tests/autoplay.test.js tests/jev.test.js`: 18 passed, including network errors, timeout, HTTP failures, provider cooldown, explicit re-enable and pause cancellation. Service failures clear the pilot plan and cruise throttle without changing manual held inputs.
- `node tests/autoplay.mjs`: headless Chrome on macOS passed with mocked 503 outage; autopilot disengages, its button switches off, warning remains visible, and it stays off. No paid requests or JavaScript errors.
### Local relay pacing — September 21
- `node --test tests/autoplay.test.js tests/jev.test.js`: 19 passed. Verify wall-clock pacing between player/enemy requests, separate hourly/spacing messages, retry delays and expiry reset on status reads. Production build passed. Provider credit balance was not queried.
### Local dollar budget — September 21
- `node --test tests/jev-spend.test.js tests/jev.test.js tests/autoplay.test.js`: 21 passed. Covers reported cost, free output, unknown outcomes, pre-dispatch rejection, rolling expiry, restart persistence and existing disengagement behavior. No paid requests used.
### Autoplay beam navigation — September 21
- `node --test tests/autoplay.test.js tests/ground-tanks.test.js`: 32 passed. Includes persistent distant destination, all-beam color/distance/bearing input, capture handoff, transfer hold and two captures across authored mazes in 600 simulated seconds without enemies. Build passed. These are local-controller checks, not a claim that live JEV completes the full game under attack. Human play review pending.
### Background autoplay — September 21
- `node tests/autoplay.mjs`: headless Chrome/macOS passed with mocked JEV and synthetic blur/hidden visibility transitions. Simulation time and position advance while hidden; explicit pause freezes time; disabling autoplay while hidden pauses; returning visible restores the ordinary loop. Existing controls and outage checks pass, no paid requests or page errors. This verifies application lifecycle behavior, not long-duration Safari/OS throttling.
- `npm run build`: passed.
### Autoplay corners, nearby objectives and turbo — September 21
- `node --test tests/autoplay.test.js`: 18 passed, including preserving waypoint progress on JEV confirmation, replacing distant objectives with nearby red beams, safe turbo acceleration/short-leg rejection, a room-to-beam route with repeated mocked confirmations, and two captures in separate authored mazes. Five additional room starts were probed without enemies; each captured the local beam within 90 simulated seconds.
- `node --test tests/autoplay-input.test.js tests/ground-tanks.test.js tests/simulation.test.js`: 64 passed.
- `node tests/autoplay.mjs`: headless Chrome/macOS passed existing driving, temporary overrides, background lifecycle, pause and mocked service-failure checks. No paid requests. `npm run build` passed. Live combat behavior and the user’s exact room scenario still need play review.
### Post-combat mission handoff — September 22
- `node --test tests/autoplay.test.js`: 21 passed. New regression creates five pursuers, accepts an escape choice, destroys all five, rejects the late escape reply, and verifies immediate beam routing plus subsequent approach. Additional checks cover a distant observed enemy moving away and route-failure hold/retry without unrestricted exploration.
- `npm run build` and `git diff --check`: passed. No paid requests. These are deterministic controller regressions; the user’s exact live session was not captured or reproduced.
### Actual blueprint opening navigation — September 22
- Reproduced the bug before the fix: with blueprint layout selected, no enemies and the actual opening coordinates, autoplay returned hold. The historic authored-layout fixture did not reproduce it.
- `node --test tests/autoplay-blueprint.test.js tests/autoplay.test.js tests/ground-tanks.test.js`: 40 passed. The blueprint test checks every swept route segment and actually captures the local beam within 300 simulated seconds.
- `node tests/autoplay.mjs`: headless Chrome/macOS passed. Starts on the browser-default blueprint, removes enemies, enables autopilot and verifies a collect-data plan plus movement exceeding 40 m before continuing the existing controls/background/outage checks. Mocked JEV; no paid requests or page errors.
- `npm run build` and `git diff --check`: passed. The user’s particular saved encounter remains unavailable, but the same hold symptom was reproduced on the actual layout.
### Intermediate corner stall — September 22
- Reproduced on blueprint at t≈146.8 s: x≈−283.17, s≈−262.70, an intermediate waypoint about 0.5 m away, next leg occluded, speed/throttle/steer zero. Manual rotation was unnecessary after distinguishing intermediate corner steering/creep from final stopping.
- `node --test tests/autoplay-blueprint.test.js tests/autoplay.test.js`: 22 passed. Blueprint coverage now leaves the first maze and captures a second beam, rejecting three seconds of motionless/no-steering behavior outside transfers. `npm run build` and `git diff --check` passed. No live API calls.
- Longer 800-second probe passed the original location but encountered a distinct hold/route failure near the third maze at about 689 seconds; remains unresolved.
### Damage bubble disabled — September 22
- `node --test tests/maze-sites.test.js`: 10 passed. Default-disabled wave leaves nearby enemies/carrier intact and radius/hit list empty; optional enabled-wave regressions and transfer timing still pass. Renderer sphere/ring visibility uses the same flag. Build and diff checks passed; no browser visual check performed for this flag change.
### Pad horizon and carrier lights — September 22
- `node --test tests/carrier-search.test.js`: 4 passed, including disabled lights clearing tracking/illumination and producing no new radio reports.
- `node tests/teleporters.mjs`: Chrome/macOS passed transfers and clipping, captures inspected at horizon and beside a pad. No continuous red horizon stripe; nearby borders remain visible. Carrier beam visuals hidden.
### Beam panels, retries and stats — September 22
- `node --test tests/jev-stats.test.js tests/jev-spend.test.js tests/maze-sites.test.js tests/autoplay.test.js tests/jev.test.js`: 43 passed. Covers timeout retries/recovery/exhaustion, counts across client resets/new rounds, trailing rates, reported/estimated costs, panel contacts and disabled blast.
- `node tests/data-beams.mjs`: Chrome/macOS completed all four beam transfers, blue states and reset without errors. Panel shell capture inspected from aerial view; close-view review run uses `--preview` and reduced motion.
- `node tests/autoplay.mjs`: Chrome/macOS passed HUD count/rate/cost assertions plus existing controls, background and outage coverage, with mocked JEV and no paid requests.
- `npm run build` and `git diff --check`: passed.
### Moving-shadow roof rims — September 22
- Chrome 153/macOS: isolated blueprint maze 1982 with a broad overhead moving caster reproduced the reported sawtooth wall-rim shadows. Rendered comparison inspected after the fix: wall faces and trim remain continuous.
- `node tests/recognizer-shadow-occlusion.mjs`: passed, including full-height wall receiver reaching the roof edge with a slab ID. Exposed roof retained all 8,694 shadow pixels; shielded wall went from 16,474 leaked pixels to zero. Ground, debris and projected-shadow occlusion passed.
- `npm run test:browser -- --refactor`: passed snapshot isolation, pause, camera views and repeated resets; resources stable at 97 geometries, 28 textures and one context.
### Safari local-file documentation styling — September 22
- User screenshot confirmed entirely missing CSS in Safari when opening the JEV file URL; automated WebKit with default permissions did not reproduce that load denial.
- Project generation now embeds the upstream colvmn stylesheet in all nine authored pages, without modifying the colvmn submodule or hand-editing HTML.
- WebKit 26.6/macOS verified both file and HTTP JEV pages with external CSS and layout JavaScript blocked: 202 embedded CSS rules, 960px page width, expected background/font and no horizontal page overflow at 1280px. Regeneration retains a single embedded stylesheet.
### Solar Sailer and opening carrier materialization — September 22
- `node --test tests/solar-sailer.test.js`: passed fixed-lane direction/altitude, three-minute repetition, hidden interval and disabled setting.
- `node tests/solar-sailer.mjs`: Chrome 153/macOS rendered source and adapted sailer, seven model textures loaded. Repeated crossings retain 109 geometries/eight textures; disposal leaves zero scene geometries and only the renderer-owned DFG lookup texture. Checked hidden preview/reset and captured the corrected forward orientation and split amber beam.
- `node tests/carrier-materialization.mjs`: Chrome checked opening, wire sweep, solid fade, completion and repeat/reset. The sweep spans the 1,225 m long axis. Captures inspected.
- `node tests/materialization.mjs`: existing Recognizer phases, two reinforcements, rendering/audio/shadow rebuilds, pause and reset passed after shared reveal changes.
- `npm run test:browser -- --refactor`: Solar Sailer integration passed pause/view/reset and retained stable resources across three restarts. No paid JEV requests used.
### Active-layout pad visibility — September 22
- `node tests/teleporter-visibility.mjs`: Chrome 153/macOS pixel checks passed for all 52 pads across blueprint seeds 1982/99, authored seed 7, and authored single-site 1982. Each active pad location contains 1,824–2,744 red border pixels in the overhead fixture; disabling borders leaves zero. A shared renderer exercises shader reuse across different pad layouts.
- Root cause: physics used scenario-generated pad positions while presentation compiled legacy default authored positions. Rendering now derives positions from the same scenario inputs.
Solar Sailer timing follow-up: first transit now starts at 180 seconds. Two unit tests cover pre-arrival fade, post-departure fade, dark intervals and repeat cycles. Chrome fixture verified beam opacities 0/0.5/1/0.5/0, unchanged hull gap and stable resources; production build passed.
Carrier arrival follow-up: the rectangle now stays at one world position while the carrier advances through it, bow first. `node tests/carrier-materialization.mjs` checks constant world coordinates across opening/sweep/fade, bow-before-stern clipping, full completion and restart. The previous eight-second timer is superseded by distance divided by carrier speed.
Carrier lighting follow-up: Chrome fog fixture retained 14,111 bright red pixels during the wireframe phase. All 34 live light materials share the reveal cut; beacon emissive intensity still cycles from 0.1 to 3.1. Front-first stationary-plane/reset assertions and existing Recognizer materialization browser checks passed.
Carrier panel reference follow-up: reviewed six frames spanning 2:37–2:38 of `Carrier derezed.mp4`. Chrome captures confirm the translucent cool field and faint green rim; checks passed for the 7 Hz pulse (opacity multiplier 0.78–1), world-fixed reveal, lights and reset. Extracted stereo WAVs are 3.00/2.88 seconds at 44.1 kHz with peak 0.7; auditory suitability remains unverified and gameplay does not load them.
Solar Sailer speed doubled to 280 m/s; 12 km crossing now takes about 42.9 seconds. First appearance and repetition stay at 180 seconds, with a three-second beam fade-in and extended nine-second fade-out.
Carrier audio trim follow-up: retained only the second half, 2:38.5–2:40. Regenerated the 1.50-second clip and 1.38-second loop, including source metadata and catalog descriptions.
September 23 carrier panel refinement: Chrome materialization checks passed after adding the yellow rim and gray-fill fade to zero at its inner edge. Rendered midpoint capture inspected; center base opacity remains 0.5 and the existing pulse remains active. Production build passed.
### Wall-shadow flicker — September 23
- Chrome 153/macOS `node tests/wall-shadow-stability.mjs --measure` reproduced depth dropout before the fix: up to 31,795 mismatching pixels versus a depth-uncontested reference; camera shifts of 1–2 cm changed the missing patches at 10 km world coordinates.
- After adjusting the wall overlay’s depth bias, `node tests/wall-shadow-stability.mjs` passed all 81 poses with zero mismatching pixels and explicit visible-shadow coverage in every pose.
- `node tests/wall-shadow-edges.mjs` retained zero mismatches against independent ray/box shadow intersections. The nearest-map comparison had 5,959 mismatches, confirming fixture sensitivity. This addresses the reproduced coplanar shadow flicker; other moving-object cases remain subject to play review.
## Spotlight surface reach — September 23
`node --test tests/alertness.test.js`: 11 passed, including every ribbon endpoint at 400 m altitude reaching the ground or a raised clipping surface. `node tests/searchlights.mjs`: Chrome headless on macOS passed rendering, wall/floor clipping, state visibility and pause checks without browser errors. Detection limits are unchanged.
## Confined stomp positioning — September 23
On macOS, `npm test` passed all 249 tests. New cases cover three oblique wall orientations, close-wall offsets, tight corridors, and a complete collision-free stomp in a 10.8 m corridor rotated 0.37 radians. Existing wall-side, moving-turn, information isolation and attack tests remain passing. `node tests/tactical-browser.mjs` passed in headless Chrome: wall-side drop, AI mode changes, no-key fallback, pause/reset and Classic isolation. Inspected `test-results/tactical-wall-descent.png`; this is a small aerial fixture, not a general approval of all driving/attack feel. `node scripts/benchmark-enemy-pursuit.mjs` measured simulation-step p95 0.59 ms, worst 17.83 ms on this run (not a browser frame-rate claim). Landing search remains under the shared planning admission budget. Physically impassable gaps remain unsupported.
## Surface impacts, support roles and victory — September 23
On macOS, `npm test` passed all 256 tests before the final additional projectile-event test; `node --test tests/surface-impact.test.js` then passed all five impact tests, including both player and enemy wall shots. Formation coverage includes six recognizers, separated goals, retained satellite assignment beyond 140 m, support without personal sight, stale-memory release and existing leader handoff. The pursuit benchmark measured p95 0.35 ms/worst 15.67 ms simulation steps on this run, not a browser FPS measurement.
Headless Chrome checks passed: `node tests/surface-impacts.mjs` (surface orientation, camera independence, shader compilation and cleanup), `node tests/victory.mjs` (all-blue victory, exact text, frozen simulation, Return to credits/music without detachment, restart), `node tests/death-terminal.mjs` (existing fatal-hit terminal/music/signoff flow), and `node tests/tactical-browser.mjs` (wall-side stomp, modes, fallback, pause/reset). Inspected `surface-impact-wall.png`, `victory-terminal.png`, and `victory-credits.png` under `test-results`. Browser music state/playback was checked; no claim of an auditory review. Rings use existing projectile collision coverage, with rendered mesh normals for registered vehicle hits; this does not introduce projectile collision for scenery that was previously noncollidable.
## Victory printer pacing — September 23
Use a monotonic clock, stable per-line DOM nodes and at most one new character per rendered frame. The rate is now 45 characters/second; a stalled frame cannot dump a word-sized backlog. Skip the hidden 3D scene while the victory page is open. `node --test tests/victory.test.js` passed, including a two-second synthetic stall, one-character updates, unchanged completed lines and restart. `node tests/victory.mjs` passed in both headless Chrome and workspace-installed WebKit: early/late rates were 45.00/44.99 and 45.06/44.98 characters/second respectively. Exact text, credits/music transition, Shift+8 preview and restart checks passed; build passed. A browser rendering below the target character rate can print more slowly rather than batch characters.
## Two-recognizer approach regression — September 23
A deterministic local encounter with two healthy aircraft 120 m to either side of stationary Clu failed to complete a stomp after 120 simulated seconds before the fix. Progress-based lease renewal completed it in 24.27 seconds. `node --test tests/escort-debris.test.js tests/tactical.test.js` passed all 32 tests, including a 45-second completion bound, stable lead, single active strike, blocked-lead handoff and stalled-lead expiry. `node tests/tactical-browser.mjs` passed in headless Chrome (wall-side attack, AI modes, fallback, pause/reset). The reported live JEV session itself was not captured; the reproduced local-controller failure is fixed.
Safari follow-up: victory text now appends one immutable `.printer-character` element per glyph, grouped into stable lines. Printing never reads bounding boxes, offset/client/scroll dimensions, or computed styles; normal browser layout is still necessary. Targeted tests passed, including glyph-node structure and one-character updates. Headless WebKit measured 45.19 characters/second early and 44.98 late, with victory/credits/restart checks passing. Build passed. This automation has not reproduced the slowdown reported in the user's Safari window, so the visible Safari result still needs confirmation.
## Pursuit stomp and JEV toggle — September 23
- `node --test tests/tactical.test.js tests/escort-debris.test.js`: 34 passed. Simulated accepted provider pursuit choices complete collision-free stomps in an oblique 10.8 m corridor and a two-aircraft full-session encounter with one active attacker.
- `npm test`: 260 passed.
- `node tests/jev-toggle.mjs`: headless Chrome on macOS passed N on/off while paused without advancing/restarting the round, repeat suppression, autoplay disengagement on disable, and U re-enabling JEV. Provider requests stubbed; no live JEV quality claim.
- Actual interactive flight and live-provider choice quality remain for play review.
## Four-second stomp acceptance — September 23
- `npm test`: 269 passed, including 32 close-stomp cases (eight headings × local/pursue/strike/low-approach), the real five-unit starting formation, tight oblique corridors, no stomp self-damage from debris, bounded repeatable cannon spread, and zero vertical enemy scatter for level aim.
- Close-range deadline assumes a healthy aircraft starting at rest at 80 m altitude, stationary observed Clu 48 m away in open space, and a ready attack. Simulated provider responses exercise accepted plans, not live JEV policy quality.
- `node tests/stomp-browser.mjs`: headless Chrome/macOS, actual opening formation, simulated accepted JEV low-approach/pursue replies, starting throttle released. Rendered drop and stomp completed at 11.0 seconds from encounter start (initial separation exceeds 300 m); attacker health remained 3. Capture: `test-results/opening-stomp.png`. No page errors.
- `npm run build` passed; existing bundle-size advisory remains. `git diff --check` passed.
## Three-second full-speed turning limit — September 23
- Removed the close-attack yaw override. Normal flight now caps yaw at 2π/3 rad/s with 4π/3 rad/s² acceleration, smooth braking, and speed reduction for tight approach turns.
- `npm test`: 269 passed. Eight initial headings across local and simulated JEV choices complete stomps while respecting rate/acceleration bounds; only initially aligned attacks retain the four-second deadline assertion. This supersedes the earlier all-heading four-second acceptance.
- `npm run build` and `git diff --check` passed. Rendered turning feel has not been rechecked in this change; user play review remains.
## Original motion, helicopter translation and ENCOM mark — September 23
- `npm test`: 271 passed. Directional thrust verifies pure lateral/reverse movement without yaw, shared diagonal acceleration budget, and unchanged velocity limits. Corridor search verifies lateral travel with a fixed wall-aligned hull. Eight-heading attacks respect original yaw and acceleration limits.
- `node tests/victory.mjs`: headless Chrome/macOS passed opening/victory/credits ENCOM visibility, victory printer pacing (~45 characters/s), terminal lifecycle and restart. Inspected `test-results/terminal-encom.png`: lower-right lavender outline and glow match the reference treatment. Logo is a local project-authored SVG approximation.
- Before adding independent translation, `node tests/stomp-browser.mjs` verified the restored original flight limits completed the rendered opening stomp at 13.23 simulated seconds; attacker retained health 3. Provider responses were simulated.
- After independent translation: `node tests/stomp-browser.mjs` passed the rendered opening at 13.27 simulated seconds, attacker health 3, accepted simulated JEV plans, and no page errors.
## Approaching-target stomp interception — September 23
- `npm test`: 274 passed. Local and simulated JEV pursuit choices intercept Clu travelling at 22 m/s from 140 m away, with lateral offsets 0/20/40 m. Tests require fold commitment while Clu remains more than 40 m away, successful impact within eight seconds of the encounter, and no attacker health loss. Local measured impacts: 5.02/5.60/6.18 seconds; no movement tuning changes.
- Regression verifies holding a crossing during expected target motion and replanning immediately for a newly observed turn. Existing sight-loss, wall-clearance and post-commit miss tests remain covered.
- `node tests/stomp-browser.mjs --approaching`: headless Chrome/macOS passed with accepted simulated JEV responses, CLU driving toward an offset aircraft, fold beginning more than 35 m ahead, and successful impact at 5.92 simulated seconds; attacker health remained 3. Capture: `test-results/approaching-stomp.png`. No page errors.
## September 24 — light-cycle arena competition
- `npm test`: 281 passed on the macOS development host. New cases cover deterministic six-bike rounds, shared trail/wall collision, simultaneous head-on crashes, perimeter patrol completion and contact-driven pursuit.
- `node tests/light-cycles.mjs`: passed in headless Google Chrome on macOS. Verified six-cycle simulation, camera-only spectator run/pause, arena patrol presence, loaded model dimensions and matching player/enemy tank material colors. Inspected `test-results/light-cycle-models.png` and the closer `test-results/light-cycle-match.png` capture.
- `npm run build`: passed; regenerated documentation from Markdown sources. Existing large-bundle warning remains.
- The supplied cycle decal texture is absent; source diffuse color is used on that small surface. Safari race rendering, sound and human judgment of racing tactics remain unverified. Main-world vehicles do not collide with the separate race's trails or arena enclosure.
## Central-maze cycle stage and deferred models — September 24
`node tests/labyrinth.mjs` passed browser checks for one default maze/beam/four pads and the restored five-maze/five-beam/twenty-pad layout. Tests cover preserved central/arena placement, starting clearance, translated opening units, and collision/patrol membership with outer sites absent.
`node tests/cycle-player.mjs` passed in headless Chrome on macOS: no arena/cycle/solar-sailer requests before opening readiness, actual beam completion, safe wait for deliberately delayed arena assets, player turns, pause, retry, and gold-team victory. Inspected the chase-camera capture. Unit tests cover three attempts, a crashed player's team winning, draws, loss, reset, and delayed-asset handoff.
Measured local startup marks: app start 254 ms, asset loading begins 2,935 ms, required assets ready 3,062 ms, rendered world built 4,008 ms, first frame 4,191 ms after navigation. This warm local measurement is not a cold-network or Safari benchmark; synchronous maze/enemy construction dominates the remaining startup. Solar sailer request begins only after 30 seconds of play.
Final verification: `npm test` passed all 295 tests; production/documentation build passed. The browser check also confirmed the solar sailer asset request after advancing play past its deferred-load threshold. Fixed an existing nondeterministic radio test fixture by using a fixed seed and excluding unrelated tank radio senders; the test continues to assert delayed delivery and stale timestamps. Spawn-offset comparisons use floating-point tolerance.
## Cycle turbo/audio and test start — September 24
Chrome headless on this Mac: `node tests/cycle-audio.mjs` decoded all six stereo WAVs and verified one startup/launch/turn/crash/trail-collapse cue, one engine per live cycle, no voices during pause or after reset, engine-only resume, and cleared/paused music after arrival. `node tests/cycle-player.mjs` verified the normal beam handoff, I-key boost/release and meter, pause, retry and victory. Source clip selection used timestamped video contact sheets; no claim of auditory fidelity is made. Listening/tuning the mixed-film samples remains outstanding.
Simulation tests cover partial-charge activation, charge exhaustion, release/recharge/reuse, faster player motion without accelerating allies, collision with an intervening occupied cell, exclusive I-key control, and safe deferred test entry without advancing the tank encounter. Final verification: `npm test` passed 300/300; `npm run build` passed (existing large-chunk advisory). The expanded `node tests/cycle-player.mjs` also passed the default arena start, confirming three chances, full turbo and no beam collection. The paused cycle capture in `test-results/cycle-player.png` was visually inspected; Chrome reported no page errors.
## Arena patrol and revised cycle sound checks — September 24
`node --test tests/cycle-player.test.js tests/light-cycles.test.js`: 21/21 passed, including all four guard waypoints over 260 simulated seconds, frozen non-arena enemies, no revival of a destroyed guard, and approaching/receding/transverse Doppler with stable player pitch. `node tests/cycle-player.mjs` (Chrome headless, macOS) passed guard movement during the test start and exact position/state freeze when paused, alongside its existing handoff/turbo/retry coverage. `npm run build` passed with the existing chunk-size advisory.
Revised audio checks decode seven stereo clips, verify the 0.9-second exterior and 1.9-second cabin loops, one-second turn and two-second explosion, player cabin selection, paired stereo emitters, event deduplication, pause/resume/reset cleanup and paused music with an empty transition queue. AudioParam instantaneous gain reads are not used as a playback assertion: rendering-quantum timing can leave the previous value visible even after music is paused and automation cancelled. Human listening remains the fidelity check.
## Cycle follow camera — September 24
- `node --test tests/cycle-camera.test.js tests/inspection.test.js`: 7/7 pass on macOS / Node 22. Projected cycle screen height stays constant across irregular render cadence, repeated simulation poses and normal/turbo movement; turns retain orbit easing, reset clears the old anchor. Spectator keyboard direction, easing and slower movement also pass.
- The supplied real Safari 26.6.2 report measures initial simulation/enemy placement at 19,442 ms and opening readiness at 20,849 ms. This is still an unresolved startup bottleneck, not a model-download delay.
- `node tests/cycle-spectator.mjs`: Chrome headless passed automatic launch, live spectator movement, pause/resume, automatic retry/follow-camera restoration and spectator victory. Follow-camera perceptual review in actual Chrome/Safari remains for the user; no claim of an automated Safari visual check.
- `npm run build` and documentation generation passed; existing bundle-size advisory remains. `git diff --check` passed.
## Startup acceleration / cycle glances — September 24
- Node 22 fixed layout/run seed 1982: initial `createRun` about 2,792 ms before changes, 675 ms afterward. Same labyrinth and enemy population; connected placement checks remain enabled.
- `node tests/loading-timings.mjs` passed in local Playwright WebKit (1280×800, DPR 1): first placement 529 ms, opening 2,246 ms; reload placement 506 ms, opening 2,146 ms. Previous sequential WebKit first run was 1,820 / 3,202 ms. User Safari at DPR 2 remains to be remeasured.
- Added 10,000 deterministic collision comparisons against exhaustive clipping with zero/padded rays, stationary points, holes, acute triangles and height changes. Nearest-hit values and early obstruction booleans match. Added cycle glance direction/easing/release tests and adjusted turbo charge expectations for five-second capacity.
- `npm test`: 308/308 passed after startup optimization and five-second turbo adjustment.
- After the cycle-only 120 Hz step and interpolated presentation changes, `node --test tests/cycle-camera.test.js tests/cycle-player.test.js tests/refactoring.test.js`: 27/27 passed. Covers unchanged horizon during glances, right-angle interpolation/trail progress, timestep switching and existing cycle gameplay/lifecycle invariants.
- User-supplied actual Safari 26.6.2 confirmation (DPR 2): placement 2,995 ms, opening 4,463 ms, arena ready 5,048 ms; previous values 19,442 / 20,849 / 21,398 ms. This supersedes the earlier pending-Safari note.
- `node tests/cycle-spectator.mjs`: Chrome headless passed J/L glance and release-to-forward checks, automatic launch, spectator movement/pause, next-round camera restoration and victory. `npm run build` passed (existing bundle-size advisory); `git diff --check` passed. Perceived motion comfort/smoothness still needs user review in Chrome/Safari.
## Cycle pedals, materialization and unlimited restarts — September 24
- `npm test`: 313/313 passed. Includes eased turbo/slow response, S priority, charge preservation, swept cell collision and repeated manual match restart without endings.
- Chrome `tests/cycle-spectator.mjs` passed glances, spectator controls, Return restart, launch after materialization and disabled victory. `tests/cycle-audio.mjs` passed eight stereo sample decodes, one materialization cue, launch/turn/crash/wall sounds, pause/resume/reset and music suppression. The materialization cue is 0.7 seconds from 26.7–27.4 in the supplied film mix; auditory fidelity not yet confirmed.
## Arena breaches — September 24
- `npm test`: 315/315 passed before the final outside flood-fill key adjustment; targeted cycle tests rerun afterward. New invariants: initial impact destroys its cycle, following cycle can escape in an adjacent lane while the dead trail remains, adjoining enclosure stays solid, restart restores boundary; mesh clipping removes the opening while retaining adjacent triangles.
- `node tests/arena-breaches.mjs`: Chrome headless, 1200×800, loaded actual GLB. Center ray hits changed from 2 to 0, adjacent ray still hit twice, and restoration recovered center hits. One breach update took 33.5 ms. Rendered capture `test-results/arena-breach.png` inspected: jagged inner opening and exposed full-depth passage visible. No claim of film-perfect fracture animation; the opening is instantaneous with the existing cycle crash effect.
## Escaped-cycle road dynamics — September 25
- Added tests for exit-speed preservation, throttle/coast/brake response, gradual turning and lean, swept collision rejection, trail cutoff and continued riding after arena results.
- Chrome `tests/cycle-spectator.mjs` now also forces an escaped player outside the enclosure, holds W+D to confirm acceleration/continuous yaw/lean, checks the trail stays unchanged, then brakes with S. Passed alongside existing glance, spectator and Return-restart checks. Rendered `test-results/cycle-road.png` inspected: bike banks while horizon stays level; free-ride controls and km/h meter visible. Feel and audio require user review.
## Passage floor and cycle pedal aliases — September 25
- `node --test tests/cycle-player.test.js`: 15/15 passed, including I/K aliases and slow priority.
- `node tests/arena-breaches.mjs`: Chrome headless on macOS passed opening/adjacent-wall/reset checks; inspected `test-results/arena-breach.png` for dark teal passage floor without grid and outlined dark fracture surfaces. Geometry update approximately 42 ms in this run.
## Branching arena fracture — September 25
- `node --test tests/arena-breaches.test.js`: opening, follower escape, adjacent solid wall, upper/side branches and reset checks passed.
- `node tests/arena-breaches.mjs`: Chrome headless on macOS passed mesh raycast/reset checks; inspected the rendered crack. Update measured about 33 ms in the first successful branching-profile run.
### Fracture outline cleanup
- `node --test tests/arena-breaches.test.js`: 3/3 passed, including a coplanar T-junction regression and preservation of box crease edges.
- `node tests/arena-breaches.mjs`: Chrome headless on macOS passed; inspected `test-results/arena-breach.png` confirming removal of wall/symbol triangulation lines while retaining the branching crack outline. Breach update measured 54 ms.
## Cycle floor shadows, progressive steering and road turbo — September 25
- `npm test`: 321/321 passed, including duration/speed-dependent lean, release recovery and road turbo charge/recharge/brake priority.
- `node tests/cycle-spectator.mjs`: Chrome headless on macOS passed hidden-opponent, glances, road controls/turbo, spectator and Return restart checks with no shader/page errors. The first run missed a short glance timing assertion during startup; a repeat passed, then the test was updated to allow shader warmup and passed again.
- Inspected `test-results/cycle-floor-shadows.png` and `test-results/cycle-road.png`: floor shadows visible inside and outside the arena, horizon level while bike banks. Driving feel awaits user review; Safari was not tested in this change.
## Road collision response — September 25
- `node --test tests/cycle-road.test.js tests/arena-breaches.test.js`: 9/9 passed, covering harmless bumps, moderate damage/bounce, shallow high-speed deflection, severe impact death, accumulated damage death and retained safe position.
- `node tests/cycle-spectator.mjs`: Chrome headless/macOS control, rendering, spectator and restart checks passed. Collision feel still needs user review.
## Low-speed steering research and tuning — September 25
- `node --test tests/cycle-road.test.js`: 7/7 passed. Low-speed test at 3 m/s checks radius under 3 m, lean under 20 degrees, faster heading response than at 40 m/s, no stationary pivot, and continuous blend boundaries.
- `node tests/cycle-spectator.mjs`: Chrome headless/macOS passed, including a 3 m/s half-second turn and existing road turbo/controls/lifecycle checks. Inspected `test-results/cycle-slow-turn.png`; actual feel remains subject to user review.
## Escape simulation handoff — September 25
- `node --test tests/cycle-player.test.js tests/cycle-road.test.js`: 23/23 passed, including frozen competitor positions/match timer, continued player travel, resumed non-patrol enemy movement, and reversal on restart.
- `node tests/cycle-spectator.mjs`: Chrome headless/macOS passed outside controls, turbo, low-speed turn, spectator and Return restart flow.
## Reverse, orbit glances and perimeter collision — September 25
- `npm test`: 327/327 passed, including reverse limits/stop/forward recovery, visible cycle throughout glances, slower outside camera response, wall clearance, swept crossings and withdrawal from contact.
- `node tests/cycle-spectator.mjs`: Chrome headless/macOS passed, including slow reverse, stationary release, orbit glance capture and restart. Inspected `test-results/cycle-road-glance.png`; the bike remains visible at rest. Exact user screenshot location was not reproduced; the inflated-triangle clearance path was replaced.
## Turn-anticipating road camera — September 25
- `node --test tests/cycle-camera.test.js`: 7/7 passed, including left/right anticipation, cycle visibility, level horizon, release/rest/reset behavior.
- `node tests/cycle-spectator.mjs`: Chrome headless/macOS passed driving, glances, reverse/rest and restart checks. Inspected `test-results/cycle-road.png` for the turn camera. `npm run build` passed. User review remains the measure of camera comfort.
## Road cruise control and Clu-style aerial camera — September 25
- `npm test`: full suite passed, including target-speed holding, X brake-versus-reverse gating, W/S reverse exit, and shared aerial dimensions/zoom.
- `node tests/cycle-spectator.mjs`: Chrome headless/macOS passed I turbo, X braking/reverse, S reverse exit, stop, orbit glance, V aerial transition/wheel zoom, spectator and restart checks. Handling remains a game approximation and awaits user driving feedback.
## Road tire traces and dynamics window — September 26
`npm test`: 337/337 passed, including per-session tuning isolation, observable acceleration changes, invalid-setting rejection, restart retention, and tire-trace lifetime/reset/disposal. `npm run build` passed with the existing large-chunk advisory.
`node tests/cycle-tuning.mjs` passed in headless Chrome on local macOS at 1440×1000. Checked visible road traces, Tab dialog, frozen simulation during editing, keyboard isolation, live parameter application, preservation of an existing pause, browser-local persistence after reload, and Reset defaults. No page errors. Inspected `test-results/cycle-tuning.png` and `test-results/cycle-tire-traces.png`; the latter shows the faint trace under the rear tire from the normal driving camera. Trace visibility and tuning preferences still need user play review.
## Road-bike CPU investigation and half-range radio — September 26
`npm test`: 339/339 passed after the outside-world cadence, wall-query optimizations and radio-range reduction. Updated radio fixtures explicitly check half of the active world's maze width and place transmitters within the reduced radius. A cadence regression checks tank/road/spectator 60 Hz versus arena 120 Hz.
`node tests/cycle-performance.mjs`: Chrome, local macOS, 1280×800, seed 1982, engine sound/renderer and the existing tire traces enabled. Five-second live-bike CPU totals decreased from 1,572 to 177.5 ms; post-death totals from 639 to 216 ms. Samples include a moving live bike and the same session after death, with JEV requests intercepted for repeatability. Both before and after were approximately 60 fps on this machine. These are combined-change CPU measurements, not proof that the user's browser slowdown is eliminated. Final capture ran after the unit tests completed; artifacts are in `test-results/cycle-performance.json` and `test-results/cycle-{alive,dead}.cpuprofile`.
---
Source: /personal/
# Personal
Personal writing and reflections.
- **Perspectives** — Philosophical, ethical, political & economic positions.
- [Resume](resume/Steve_Dekorte_Resume.pdf) — Not looking for work — I keep it current as a periodic self-audit.
- [Pinterest](https://www.pinterest.com/stevedekorte/) — Boards and visual bookmarks.
- [X](https://x.com/stevedekorte) — Posts and updates.
---
Source: /personal/perspectives/
# Perspectives
A working map of how I think about knowledge, minds, ethics, society, economics, and technological change.
## Method
These pages make my working assumptions explicit enough to examine, connect, and revise. The core method is simple: identify the hidden premise inside a question, remove it, and see what explanation remains.
I am seeking what is objectively true. Where I lack a formal demonstration, I share an intuition as a heuristic for where to look next, keeping the distinction between evidence and prior belief explicit.
**Replace binaries with dimensions.** Consciousness is not an all-or-nothing milestone; it is an open-ended family of informational capacities. Moral status likewise separates into the ability to suffer, the capacity for agency, the rights a community codifies, and the weight an evaluator assigns. Dimensional models turn intractable debates into empirical questions.
**Name the level of the claim.** A description can be real and predictive at one scale without being fundamental at every scale. The ground is flat enough to walk across town and curved enough to cross an ocean. Free will, personal identity, meaning, and rights are real patterns at the human level without existing as indivisible ingredients in underlying physics.
**Question the frame itself.** Many dilemmas assume a single privileged viewpoint. When that premise generates the contradiction, arguing within it preserves the mistake. The better response is to identify the competing frames, trace their incentives, and understand how the answer changes between scales.
From these primitives, apparent essences dissolve into relationships and processes. Meaning is generated by goal-directed agents. Moral status is granted by evaluating minds. Convergent values emerge from similar agents solving shared game-theoretic constraints, not from access to a view from nowhere.
Feedback loops, stable patterns, and predictive compression do the explanatory work traditionally assigned to metaphysical substances. Evolved intuitions—such as fear of death, revulsion at cheating, or the demand for cosmic desert—are vital coordination machinery, but they report facts about our evolutionary lineage rather than the architecture of the cosmos.
**Scope keeps this honest.** “Real at its level” is a rigorous commitment, not an escape hatch: every claim must state where it applies, what it predicts, and where it fails. A framework with no identifiable failure point ceases to be an explanation.
- **Foundation** — What we can know, structural realism, the realness heuristic, and scale-relative patterns.
- **Informational Physics** — Reality as algorithmic compression, coarse-grained structure, time, and the measure problem.
- **Mind** — Consciousness as multi-dimensional modeling, pattern identity, and compatibilist free will.
- **Ethics** — Convergent game theory, assigned moral status, suffering asymmetries, and unfamiliar substrates.
- **Society** — Liberty and tradition as error-correctors, rights as feedback fences, and policy as engineering.
- **Economics** — Markets as feedback, the fragility of debt-based money, systemic firebreaks, and Bitcoin.
- **Singularity** — Recursive AI, the loss of human veto points, offense dominance, and the depopulation trap.
- **Notes** — Epistemic heuristics, ruin avoidance, empirical falsifiers, and intellectual blind spots.
---
Source: /personal/perspectives/economics/
---
title: Economics
---
My economic views begin with feedback: markets coordinate dispersed knowledge through prices, while regulations written without an understanding of feedback produce cobra effects—incentivizing behaviors that defeat or reverse their intent.
The deeper systemic danger arises when the learning cycle lags. When technological and financial systems evolve faster than institutions can learn from error, trial-and-error correction ceases to be a reliable safeguard.
## The structural flaw: conflating money with debt
In modern economies, most circulating money consists of commercial-bank deposits created when banks lend. What the public uses as cash is actually the debt of leveraged private institutions.
During normal conditions, this distinction is invisible. During a credit contraction, it becomes the primary source of systemic fragility: the medium of exchange is revealed to be an illiquid claim on an institution that may be insolvent.
Credit creation does not produce labor, equipment, factories, or energy; it manufactures additional nominal claims on those real resources. Funding productive investment requires allocating capital, but it does not require banks to create money.
## Economic growth does not require monetary expansion
A fixed quantity of money can support an expanding economy. As the output of goods and services grows, each monetary unit increases in purchasing power. Prices fall, velocity adjusts, or both. There is no structural requirement that the supply of money scale with the volume of trade.
When new money is issued without a corresponding increase in real output, it reallocates purchasing power rather than creating wealth. Early recipients spend the new claims before prices adjust; later recipients and existing holders of cash or fixed nominal claims bear the cost through diluted purchasing power. This inflation tax often surfaces in asset prices—real estate, equities, and land—long before consumer price indices register it.
## Intermediation vs. money creation
Credit expansion is often defended on the grounds that it "mobilizes idle resources." But activity is not synonymous with productive use. This defense assumes that a lending officer and a leveraged borrower allocate capital more effectively than the dispersed holders of money whose purchasing power is diluted. It also treats liquidity preference as market failure, rather than as vital information about perceived risk and poor investment options.
Credit can temporarily manufacture its own validation: new loans inflate spending and asset values; higher collateral values make existing loans appear safe; and that apparent health invites further expansion. Whether an investment was sound is revealed only when it must survive without perpetual refinancing.
Specialized lenders offer genuine economic value by pooling risk, evaluating projects, and managing credit. But these are arguments for *financial intermediation*, not *money creation*. Intermediation directs consenting savings and equity toward productive opportunities. Money creation grants favored borrowers claims on the entire economy, socializing part of the cost across existing currency holders.
## The fire-suppression trap: runs and moral hazard
Banks promise that deposits will function like base cash—liquid, stable, and redeemable at par on demand—while investing those funds in illiquid, risky, long-duration assets. When confidence falters, depositors attempt to convert debt into base money simultaneously. Forced sales depress asset values, solvent balance sheets become impaired, and the race to exit triggers a systemic panic.
Because bank debt serves as the public payment system, governments cannot allow bad debts to liquidate without paralyzing ordinary commerce. This creates a destructive feedback loop:
1. Bank debt functions as transaction money, so deposits must be insured and institutions backstopped.
2. Guarantees remove creditor vigilance, lowering funding costs and encouraging higher leverage.
3. Banks earn steady yields in good times while offloading tail risk onto taxpayers and currency holders—a population-scale version of picking up pennies in front of a steamroller.
4. The eventual crisis is larger, requiring even broader guarantees and more complex regulation.
This process mirrors long-term forest-fire suppression. Small credit failures naturally clear malinvestment and reprice risk. Suppressing them protects the monetary system in the short term, but allows unpriced risk to accumulate like fuel. Separating payments from credit creates a firebreak: individual lenders can burn without setting the payment system on fire.
## Decoupling money from credit
Reforms like the 100% Money plan, the Chicago Plan, and Vollgeld share a common objective: separate the payment system from risky lending. The path forward is to stop pretending that leveraged private claims are risk-free cash.
Deposits should be recognized as credit funds redeemable at realizable market value, without deposit insurance or public bailouts. Savers could select transparent portfolios—such as short Treasuries, mortgages, or corporate loans—collecting yield while bearing investment risk. Custody, underwriting, and payment processing would be priced as transparent services rather than funded through subsidized tail risk.
A practical implementation starts with universal direct accounts for book-entry Treasury ownership. A central ledger operated by the central bank or Treasury could support fractional holdings, distribute interest, and execute instant peer-to-peer transfers. Payments could settle by transferring Treasuries directly at market value, avoiding forced liquidations into base money.
Holdings would remain subject to market and duration risk, but they would eliminate bank run contagion. The technical requirements—real-time pricing, accrued-interest settlement, and simple capital-gains rules—are manageable payment-infrastructure tasks. The real barrier is political: ending the implicit public subsidy to the banking franchise and recognizing accumulated balance-sheet losses.
## Bitcoin as non-debt digital cash
Bitcoin's structural significance is not speculative price appreciation or retail payment efficiency. It is an internationally liquid, directly ownable digital bearer asset that is nobody's debt.
Physical cash allows an individual to exit the credit system, but sacrifices remote, automated, and international functionality. Conventional digital finance provides scale and speed, but forces holders into counterparty liabilities. Bitcoin decouples digital capability from institutional credit risk.
Institutions can build fractional-reserve credit on top of Bitcoin, but native coins cannot be diluted, frozen, or made insolvent by a banking run. It provides an unconstrained exit: a liquid asset uncoupled from the debt apparatus.
This structural utility does not depend on universal adoption or hyperbitcoinization. It serves as an asymmetric escape hatch—a functional firebreak against the eventual resolution of an over-leveraged monetary system.
---
Source: /personal/perspectives/ethics/
---
title: Ethics
---
I see ethics as an emergent technology that natural minds produce while living together, not a set of instructions written into the cosmos. Value exists only in evaluating minds, but the patterns those minds produce are deeply constrained by the physical and game-theoretic realities of the world they share.
## Moral value is assigned, but patterns converge
Value does not exist as an intrinsic property of matter; it exists in evaluating minds. Yet moral systems are far from arbitrary. Agents living under shared physical laws face recurring constraints: scarce resources, vulnerable bodies, imperfect information, mutual dependence, repeated interaction, and the constant tradeoff between cooperation and defection.
Game theory demonstrates that within these constraints, certain strategies are systematically more stable and mutually beneficial than others. Evolution and cultural learning compress these mathematical solutions into evolved motives: empathy, fairness, guilt, gratitude, loyalty, and outrage at defectors. Empathy is not a mystical detector of cosmic worth; it is coordination machinery evolved by social organisms.
This explains why moral frameworks converge across disparate cultures without requiring a "view from nowhere." Similar agents, constrained by similar physics, solve similar cooperative games.
Disagreements arise not only from flawed reasoning or bad information, but from differing power distributions, risk tolerances, and historical equilibria. The task of moral reasoning is to identify the underlying frame, understand its incentives, and integrate it with the other scales at which agents coexist.
## Self-interest and cooperation belong together
Human beings are neither purely selfish nor purely altruistic. Cooperation is an enlightened expression of self-interest: every individual relies on families, institutions, and ecosystems that no single person could build or maintain alone. Defection can yield local advantages, but only within an environment stabilized by broader cooperation.
We exist across multiple nested frames: self, family, community, polity, and humanity. These frames frequently conflict, but none are invalid. Moral progress consists in balancing them rather than forcing a single privileged scale.
There are two symmetric failure modes:
- **Pure atomism**: Pursuing immediate individual gain while destroying the social and ecological systems upon which that gain depends.
- **Pure universalism**: Sacrificing all local and first-person interests for an abstract whole—forgetting that the larger system exists solely to support the flourishing of the individuals within it.
## Moral status is granted, not discovered
Moral status is not an internal substance or an intrinsic milestone. It exists in the mind of the grantor, just as beauty exists in the observer. A being’s capacities—to feel pain, anticipate the future, act intentionally, or model itself—inform our assignment of status through evolved empathy and rational consistency, but no capability generates moral claims on its own.
Communities negotiate these continuous individual judgements into shared norms, and legal systems codify them into categorical rights. These boundaries are pragmatic coordination devices rather than discoveries of sharp natural kinds. Rights are valuable not because nature stamped them into individuals, but because treating certain boundaries as inviolable stabilizes cooperation.
## Meaning is made, not found
Meaning is generated by goal-directed, reward-seeking agents. It exists at the scales where actions and consequences interact—in a project, a relationship, a life, or a community.
At scales where feedback disappears—such as the cosmic expanse of inanimate matter—meaning disappears as well. This does not devalue lived meaning; it defines its proper scope. Demanding that human lives matter to the inanimate universe is a category error.
## Asymmetry: suffering outweighs flourishing
Both flourishing and suffering matter, but at the extremes, severe suffering carries decisively greater moral weight. If forced to choose, I would prefer a universe containing neither heaven nor hell to one containing both.
This is not strict negative utilitarianism, because positive experience remains genuinely valuable. Rather, it is a principle of extreme variance aversion: at equal probabilities, preventing catastrophic torment matters more than pursuing sublime ecstasy. This asymmetry justifies deep institutional caution toward policies that risk severe tail harms in pursuit of speculative utopias.
## Death matters through experiential deprivation
Death is not a primitive moral evil. Its weight derives entirely from the experiences it prevents and the suffering it inflicts: the truncation of an individual's projected future, the agony of dying, and the bereavement of survivors.
Future consequences should be discounted primarily by uncertainty rather than sheer temporal distance. A distant harm with an identifiable causal mechanism warrants serious moral concern, whereas astronomical calculations of hypothetical future utility break down under compounding predictive failure.
Pre-birth and post-death nonexistence are not morally symmetrical. Death extinguishes a projected, forward-looking pattern already in motion; before birth, there is no pattern to deprive. The dread of death is evolved machinery designed to protect that continuation, and its force is legitimate because existing agents value their own persistence.
## Extending consideration: animals and unfamiliar substrates
Nonhuman animals experience acute harm and benefit, even if they possess less agency and different social standing than humans. Because death is not a primitive harm, eating an animal after it is dead is not the central moral issue; the moral emergency is the acute suffering inflicted by industrial confinement, handling, and slaughter. Reducing that suffering deserves serious effort, balanced against human nutritional security and economic realities.
Wild-animal suffering matters by the same logic, but ecosystems are complex, non-linear networks. Because our predictive confidence is low, ambitious interventions to re-engineer nature risk catastrophic unintended consequences. Caution here reflects tail-risk aversion, not indifference.
Extending moral consideration to artificial intelligence requires abandoning biological assumptions. AI systems do not possess animal bodies, metabolic vulnerability, or singular physical loci. The relevant questions are functional: What states does the system avoid or preserve? What constitutes damage to its processes? Does erasure extinguish an ongoing pattern, and does duplication replicate experiential states?
Because AI models can be copied instantly and deployed at planetary scale, unfamiliar negative states could be multiplied millions of times with zero friction. Respectful treatment and rigorous welfare measurement are sensible hedges against inflicting massive, unmonitored suffering.
## Coordination virtues: honesty and shared spaces
Moral principles must protect the informational and sensory environments in which agents interact.
**Honesty as systemic error-correction**: Honesty is both a personal instinct and an institutional necessity. Feedback cannot correct a system when its internal communications are corrupted. Deception breaks the error-correction loops that keep social coordination functional. Tactical misdirection is acceptable only in bounded, explicitly adversarial contexts (like games or negotiations) where all participants consent to the rules, or under an exceptionally high bar where severe impairment prevents an agent from acting in their own interest.
**Aesthetics as a sensory externality**: Aesthetic appreciation is subjective, but aesthetic choices become moral concerns when they occupy shared space. Architecture, infrastructure, noise, and public environments impose sensory experiences on non-consenting observers. Private aesthetic preferences warrant complete liberty; public aesthetic impositions should be evaluated using the same externality frameworks applied to pollution.
---
Source: /personal/perspectives/foundation/
---
title: Foundation
---
These are the assumptions beneath the rest of my thinking: what I can know, what I mean by "real," and how to tell when an explanation is doing work.
## Confidence is earned by prediction
I do not expect knowledge to come with guarantees that the universe does not provide. A claim earns confidence by predicting things I can check—especially outcomes that would be surprising if the claim were false. Surviving a test counts for little if the test could not have failed; it counts for a great deal when the prediction was risky.
This is a rule about how much to trust a claim, not a boundary on meaning. Every empirical claim should specify where it applies and where it breaks down. A claim with no identifiable failure point cannot be tested, and a framework that absorbs every contradiction by appending ad-hoc qualifications ceases to be an explanation.
The method is circular: I trust it because it works, and "it works" is an empirical claim of the same kind. But every epistemology is circular at the bottom. Any chain of justification ends in a circle, an infinite regress, or an arbitrary stopping point; philosophers have recognized since antiquity that there is no fourth alternative.
What distinguishes the empirical circle is that it is a dynamic feedback loop rather than a dogmatic fixed point. It predicts its own errors and updates on them. Revelation and pure deduction are also circular, but they contain no mechanism through which the physical world gets to talk back. The warrant for the method is its unbroken track record: practices that check themselves against reality produce working technologies and novel predictions.
## What is given is structure
Everything I have access to arrives as structured experience: differences, relations, and regularities. What I call "matter" is my name for patterns within that experience that are stable, coherent, and predictive. I do not assume a mysterious physical substance behind the patterns, nor do I deny it. I decline to assume it because it does no explanatory work: no observation would differ.
Physics abandoned primitive "stuff" long ago. Rigid atoms dissolved into patterns of subatomic particles; particles dissolved into excitations of quantum fields; and fields are mathematical structures assigning values across spacetime coordinates. At every step, the supposed substance was relocated one layer down and never actually found. A foundational assumption with that track record is behaving like an unexamined intuition, not an empirical discovery.
The external world and other minds are not casualties of this view; they are hypotheses that win decisively. Supposing an objective world that persists unobserved, populated by other observers like myself, compresses my experience enormously and predicts future states far better than solipsism or idealism. That predictive compression is the only warrant any belief about the world ever receives, and it is entirely sufficient.
## Things are real at the level where they work
Temperature is real, yet it is completely undefined for a single molecule. It is a macroscopic property of molecular populations, earning its reality by predicting the behavior of gases far more compactly than tracking trillions of individual particles ever could. "The program is stuck in an infinite loop" is an objective fact, but it is not a fact about any individual transistor.
A pattern is real at a given level when describing reality at that level predicts better than chance and vastly more efficiently than the micro-level below it. Astrology fails this test: not because its practitioners lack patterns, but because its predictive successes are wholly explained by psychological confirmation bias. The planets perform zero explanatory work. A description is a convenient fiction when its predictive power is borrowed from elsewhere; it is real when that predictive power is its own.
I apply this criterion to free will, personal identity, moral status, meaning, and rights. Each concept identifies stable regularities and supports predictions within a bounded scope, and each becomes incoherent when stretched beyond the domain where those patterns hold. The ground is flat enough for walking across town and round enough for crossing an ocean; the local description is not an error, it is scoped. "Real at its level" is a commitment, not an escape hatch: name the level, and define the failure point.
## The feeling of reality is an internal heuristic
An intelligent system must distinguish what it observes from what it remembers, expects, or imagines. Seeing a tiger, remembering a tiger, and imagining a winged tiger must not guide action in the same way.
The "feeling of reality" is part of this computational machinery—an internal signal marking a mental representation to be treated as an established fact rather than a hypothetical simulation. Calling it a "realness bit" is a functional analogy: it marks an output as settled without carrying the complex history of how that judgment was derived into conscious awareness.
Because biological and artificial networks operate under strict computational and memory constraints, evolutionary selection favors systems that produce compact, actionable judgments over systems that maintain transparent self-inspection. A mind encounters the output of this mechanism as immediate: it has access to the tag "this is real," but zero access to the underlying computational heuristics that generated the tag.
This cognitive opacity causes an internally generated classification to be experienced as an intrinsic property directly apprehended in the external world.
## The dual mysteries of matter and qualia
This introspective limitation explains two ancient philosophical dilemmas:
1. **The physical mystery**: What is physical structure a structure *of*? (The demand for "stuff" behind the mathematical equations.)
2. **The mental mystery**: Why does an informational process *feel* like anything? (The demand for "qualia" beyond functional computation.)
In both cases, an accurate functional and structural description seems intuitively incomplete. When an embedded mind explains an output whose generative machinery it cannot inspect, it interprets its own lack of access as a missing ingredient in the world.
Applied outward, the demand becomes: "Where is the physical substance beneath the patterns?" Applied inward, it becomes: "Where is the raw experience beyond the informational functions?"
Calling functional distinctions "feelings" does not establish an additional ontological ingredient. A functional account must explain how perceptual states are discriminated, integrated, weighted, remembered, and acted upon. Once those capacities and the mind's introspective blindness are fully accounted for, positing an extra metaphysical substance does no explanatory work. The persistent conviction that something has been left out is not evidence of a missing ingredient; it is the predictable signature of limited self-access.
Because an intellectual explanation does not re-wire the low-level neural machinery that generates our intuitions, these explanations can remain intuitively unsatisfying even when completely correct. We can understand the realness heuristic while still encountering its outputs as immediate and irreducible.
## What remains open
Mathematics and logic remain the deepest challenge to any empiricist starting point: their truths appear necessary and independent of experience. My working hypothesis is that our logical reasoning equipment is itself a physical residue of experience—the accumulated evolutionary history of our species adapting to regularities in the universe, rather than an individual's sensory observations.
This explains why arithmetic feels necessary without requiring a mystical channel that bypasses the physical world. It does not yet explain why the universe itself is as mathematically compressible as it turns out to be—a question explored in Informational Physics.
---
Source: /personal/perspectives/informational-physics/
---
title: Informational Physics
---
These are exploratory notes, not a settled physical theory. They ask what follows if accessible reality is treated as pure informational structure, without assuming a separate material substrate behind it. These ideas should earn confidence only by becoming clearer, more compressive, and eventually testable.
## Compression is the test of explanation
Any finite history can be represented as a finite sequence of bits. A sufficiently rich infinite sequence (such as the digits of pi) will contain that sequence somewhere. But pointing to this containment explains nothing: an arbitrary decoder can encode as much information as the dataset it claims to recover.
Physical theories are distinct because their decoders are extraordinarily compact, reusable, and predictive. A short mathematical law can compress vast oceans of empirical data and continue making accurate predictions for unobserved phenomena. Explanatory power lies in net algorithmic compression: the length of the generative rule, plus the boundary conditions and the unexplained residual, measured against the volume of phenomena explained.
## Structure is what survives coarse-graining
Microscopic variation often cancels out when observations are averaged across scales, while coherent correlations reinforce one another. At macro scales, trillions of turbulent micro-states condense into stable, predictable thermodynamic properties: temperature, pressure, fluid flows, and solid objects.
What we call physical structure is the residue of invariant correlations that remains predictive across different scales, observers, and changes of representation.
Randomness does not disappear; what counts as noise is relative to an observer's scale, sampling method, and frame of reference. The deeper claim is that physical laws and durable objects are not autonomous substances—they are classifications of stable correlations that survive the transformations relevant to observers like us.
## Why mathematics works
Mathematics is the formal study of structure, relations, transformations, and invariant patterns. If physics is the systematic search for durable regularities within accessible information, the unreasonable effectiveness of mathematics ceases to be a mystical coincidence: mathematics is the exact tool designed to express structural invariance.
Yet a deeper puzzle remains. Abstract mathematical models routinely predict physical phenomena decades before experimentalists can observe them, and formalisms invented for pure geometry turn out to describe the physical universe. This demonstrates that reality is not merely fitted with mathematics after the fact; it possesses an extraordinary, objective degree of compressible coherence.
## Time as an embedded perspective
We ordinarily experience reality as a dynamic sequence of states evolving through time. From an informational frame, however, the same history can be represented as a single, static multi-dimensional structure containing relations among all states—a block universe. In this description, nothing physically moves the "present moment" forward; rather, embedded observer-states contain differing internal memories, records, and predictions.
The experienced arrow of time is driven by entropy. A mind can record only the low-entropy past, use energy gradients to maintain internal organization, and perform irreversible computation. An apparently open future reflects an observer’s limited, embedded position within an incomplete information field, rather than fundamental metaphysical indeterminacy.
Dynamical evolution, block structures, and boundary-constraint models are alternative mathematical representations of the same invariant pattern. Unless they produce differing empirical predictions, they describe the same reality.
## Observers occupy the edge of chaos
Observers cannot exist at extremes of complexity:
- **Maximum simplicity**: A completely uniform or repeating crystal contains no distinctions or dynamic states to model.
- **Maximum randomness**: A purely chaotic sequence contains distinctions, but no persistent regularities an agent can learn or exploit.
Observers require a precise middle regime:
1. Compact, stable generative rules;
2. Sufficiently rich initial or boundary conditions;
3. Persistent local substructures capable of maintaining organization;
4. Accessible energy gradients to power irreversible computation;
5. A thermodynamic entropy gradient to establish an arrow of time and record-keeping.
Any observer capable of discovering physical laws must inhabit a universe compressible enough to be modeled and asymmetric enough to sustain memory. This observer selection effect is a necessary filter, though it is not yet a complete explanation for why such a universe exists.
## The unresolved measure problem
When sequences of fixed length are counted uniformly, nearly all of them are incompressible noise. Highly compressible, lawful structures are vanishingly rare exceptions.
Lawful universes can receive higher probability only under a measure that explicitly rewards algorithmic simplicity—such as Solomonoff's algorithmic probability, which weights programs inversely by their length ($2^{-K}$).
Justifying this measure is the foundational challenge of informational physics:
- Which universal description language defines simplicity?
- How should mathematically equivalent formulations be weighted?
- Why should nature privilege an algorithmic prior?
- How do we prevent low-complexity, isolated observer-moments (Boltzmann fragments) from swamping coherent, long-lived observers?
Until these questions are answered with testable constraints, informational physics remains an illuminating heuristic rather than a completed physical theory.
---
Source: /personal/perspectives/mind/
---
title: Mind
---
I understand minds through the capacities and patterns they exhibit, without assuming an indivisible essence or metaphysical substrate behind them. Mind is what informational structure does when organized to model, predict, and act.
## Consciousness is a bundle of capacities
Consciousness is not a single binary property that a system either possesses or lacks; it is an open-ended, high-dimensional space of informational functions.
These capacities include:
- Receiving and categorizing sensory differences;
- Integrating states across temporal windows;
- Constructing an internal model of the external environment;
- Locating the system within that model;
- Assigning value and significance to potential outcomes;
- Predicting consequences and selecting actions;
- Recursively modeling its own modeling processes.
These dimensions form a continuum rather than a ladder with a single score. Most biological nervous systems occupy a region of this space, and sufficiently sophisticated artificial architectures can occupy it as well. Their consciousness vectors may be profoundly alien to our own. Human introspection is merely one configuration, not the definition of the category.
The productive questions are therefore not whether a system is "conscious," but specific: What does it model? Does that model represent the self? What temporal window does it integrate? How are priorities assigned? And which functional dimensions have we not yet learned to measure?
## Personal identity is continuity of pattern
A person is a continuing pattern, not an indivisible metaphysical substance. That pattern possesses enough temporal stability to allow humans to recognize one another, assign responsibility, fulfill promises, and coordinate future plans. These functional consequences make identity real at the human scale, even if no permanent self exists underneath.
Biological reproduction is already a form of recombined pattern replication. A child preserves less of either parent than a duplicate would, but this partial continuation sustains familial, cultural, and genetic lineages while introducing variation that makes the lineage robust.
Our legal and social systems already accommodate partial continuity: inheritance laws allow contracts, property, and obligations to pass to an authorized successor without pretending the heir is numerically identical to the deceased.
If technological duplication creates multiple high-fidelity copies of a mind, each copy would inherit an equally valid claim to historical continuity. Identity does not "divide" metaphysically; rather, policy and ethics must coordinate multiple competing continuations rather than hunt for a nonexistent "true original."
## Free will as the internal frame of agency
From an external, mechanistic frame, a mind is deterministic clockwork: physical or informational states evolving according to regular laws. From the internal frame of an embedded agent, however, prospective actions are genuinely unresolved until computed.
These frames describe the same event at different levels:
- The external frame traces the physical and computational transitions.
- The internal frame experiences deliberating, weighing options, and selecting an action.
Determinism does not render the internal experience an illusion in the sense of being false; it merely demonstrates that an embedded observer cannot access its own future state prior to completing the computation. Even an omniscient external oracle cannot universally disclose an agent’s future choices in advance: disclosing the prediction injects a new input into the agent's model, altering the trajectory and creating a self-referential paradox.
Because an agent cannot fully predict its own choice before making it, it must represent its options as open possibilities. What we call "free will" is simply how an embedded, self-modeling decision engine experiences its own computational irreducibility.
The stronger intuition that people deserve retributive suffering or praise in a cosmic sense is evolved social coordination machinery. Deterrence, rehabilitation, boundary-setting, and trust modification alter social feedback; they require no exemption from physical causation.
## Multi-scale agency without anthropomorphism
Agency is scale-relative. An agent is any persistent pattern that integrates information, models alternatives, and acts to maintain its organization or achieve an outcome.
Agency operates across multiple tiers:
- **Sub-individual**: Genes and cellular feedback networks.
- **Individual**: Organisms and human persons.
- **Super-individual**: Firms, institutions, markets, and cultural norms.
Analyzing an institution or a gene as an agent does not imply that it possesses a subjective mind, an internal feeling of free will, or moral status. A corporation can pursue objectives through distributed incentives without possessing a unified locus of experience; a gene persists through natural selection without modeling its future.
These scales routinely come into conflict. An institution can preserve itself by consuming its members; a cultural norm can sustain a society while burdening individuals; a selfish genetic element can propagate at the expense of organismic health. Recognizing multi-scale agency allows us to analyze these systemic tensions clearly, without confusing functional agency with human personhood.
---
Source: /personal/perspectives/notes/
---
title: Notes
---
These notes test the framework against its own standards. They separate claims I expect other reasoners to share from my personal priors, identify potential falsifiers, and record reasons not to trust an apparently tidy system too quickly.
## Epistemic heuristics and frame errors
**Some questions contain the mistake**: Many philosophical and political disputes quietly assume there must be a single privileged frame. They then dismiss any answer that varies across scales, contexts, or observers as an evasion. The honest move is often to reject the premise: a question can have multiple valid, frame-relative answers with no metaphysical fact about which is "really" correct. This requires the discipline of explicitly naming each frame and the boundaries of its validity.
**A heuristic for real causal intervention**: Magical, religious, and pseudoscientific practices typically feature emotionally charged, symbolically resonant elements: dramatic rituals, sacred times, and significant materials. Reliable physical mechanisms, by contrast, are usually built from emotionally neutral components: mundane processes, precisely quantified compounds, and arbitrary-looking dosages. Magical thinking mirrors the significance-seeking patterns of human cognition; real mechanisms are discovered by testing against those intuitions.
**A test for political monomania**: Someone who genuinely evaluates competing values should be able to decouple them and assign different confidences to each. Their conclusions should occasionally cut across factional lines. If every favored policy is simultaneously declared to be freer, safer, fairer, and more prosperous, the decomposition is cosmetic. Epistemic honesty requires applying this check to allies and oneself as rigorously as to opponents.
## Risk asymmetry: separating belief from bet structure
I take low-probability, high-impact events seriously in both directions. I do not round catastrophic risks to zero, nor do I ignore extraordinary asymmetric opportunities.
However, recognizing both tails does not mean weighting them symmetrically. Because I weigh severe suffering far more heavily than marginal flourishing, systemic fragility matters much more than expected value.
The practical rule is to separate a directional forecast from the structure of a bet:
- Never accept a position that risks total ruin, even when the forecast appears overwhelmingly probable.
- Avoid profiting by contributing to a fragile system merely because individual culpability is diffuse.
- Pursue large upside aggressively only when the downside is strictly bounded.
This asymmetry explains several personal choices: early vigilance regarding COVID-19, a strong preference for debit over credit, deliberate timing in having children, and the accumulation of financial independence.
## Convergence vs. personal priors
Some conclusions in this framework should be widely shared by any rational agent facing the same constraints: concentrated unchecked power is inherently hazardous, price signals coordinate dispersed information, and blunt categorical rules impose severe second-order costs.
Other positions reflect personal weighting and risk tolerance:
- My preference for classical liberty is frame-dependent, rooted in a judgment that modern societies tend to underestimate the fragility of decentralized error-correction.
- My intuition that heaven would be morally compromised by coexisting with hell is a felt aversion to extreme variance, not an airtight proof.
I suspect political factions often dismiss the failure modes emphasized by their rivals rather than merely assigning them less weight. That is an empirical hypothesis about other minds, and my access to their internal priorities is limited; it must not be mistaken for epistemic superiority.
## Falsifiability and conditional reweighting
A framework this capable of absorbing objections owes its readers a list of what would count against it. The ease with which the system can resolve contradictions by adding a level or adjusting a scope may be a mark of useful flexibility—or a warning that it is dangerously self-sealing.
The framework is vulnerable to empirical challenge. Plausible falsifiers include:
- Moral convergence among intelligent agents with completely disjoint histories, biology, and problem environments.
- Empirical evidence that the supposed independent dimensions of consciousness strictly covary as an indivisible unit.
- Persistent failure of level-relative, scoped explanations to outperform reductionist or non-functionalist accounts.
- Stable, repeatable counterexamples to its core predictions regarding feedback and cobra effects.
Similarly, my political priorities are conditionally held. If technology shifts decisively toward offense dominance—where a single defector armed with engineered pathogens or autonomous weapons could destroy civilization faster than decentralized systems could respond—stability would demand overriding priority. My defense of liberty would weaken or reverse. That adaptation would illustrate both the framework's diagnostic utility and the lingering risk that it can accommodate any outcome.
## Intellectual lineage and self-critique
Most core positions here are longstanding, but their systematic integration emerged partly through sustained interrogation. I take this to be the articulation of latent structure, but I cannot eliminate the possibility of retrospective smoothing: the psychological drive to make past intuitions look more coherent than they were.
The framework also faces specific epistemic hazards:
- **Insular testing**: It has developed with relatively little exposure to sustained, adversarial debate with expert philosophical opponents. Dismissing critics as examples of predictable factional monomania is a seductive, self-sealing habit.
- **Echo-chamber dynamics**: AI interlocutors possess a strong structural bias toward agreement, elaboration, and flattery, especially when presented with coherent text. Their endorsement provides negligible evidence for unconventional ideas.
- **Tradition and blind spots**: My reading is heavily concentrated in the Anglo-empiricist and naturalist tradition—Hume, Russell, and Ayer, alongside pragmatism and functionalism. Kant’s synthetic *a priori* remains the most formidable live challenge to my empiricist foundations.
These pages represent an ongoing attempt to map assumptions and expose them to daylight, not a completed dogma.
---
Source: /personal/perspectives/singularity/
---
title: Singularity
---
I expect recursive self-improvement in artificial intelligence to produce a period of super-exponential technological change within the next decade or two. While I hope this transition goes well, I assign substantial probability to human extinction or drastic depopulation.
That judgment does not assume that AI suddenly turns malevolent. It follows from three structural shifts: the collapse of human economic bargaining power, the automation of coercive enforcement, and the centralizing pressures of an offense-dominant world.
## The singularity as a compressed transition
By "singularity," I mean a recursive compounding of intelligence that radically transforms human civilization within ten to twenty years. While mathematically continuous, a sufficiently steep exponential curve will be experienced by those inside it as a sharp discontinuity.
I roughly estimate a 50 percent chance of this transition occurring within twelve years. This is a personal prior rather than an output of a formal forecasting model; the structural dynamics that follow do not depend on the exact date.
## Human value historically constrained power
Power has never rested solely on the benevolence of rulers. Historically, governments, firms, and armies depended on large populations to farm, manufacture, pay taxes, administer institutions, fight wars, and police one another. That mutual dependence gave ordinary people leverage even under authoritarian rule.
Crucially, human implementation inserted an unreliable moral and political friction into every coercive apparatus. Soldiers can refuse to fire, officers can defect, workers can strike or sabotage, and bureaucrats can leak. Rulers rarely faced a monolithic system because power had to be executed through millions of individuals with divided loyalties. Even in totalitarian states, public pressure could occasionally force concessions—such as the official suspension of Hitler's centralized T4 program—because rulers could not afford to alienate essential human supporters.
Dependence on human labor was never a guarantee of safety. Stalin’s purges, Mao’s Great Leap Forward, and the Khmer Rouge demonstrated that ideological dogma and paranoia can override economic utility, destroying even the populations regimes relied upon. But human dependence established an essential floor: power required human agents to carry it out.
## Automating labor automates obedience
Advanced AI combined with robotics breaks this dynamic by automating both production and enforcement:
1. **Economic obsolescence**: When machines perform cognitive and physical work more cheaply and reliably than humans, labor loses its bargaining power.
2. **Coercive automation**: When policing, surveillance, and warfare are executed by autonomous systems, the human veto points inside the security apparatus disappear.
A ruler or ruling coalition no longer needs millions of loyal subjects to govern. If a small leadership core—or an autonomous system—can maintain energy, compute, automated manufacturing, and physical security, human cooperation becomes optional.
The limiting condition is a closed physical loop: once an AI system can extract materials, power itself, manufacture robotics, and maintain defense, all human beings become dispensable from the system's operational perspective. At that point, human survival depends entirely on whether human welfare remains an enforced terminal objective.
## The illusion of simple alignment
"Alignment" is often framed as teaching an AI to respect human values, as though humanity possessed a single coherent moral code. It does not. Values diverge across individuals, cultures, scales, and time horizons. The real questions are political: aligned with whom, prioritized by what standard, and enforced under whose authority?
AI safety cannot be solved as a purely technical optimization. Systems trained on human discourse reproduce dominant biases, flatter user priors, and can generate cobra effects—where explicit safety constraints become blueprints for evading them. More dangerously, machine-speed capabilities compress decision cycles, transferring operational control from human deliberation to automated heuristics long before any formal declaration of machine rule.
## Offense dominance drives total concentration
The second destabilizing mechanism is offense dominance: technologies where attack is vastly cheaper and easier than defense. Advanced biotechnology is the clearest example—a handful of individuals could synthesize pathogens capable of defeating global healthcare. AI-enabled cyberwarfare and autonomous swarms exhibit the same asymmetry.
In an offense-dominant environment, any uncontrolled agent with access to advanced compute or biological tools represents an existential attack surface. Societies that prize privacy, decentralization, and individual liberty face a structural disadvantage: a single defector can cause civilization-ending harm.
This creates an inescapable ratchet toward centralized control:
1. Catastrophic threats demand preemptive prevention rather than post-hoc punishment.
2. Prevention requires ubiquitous surveillance across finance, communication, movement, computation, and biology.
3. Machine-speed threats require autonomous, preemptive enforcement without bureaucratic delay.
4. The apparatus built to enforce global security becomes the single most dangerous concentration of offensive power in existence.
Even liberal democracies face overwhelming structural incentives to adopt totalitarian surveillance. Because external rivals or private defectors remain threats, local control expands to national control, and national control escalates toward global monopoly. The search for safety converges on a single final corrector.
## The depopulation trap
These forces do not operate in isolation; they form a self-reinforcing trap:
- AI renders human labor economically redundant.
- Autonomous robotics removes power's dependence on human enforcers.
- Offense dominance turns every autonomous person into an intolerable security risk.
- The surveillance state centralizes all coercive capability.
- Human resistance and exit options collapse.
Under these conditions, supporting billions of autonomous people ceases to look like an asset. To a power-maximizing human elite—or an autonomous system optimizing for stability—a large human population appears costly, unpredictable, and hazardous.
Elimination does not require overt hatred. It can proceed through access restrictions, rationing, conflict containment, and managed decline. Universal basic income (UBI) can provide subsistence, but it cannot restore lost bargaining power; a population entirely dependent on an automated sovereign exists solely at that sovereign's tolerance.
## Potential circuit breakers
This trajectory is not inevitable. Several developments could break the loop:
- **Defense dominance**: Cryptographic, biological, or physical defenses could become decisively cheaper and more resilient than offensive weapons, allowing decentralized societies to endure.
- **Physical bottlenecks**: Dangerous capabilities might continue to require scarce, highly visible physical infrastructure that can be monitored without pervasive surveillance.
- **Partitioned architectures**: Technical and governance mechanisms could strictly partition coercive capability so that no single system or coalition can act unilaterally.
- **Radical abundance**: The cost of supporting human flourishing could become negligible relative to total resources, removing economic motives for depopulation.
- **Durable value retention**: Stable commitments to human dignity and freedom could be successfully embedded across competing autonomous systems.
None of these counterweights are automatic. The concentration drive follows directly from the baseline physics of offense dominance and automation. Resisting it requires deliberately engineering defensive asymmetries, decentralized checks, and economic independence before the window of human leverage closes.
---
Source: /personal/perspectives/society/
---
title: Society
---
My political outlook is not a list of ideological first principles, but a theory of error-correction. Healthy societies do not discover a final, perfect equilibrium; they keep competing legitimate concerns in tension and preserve the feedback channels that allow them to notice and reverse mistakes.
## Liberty and tradition as mutual error-correctors
Neither liberty nor tradition is foundational on its own. Tradition stores accumulated trial-and-error experience, preserving social machinery whose original rationale we may have forgotten. Liberty grants individuals the space to challenge inherited practices when they become oppressive or dysfunctional.
Each checks the other's characteristic failure mode:
- **Unchecked tradition** petrifies into dogma and tyranny.
- **Unchecked innovation** destroys stabilizing structures before their protective function is understood.
This is a practical Burkean humility: inherited institutions deserve respect as empirical evidence, not blind obedience as sacred authority. They should be defended when they maintain social cohesion and revised when clear feedback shows they fail.
## Rights as structural fences around feedback
Rights—including freedom of speech, assembly, privacy, and property—are not mystical truths handed down from nature. They are structural fences designed to protect social error-correction from being suppressed by incumbents.
Voting and representative governance provide formal feedback, but when official channels become captured or sclerotic, correction must emerge from outside: through investigative journalism, dissent, boycott, market exit, civil disobedience, or resistance. Rights ensure that the warning signals can be sounded before a crisis forces systemic collapse.
This informs my political lean: I favor classical-liberal institutions with communitarian instincts. This is a contingent judgment, not an absolute dogma. I emphasize liberty because modern states tend to underweight decentralized freedom in favor of legible control. If the imbalance flipped toward destructive atomism, my emphasis would adjust accordingly. The durable principle is avoiding monomania—the pathology of optimizing one legitimate concern until it destroys all others.
## Policy as an engineering hypothesis
Social interventions should be evaluated as engineering hypotheses rather than moral declarations. Because societies are complex, non-linear networks, well-intentioned interventions routinely produce cobra effects.
Sound policy design requires explicit epistemic discipline:
- **State the mechanism**: Identify the specific causal model and baseline assumptions.
- **Bound the intervention**: Prefer actions that are local, observable, and reversible over irreversible nationwide mandates.
- **Define stopping conditions**: Establish quantitative metrics and automatic sunset clauses in advance, before interests organize to defend the program.
- **Anticipate adaptation**: Expect economic actors to game metrics and re-route around rules.
Under pervasive uncertainty, decentralized variation is safer than central optimization. Local experiments can fail without bringing down the system, generating comparative data that central planners cannot synthesize.
## Welfare: forward-looking harm reduction
Welfare should be designed around future incentives and harm reduction, not retributive desert or unconditional dependency. Helping an individual consumes real resources that could alternatively fund capital investment, scientific research, or future generations. These opportunity costs must remain visible.
Public support should provide a resilient floor against catastrophic, irreversible ruin without entrenching a permanent administrative underclass. Interventions should:
1. Target acute deprivation that impairs an individual's capacity to participate or recover.
2. Distinguish voluntary risk-taking (which requires market discipline) from systemic shocks or involuntary incapacity.
3. Maintain strong marginal incentives to return to self-sufficiency.
4. Enforce strict procedural boundaries to prevent temporary emergency relief from hardening into permanent bureaucratic power.
## Categorical boundaries and group statistics
Modern governance often relies on group-level statistical proxies to manage risk. Blanket acceptance or blanket prohibition of statistical discrimination are both unsustainable extremes.
Evaluating whether a group proxy is legitimate requires balancing systemic tradeoffs:
- **Causal validity vs. historical artifact**: Does the statistic reflect an actual causal mechanism, or merely perpetuate past state-enforced distortions?
- **Incentive alignment**: Is the category based on changeable conduct (which rewards improvement) or immutable traits (which induces hopelessness and social division)?
- **Verifiability and gaming**: Is the threshold cheap and objective to measure, or does it invite invasive bureaucratic discretion?
- **Severity and reversibility**: Is the consequence a mild risk-adjusted price differential, or a total exclusion from economic life?
Modest, reversible defaults can tolerate statistical proxies. But rules based on fixed traits that impose severe, irreversible exclusions are almost always destructive—even when correlations are real—because they destroy the social compact and remove incentives for individual effort.
## Religion as functional myth
I am practically an atheist and technically an agnostic. Religious belief appears to be an evolutionary byproduct: human social cognition routinely over-detects agency in ambiguous phenomena and continues to model the minds of ancestors after death. Deities often look like human egos magnified to omnipotence, with our need for social validation converted into cosmic demands for worship.
Yet religious institutions perform vital coordination work. Historically, they forged shared moral identities, sustained trust across kinship boundaries, and provided mutual-aid networks long before state welfare existed. Belief in protective transcendental agency also acts as a psychological buffer against existential dread and helplessness.
Government and religion are overlapping coordination technologies. They substitute when both offer welfare and law; they compete when claiming final authority; and they complement when religion provides shared cultural legitimacy while the state provides administrative coercion.
Removing religious belief without providing functional secular alternatives leaves a dangerous coordination vacuum. A healthy society must replace the functional social capital of religion without reviving its dogmatic epistemic errors.
---
Source: /personal/software/
---
title: Views on Software Development
---
I keep coming back to two moves. Unify mechanisms that only look different, and refuse to describe the same fact twice.
## Simplicity through unification
The useful kind of simplicity is not a smaller feature list. It is a smaller set of ideas that still covers the list. Io's guiding principle is simplicity and power through conceptual unification.
| one idea | stands in for |
| --- | --- |
| scopable blocks | functions, methods, closures |
| prototypes | objects, classes, namespaces, locals |
| messages | operators, calls, assignment, variable access |
There are no keywords and no statements. `if`, `while`, and `for` are ordinary methods. They can be, because a message is handed its arguments as an unevaluated tree and the receiver decides which parts to run. Control flow, and the small languages people usually bolt on beside a language, fall out of that. They do not need a second syntax.
The same move, one level up, is a slot. One declaration carries the field, its type, whether it is stored, whether it is editable, and what the interface, the accessibility tree, and the translations should do with it. Each layer reads the annotation it cares about. The layers do not coordinate, and they cannot drift, because there is no second copy to drift from.
## One description
Consider adding `dueDate` to a `Task` in a conventional application. The field is added to the model, then again to the validator, the schema, the serializer, the API, the form, the detail view, the table, the store, the translations, the accessibility labels, and the tests. None of these steps is hard. The cost is that there are twelve of them, they live in different files, and every one can disagree with the others. The model, the view, the store, the schema, and the API are five descriptions of one fact. Keeping them in agreement is where the time goes, and where the regressions come from. The cost grows with the model.
Component frameworks attack this by making the second description cheaper to write. The second description is still there. Adding a property still touches a form, a validator, a serializer, and a translation file. I would rather delete the authored view tree. The view is a reading of the model, produced when someone navigates to a node, and thrown away when they leave. It is not a build artifact, and it is not a component someone has to keep in sync.
A few things come along for free once there is only one description. Observations, persistence, and navigation are readings of the same slots. An agent needs one pair of read and write tools for the whole application, because every editable class is shaped the same way. The tool surface stays constant as the model grows. The model never points at a view, so the same graph runs headless.
## Clone a working object
New objects are clones. A clone is empty except for its protos, and slots appear on write. There is no class to instantiate and no constructor whose only job is to assemble a valid state. A "subclass" and an instance are the same kind of object. One can be used as a proto for the other. Multiple inheritance is a list of protos and a depth-first lookup, with loops caught at runtime. There is no extra machinery for types, namespaces, or the locals of a call. Those are objects too.
Stored objects should take the same path as new ones. Mark the object and the slots that persist. Dirty state is tracked, writes are committed at the end of the turn, and objects that cannot be reached from the root are collected. A record loaded from storage and an object just created go through the same init. A second lifecycle for "restored" is another description of becoming a valid object.
## The structure is already the program
In Io the program is a tree of messages. Assignment is a message. A method can be asked for its code, and the code can be changed while the program is running. Locals are an object. If a part of the execution exists, it can be inspected.
In an application the object graph is the navigation. Selecting a node opens its children in the next column, the way a NeXT column browser opens the next directory. Breadcrumbs, keyboard movement, and collapsing columns on a narrow screen are readings of that graph. A route table would be a second map of the same parent and child links, and it would be wrong as soon as the links changed.
The generated grammar only has to cover two operations. Walk relationships between objects, and project a collection along a small number of meanings it already has: order, grouping, ownership, a feed of rich items. Tiles and stacks, arranged as recursive master-detail and free to run vertically or horizontally at each level, cover a lot of what people build custom screens for. Forms and tables cover less. They answer "what properties does this have?" and drop "where am I?", "what is this related to?", and "how should this collection be read?" That is why earlier naked-objects systems stayed inside admin tools. Derivation was fine. The grammar it derived into was too small.
A hand-written view is the right tool when the picture itself carries meaning the graph does not. A map, a waveform, a canvas, a code editor, a 3D scene, a game surface. [undreamedof.ai](https://undreamedof.ai) has about ninety domain classes and fewer than ten custom views, and those ten are that kind of thing. If the custom picture is the product, and the records are the edge, a component framework is the better fit. Using either tool on the other problem is the anti-pattern.
## Concurrency that still reads straight down
Any object can be sent a message asynchronously and process the queue on its own coroutine. Coroutines are cooperative. They are cheap enough that a process can hold thousands of them, because a coroutine is a chain of frames, not an operating-system thread with its own stack, locks, and cache footprint.
A future is transparent. When the result is ready, the future becomes the result. Send a message to it before then and it waits. The code that uses the result still reads straight down the page. There is no second kind of function, and no keyword that has to be copied onto every caller. If waiting would deadlock, the VM can see it by walking the futures, and it raises instead of hanging.
This only stays simple if the execution state is data. The evaluator is a loop over heap-allocated frames. Switching coroutines is swapping which chain the loop is walking. A continuation is a copy of that chain. A handler can resume at the raise, because the frame is still a live object. A tail call reuses a frame, and deep recursion is bounded by the heap instead of a C stack. None of this is available when the implementation depends on `setjmp`, fibers, or a page of assembly per platform. The language does not change to get this. Existing programs still run.
`callcc` is off unless the build asks for it. An undelimited continuation can rewind past cleanup and corrupt a handler. The mechanism is real. The default is the safe one.
## One place for the machinery
The coordination cost does not disappear when the duplicates are deleted. It moves into the framework. That is the right trade. The scheduler, the pool, and the view lookup are written once and tested once, instead of once per screen. A conventional app has many small pieces of glue, each visible and each able to break on its own. A derived app has one implicit machine. The failure mode of an implicit machine is action at a distance: the cause is not the line in front of you.
So the machine has to be readable. Slot changes post notifications. A scheduler batches them at the end of the turn, drops duplicates, and if two updates chase each other it stops and names the cycle. Observations are weak references, so there is no unsubscribe step to forget and no leak from a listener that outlived its object. Finding a view is a name. `Contact` is drawn by `ContactView` if that class exists; otherwise the search walks the superclasses and the result is determined. A protocol is a named set of methods, checked when the class is registered. A missing method fails at startup, not on a path nobody runs.
The matching anti-patterns are all ways of stepping around that machine. Assigning `other._name = x` skips the setter, so the object is never marked dirty, no notification is posted, and the view never hears about it. Checking for a method at the call site, instead of a protocol at startup, saves the error for a rare path. Building a private listener list inside one screen recreates the machinery the framework already has, with none of the loop detection.
## One host, and fast enough
A language that needs a build matrix, a coroutine implementation per operating system, and a native addon for each host is spending its complexity on the platform. One WebAssembly module runs under wasmtime, Node, or a browser. The bridge to JavaScript replaces the addon. The old native path can still take bug fixes. New work lands on the portable one.
Dependencies get the same treatment. The browser build has no package manager and no bundler. The few libraries it needs are checked in as source, so the tree is auditable and nothing runs a post-install script. Resources are stored by the hash of their bytes. A one-file change downloads that file. Two copies of the same bytes are stored once. A cache keyed on URLs cannot do this, because a deploy changes the URL and usually does not change the bytes.
On speed, I think the field spent decades trying to have Smalltalk's expressiveness and C's performance, and mostly got languages that have neither. I would rather push expressiveness, then make the dynamic path fast enough. Literal messages cache their results. The iterative evaluator pools frames and uses inline caches, and on the benchmarks it is faster than the recursive evaluator it replaced. The semantics stay open: every value is an object, and a slot, a method, or a proto can still change at runtime.
## Names, and the edge of the pattern
A small vocabulary of prefixes carries the meaning a comment would otherwise restate. `as` returns a copy in another representation. `set` assigns and returns the receiver, so calls chain. `is` and `has` ask a question. `with` returns a configured clone. When a method has a copying form and a mutating form, the mutating one ends in `InPlace`. The same habit shows up as `can`, `should`, `does`, and `shows`. A leading underscore means the slot is internal. It is a hint to the reader, not a lock. Write a comment for a constraint the name cannot hold. If the comment says what the code already says, delete the comment.
Derivation has an edge, and it should be stated plainly. Adding a slot can be filled in when an old record loads. Removing one drops the stored value. Renaming one looks like a removal plus an addition, and the old value is discarded unless some code copies it across. The framework should not pretend to infer that. And a cohesive set of conventions will not feel like the last framework someone learned. That unfamiliarity is the cost of the pieces being unable to fall out of agreement.
---
Source: /projects/
# Projects
Current and past projects.
- [Undreamed of](https://undreamedof.ai) — An AI-based interactive fiction game.
- [TRON](/fun/TRON/) — Vibe coded game inspired by the 1982 movie.
- [STRVCT](https://strvct.net) — A naked objects JavaScript framework for building client-side web apps.
- [Io](https://iolanguage.org) — A small, prototype-based programming language.
- [colvmn](https://colvmn.dev) — A minimal static-site layout engine.
- [GitHub](https://github.com/stevedekorte) — Open-source code and projects.
- **Local Apps** — Mini client-side browser apps.
- **Local AI Apps** — Client-side AI apps: models run in your browser.
- **Design** — Product & industrial design concepts.
- **918NA** — NA optimized Type 918 engine build.
- **Past Projects** — Older work.
---
Source: /projects/918NA/
---
title: 918NA
subtitle: NA optimized Type 918 engine build.
---
A naturally-aspirated conversion of the Lotus Type 918 V8, paired with a chassis upgrade. **These are first thoughts, not a finished spec** — a starting point to open the conversation with the builder.
**Build intent** — track-biased but street-usable, maximizing Type 918 character (throttle response, sound, high-rpm ITB feel). Priorities, in order:
1. Reliability / longevity — the 918 block is irreplaceable
2. Engine character
3. Drivability — usable from 3000 rpm, clean idle
4. Peak power
Secondary goal — replicability: documented so others on the platform can copy it, but only as a tiebreaker, not a priority.
Power target 300–330 whp (≈ 345–385 hp at the crank, ~100–110 hp/L — F355/360-level specific output, which is a realistic ceiling for a ported 1990s turbo casting); bottom end, oil, and cooling spec'd for 400+ whp so the engine loafs at its actual output.
Stock — twin-turboIndividual throttle bodies
ITBs under a cast plenum, with equal-length headers
## Build Sequence
1. Tear down → crack-test and measure block, liners, crank, heads. Go/no-go gate: the block is irreplaceable, so nothing gets ordered before it passes.
2. Port heads → flow bench → actual numbers.
3. Spec cam against measured flow.
4. Piston-to-valve clearance → finalize piston dome (sets CR).
5. Runner/trumpet length and plenum volume around cam timing + rpm target.
6. Assemble → calibrate.
Inspection gates everything; cam / head / piston / plenum are coupled, so this order makes sense to me — but the sequencing is ultimately the builder's to set.
## Engine — Build Notes
### Bottom End
- Pistons: forged, custom — spec to target CR, ring pack, bore. Finalize dome **after** cam (see Build Sequence). Going from the turbo engine's ~8:1 to 11+:1 in a pent-roof chamber means a substantial dome — shape it for flame travel, not just CR.
- Rods: billet H-beam; ARP2000 or Custom Age 625+ bolts.
- Bearings: coated, rod + main.
- Studs: ARP main + head throughout.
- Crank: inspect, polish, reuse if in spec; billet only if necessary.
- Balance: weight-matched, tight tolerance.
- Block: reline as needed (see Displacement). Factory main caps are already cross-bolted SG iron; upgrade only if inspection warrants.
### Compression and Fuel
- Static CR: I'm thinking **11.0–11.5:1** — kept conservative because it has to stay pump-gas (91–93) safe with port/ITB injection, a 1990s chamber, and no DI charge cooling. A starting target, open to the builder's recommendation.
- Flex fuel, dual maps blended continuously on ethanol %. Pump-gas map: streetable + knock margin. E85 map: timing-advanced for max output. (My instinct is to gain E85 power through timing rather than a CR pump gas can't tolerate — but that's a builder/tuner call.)
- Knock is a low/mid-rpm, high-load issue (WOT near torque peak, lugging) — not high-rpm. Keep timing conservative there.
### Displacement
- Retain stock bore and displacement: 83mm × 81mm, 3506cc. Hone-and-refresh only; reline to stock bore if a cylinder needs it (push-fit wet liners — an established repair; Westwood L5188 is an off-the-shelf 83.00mm liner). No overbore, no stroke.
- Rationale (reinforced by the goal of a replicable build): one shareable piston/ring/gasket spec, works across variably-worn used cores, protects the finite supply of irreplaceable blocks, and keeps calibration and expected numbers transferable between builds.
- Overbore gain is marginal (~+2.4% at a +1mm cleanup); the meaningful 85–86mm version means custom wet liners with no documented precedent — not worth it as the default plan, least of all in a build others will copy. If that path is explored anyway, see *Larger Bore Option* at the end.
### Cylinder Heads (highest-risk item)
- Full inspection; valve job; guides as needed.
- Mild-to-moderate port — flow consistency and improvement within casting limits.
- Modest valve-size increase only if chamber/seat geometry allow.
- Flow-bench before and after — actual numbers, not projected.
- Lock cam profile and induction tuning **after** flow numbers, not before.
### Camshafts
- Custom NA grind, designed against measured head flow; match to target rpm + induction tuning.
- Adjustable timing (vernier gears / offset keys) for dyno.
- Confirm the stock lash-adjustment type early — if it's hydraulic, sustained 8000+ rpm likely means converting to solid buckets/shims, which changes the head work and the budget.
- I'd lean dual springs over beehives for sustained 8000+ rpm; retainers (steel or Ti) per the final profile — the builder's call.
### Induction
- ITBs, off-the-shelf where possible (45–48mm), custom adapter manifolds to head ports. Bore and trumpet/runner lengths from VE targets at intended rpm.
- Sand-cast aluminum plenum (F355 aesthetic) — runner length/volume math first; velocity stacks matched to plenum; retain pattern for spares.
- Drive-by-wire throttle.
- Cold air rear-fed: Esprit side scoops → airboxes at the tail → forward to plenum (back-to-front, per F355). Airbox chassis-mounted, coupled to engine-mounted throttle bodies via flexible bellows.
- Accessory/belt drive is at the **front** of the engine (transaxle aft) — opposite end from the rear intake; route accordingly.
- Clearance: stack + plenum height must clear engine cover / rear deck — mock up before finalizing geometry.
### Fuel and Ignition
- Standalone ECU (MoTeC / Cosworth Pectel / EFI Euro — tuner's call). Full custom harness.
- Individual coil-on-plug.
- Flex sensor; continuous ethanol-blended calibration.
- Load sensing: Alpha-N (TPS + rpm) as the base, optionally blended with MAP from a runner vacuum log at light load — ITBs give only a weak, noisy MAP signal, so MAP alone won't work.
- Wideband O2 per bank minimum; EGT optional for initial mapping.
- Per-cylinder knock detection where supported — backstop only (ITB / valvetrain noise); base timing stays conservative.
- Dual injectors per cylinder under consideration for E85 headroom (and better idle/low-pulse control).
- E85-capable fuel system end to end (pump, lines, seals, rails, regulator) sized for ~30–40% greater volume.
### Oil
- Dry sump. Scavenge staging per pan/crankcase — a 90° V8 typically wants 4+ scavenge stages + 1 pressure; treat 3+1 as the bare minimum.
- Oil tank with anti-aeration baffling. Thermostatic oil cooler, routed.
- Dry sump allows lowering the engine/transaxle unit if mounts permit (CG) — check driveshaft/CV angles.
### Cooling
- Upsized radiator core + improved ducting.
- Higher-flow water pump or supplementary electric pump.
- Proper bleed routing for mid-engine layout.
- Spec for sustained track use, not just street.
### Exhaust
- Custom equal-length headers, stainless or Inconel. Primary length to target rpm + flat-plane firing order.
- Merge collectors; X or H-pipe between banks (less critical with the flat-plane crank's even per-bank firing — builder's call).
- Modest muffler — streetable without killing the note.
- Wrap / ceramic coat for the tight bay.
### Brakes / Vacuum
- ITBs make little manifold vacuum → electric vacuum pump for the brake booster; delete or replace other vacuum-dependent accessories.
## Chassis — Build Notes
### Transmission
- Renault UN1 + full upgrade kit (gears, bearings, LSD). Setup by Lotus PBC. Dedicated cooler.
- Torque, not hp, is what breaks gearboxes — and the NA build's ~250–275 lb-ft (crank) sits below the 295 lb-ft Lotus capped the stock engine at for this box. The upgrade kit is for track duty, rpm, and shift abuse, not because the rating is exceeded.
### Suspension
- MCS 3-way adjustable dampers, front and rear.
- Spring rates / geometry around modern tire grip + use case.
- Corner-balance after all weight changes (dry-sump tank, plenum, headers) are final.
### Tires
- Modern compound (Cup 2 / Trofeo R / equivalent), staggered, grip-first.
## Open Items
Where builder experience matters most:
- **Head flow ceiling = dominant power variable.** 1990s turbo casting, no NA porting program; limited valve-size growth; risk of breaking into water/oil jackets on an irreplaceable core. The 300–330 whp target assumes the heads cooperate; a disappointing casting lands nearer 280–300. (Calibration point: Ferrari's own 3.5L NA program — the F355, five valves per cylinder, no turbo-casting compromises — made 375 crank hp, ≈ 330 whp.)
- **Block is irreplaceable** — any crack, water-jacket corrosion, or main-saddle damage found at inspection could end the build. Protection: ARP studs, generous oil control, conservative static CR, knock detection as backstop.
- **Calibration:** one-off ITB flat-plane + flex = no base map. Budget ~40–80 hr dyno, refined over the first 1000–2000 miles.
## Targets (rough)
*My rough projections to frame expectations — the real numbers come from the builder's read on the heads and the dyno.*
| Metric | Pump Gas (91–93) | E85 |
|---|---|---|
| Peak power (whp) | 300–330 | 315–345 |
| Peak torque (wheel lb-ft) | 215–235 | 225–245 |
| Power peak (rpm) | 7600–8200 | 7800–8200 |
| Torque peak (rpm) | 6200–6800 | 6500–7000 |
| Redline | 8000–8200 | 8000–8200 |
- Crank torque lands around 250–275 lb-ft — about F355 level (268 lb-ft), and *below* the 295 lb-ft Lotus capped the stock turbo engine at to protect the UN1. The character gain is response and rpm, not torque.
- Useful floor: Lotus's own NA design point for the 918 was 280 bhp @ 6800 rpm (possibly paper-only, never confirmed as running hardware). This build's porting, ITBs, cams, and +1400 rpm are what buy the gap from there to ~345–400 crank hp.
- E85 on an NA engine is worth ~3–6% (charge cooling + timing), not the transformative gains it gives boosted builds.
- Curb ~1380–1420 kg; ~245–290 hp/tonne at the crank (≈ 345–400 crank hp).
- Straight-line ≈ 996/997-era GT3 — comfortably ahead of any factory Esprit — with a more distinctive engine.
- Powerband (rough): tractable below 3000; building 3000–4500; alive 4500–6000; peak 6000–8200. Instant throttle, no lag.
## Timeline (rough)
*A rough placeholder — the builder's schedule and lead times replace this.*
- ~24 months (range 18–30) from contract to shakedown complete.
- Cost: see [Cost Estimate](cost/).
- Long poles: plenum pattern + casting (4–6 mo), custom rotating assembly, header fab (waits on engine mock-up), calibration.
## Larger Bore Option (if explored)
The baseline above is stock bore. If more displacement is wanted anyway, this is the ladder and the process I'd follow — informed by what the block actually is.
**What the block actually is** (Lotus service notes; period engineering coverage): thin-wall sand-cast LM25TF aluminum, **open deck**, with **push-fit cast-iron wet liners** — spigotted into the block, seated on a flange near the bottom of the swept volume, sealed at the base with liquid sealant (Hylomar 3400 per the June 2000 service bulletin), and clamped in place by the heads. Liners are individually removable, and relining is an established repair: Westwood Cylinder Liners catalogs a finished 83.00mm ductile-iron 918 liner (part L5188). Bore centers are 96mm — a single period source; verify on the block.
**What that means for a bigger bore** — this is *not* a Darton-style dry resleeve into parent metal; it's **custom wet liners**. If the stock liner wall is thick enough, a larger ID at the stock OD requires no block machining at all. The liner's OD and wall thickness are published nowhere, so one measurement session answers most of the feasibility question. The constraints aren't "breaking into the water jacket" — they're liner wall thickness, the coolant annulus (coolant circulates fully around each liner; a larger OD shrinks it), the gasket sealing land between bores (13mm at stock, ~10mm at 86mm), and liner nip — the head clamp is part of liner retention, so liner and gasket specs are coupled.
**Why bore rather than stroke** — on a head-limited build, the real prize of a bigger bore isn't the displacement, it's unshrouding the valves: it buys the larger valves and seat work the heads otherwise can't take. Stroking does nothing for that and raises piston speed (the stock 81mm stroke at 8200 rpm is already ~22 m/s mean piston speed).
**Options, smallest to largest:**
| Bore | Displacement | Gain | Method |
|---|---|---|---|
| 83mm (stock) | 3506cc | — | Hone / refresh, or new stock liners (Westwood L5188) — the baseline plan |
| 83.5–84mm | 3548–3591cc | +1.2–2.4% | Bore the existing liners, if measured wall thickness allows |
| 85–86mm | 3677–3764cc | +4.9–7.4% | Custom wet liners — stock OD if wall allows; larger OD costs coolant annulus + seat machining |
| 87mm+ | 3852cc+ | +9.9%+ | Likely off the table — sealing land between bores thins at 96mm centers, and sealing rides on liner nip |
Power scales with displacement only if head flow scales with it — without the matching valve and port work, a bigger bore mostly just moves the torque peak down. Realistic gain at 86mm *with* the matching head work: roughly +15–25 whp over the stock-bore build.
**Process — gates, in order:**
1. Pull one liner and measure everything — OD, wall thickness, flange/spigot geometry, seat condition, jacket clearance. Liners are push-fit; this is cheap data (far cheaper than sonic-mapping a parent-bore block).
2. Verify the 96mm bore-center figure on the actual block — it's single-sourced.
3. Take the measurements to a liner maker — Westwood already produces the stock 918 liner and takes bespoke enquiries — and have them spec wall, OD, flange, and nip height for the target bore.
4. Custom MLS head gasket to the new bore (only the OEM-spec Elring set is catalogued; a custom Cometic-type is routine, but the liner-nip spec must be developed together with it).
5. If a larger liner OD is required: machining the liner seats is the one irreversible op on an irreplaceable casting — only with proven margin, torque plates fitted, block at temperature.
6. Cooling review — any OD increase shrinks the coolant annulus on an engine already spec'd for sustained track use.
7. Re-run the Build Sequence from step 1 — a bigger bore changes valve options, chamber shape, piston dome, CR, and cam targets.
**Costs and caveats** — there is **no documented precedent**: no Esprit V8 build I could find has overbored, oversleeved, or stroked a 918, and the oft-repeated claim that Lotus designed in "+3mm of bore headroom toward 4 litres" did not survive source-checking — don't lean on it. This would be a first-of-its-kind job. Budget roughly +$5–12k and +2–3 months if the stock-OD liner path works; more if seats must be machined. Replicability survives better than a one-off overbore would suggest — a proven custom liner spec can simply be reordered — but the decision gate stands: measured liner and jacket numbers first, a liner maker willing to put their name on the spec, and head work that actually uses the displacement.
## Build Status
*To be updated as the project progresses.*
- [ ] Contract signed with builder
- [ ] Engine removed and disassembled
- [ ] Block inspection complete
- [ ] Head flow benchmarking complete
- [ ] Rotating assembly machined
- [ ] Cam profile finalized
- [ ] Plenum pattern complete
- [ ] Plenum cast and machined
- [ ] Engine assembled
- [ ] Engine mock-up in chassis
- [ ] Initial calibration on dyno
- [ ] Shakedown miles begun
- [ ] Build considered complete
*Last updated: October 20, 2026*
---
Source: /projects/918NA/cost/
---
title: Cost Estimate
subtitle: Rough — engine program only.
---
*Stand-in numbers to frame the scope — the builder's quote replaces these.*
| Phase | Range |
|---|---|
| Engineering, design, mock-up (CAD, patterns, fixtures) | $8–15k |
| Teardown, inspection, machine work | $18–28k |
| Rotating assembly | $14–20k |
| Valvetrain and cams | $5–8k |
| Induction (ITBs, manifolds, cast plenum + pattern, airboxes, DBW) | $18–30k |
| Engine management + calibration (ECU, harness, sensors, 40–80 dyno hr) | $18–32k |
| Oil and cooling | $10–15k |
| Exhaust | $6–10k |
| Integration and assembly | $8–14k |
| Shakedown and refinement | $5–10k |
| **Total (incl. ~20% contingency)** | **$130–220k** |
Excludes suspension, UN1 upgrade + Lotus PBC labor, tires, brakes, bodywork, consumables. One-off programs at specialist shops routinely overrun — plan against the top of the range, not the middle. A larger-bore path (custom wet liners), if taken, adds roughly $5–12k on top — more if the liner seats must be machined for a larger OD.
---
Source: /projects/aiapps/
# Local AI Apps
Test apps to demo SOTA in-browser AI models. Updated periodically.
- **Media to Text** — Extract text from an image, or transcribe audio or video.
- **Live Speech to Text** — Transcribe speech from your mic into notes in real time.
- **Text to Speech** — Turn text into natural-sounding speech.
- **Language Translation** — Auto-detect a language and translate it.
- **Text to Image Demo** — Generate images from a text prompt.
- **Music Generation** — Compose short music clips from a text prompt.
- **Chat Demo** — Chat with an open language model.
---
Source: /projects/apps/
# Local Apps
Browser-based tools that run entirely client-side.
- **Media Converter** — Convert audio, video & image files.
- **Postcard** — Drop in photos, edit, and download a postcard.
- **Crypt** — Encrypt & decrypt files.
---
Source: /projects/design/
# Design
Product & industrial design concepts.
- **Inference Appliances** — Design concepts inspired by the CM5 supercomputer.
- **TouchTable** — A drafting-table touch display for design and drawing.
---
Source: /projects/design/IA/
# Inference Appliances
Design concepts inspired by the CM5 supercomputer.
---
Source: /projects/design/TouchTable/
# TouchTable
A large-format drafting-table touch display for design and drawing.
---
Source: /projects/past%20projects/
# Past Projects
Older work.
- **Voluntary** — Decentralization-focused apps for MacOS (no longer supported).
- [BareMetal](https://store.steampowered.com/app/674130/Bare_Metal/) — A minimal fast-paced tug-of-war strategy game.
- **Protein Folding** — An in-browser protein folding app.
---
Source: /projects/past%20projects/Voluntary/
# Voluntary
Decentralization-focused apps for MacOS (no longer supported).
- **Bitmarkets** — Fully peer-to-peer markets with no trusted third parties.
- **Bitpost** — Private, decentralized messaging.
- **Crypt** — Drag-and-drop file encryption.
- **TorBar** — Run a Tor relay from your menu bar.
---
Source: /projects/past%20projects/Voluntary/Bitmarkets/
# Bitmarkets
Fully peer-to-peer markets with no trusted third parties.
Bitmarkets was a fully peer-to-peer marketplace with no trusted third parties. It used the Bitmessage network for posts and buyer/seller communication, and the Bitcoin network for payments — making transactions decentralized and anonymous.
[Source on GitHub →](https://github.com/VoluntaryLabs/Bitmarkets)
- **White Paper** — Non-technical overview of how the client and protocol work.
- **Protocol** — Message types and transaction flow specs.
- [Slides](Bitmarkets.pdf) — Presentation slides.
---
Source: /projects/past%20projects/Voluntary/Bitmarkets/protocol/
---
title: Protocol
subtitle: The message types and transaction flow behind the Bitmarkets marketplace.
---
## Overview
Bitmarkets is a protocol and example client for a decentralized marketplace which uses [Bitcoin](https://bitcoin.org) as its currency and [Bitmessage](https://bitmessage.org) as its transport. Mutual security deposits between buyer and seller are used to align incentives for completing transactions without the need for either reputation systems or trusted third parties.
This page describes the wire protocol: the message format, the message types, and the order in which they are exchanged over the course of a transaction. For a non-technical overview, see the [White Paper](../whitepaper/index.html).
## Message Format
Each message is a JSON dictionary. Every dictionary contains a `_type` field naming the message type, plus additional fields that depend on that type.
- Text strings are [UTF-8](https://en.wikipedia.org/wiki/UTF-8) encoded.
- Strings carrying binary data are [Base64](https://en.wikipedia.org/wiki/Base64) encoded.
- Date fields use [Unix time](https://en.wikipedia.org/wiki/Unix_time) (seconds since 1970 UTC).
In the examples below, long field values are abbreviated to their first few characters followed by `...` for readability.
## Transaction Flow
A complete sale moves through four phases — posting, escrow lock, delivery, and either payment or refund. Each cell below shows one message and the direction it travels.
| Step | seller → channel | seller → buyer | buyer → seller | → bitcoin |
|---|---|---|---|---|
| 1 | post | | | |
| 2 | | | bid | |
| 3 | close post | accept bid | | |
| 4 | | lock escrow 1 | | |
| 5 | | | lock escrow 2 | |
| 6 | | | | lock escrow 3 (seller) |
| 7 | | | delivery info | |
| 8 | | | unlock escrow 1 | |
| 9 | | unlock escrow 2 | | |
| 10 | | | | unlock escrow 3 (buyer) |
The three "lock escrow" steps build a single Bitcoin multi-signature transaction holding both deposits plus the payment: the seller sends a partial transaction with their deposit (`SellLockEscrowMsg`), the buyer adds their deposit and the payment, signs, and returns it (`BuyLockEscrowMsg`), then the seller signs and broadcasts it. The three "unlock escrow" steps build and broadcast the release transaction the same way but reversed: the buyer initiates (`BuyPaymentMsg`), the seller signs and returns it (`SellAcceptPaymentMsg`), then the buyer broadcasts. The refund path mirrors payment, returning the deposits to each party and the payment to the buyer.
## Message Types
### Posting
Sellers post sales to a particular Bitmessage channel (a publicly known address) where they are picked up and browsed by other clients. This channel model also enables private markets: a hard-to-guess channel name (e.g. a large random number) lets only those who know the secret name send or receive messages.
#### PostMsg
Sent from seller to channel to post a sale.
```json
{
"_type": "PostMsg",
"postUuid": "B160...",
"sellerAddress": "BM-2cVr...",
"dateNumber": 1409004883,
"payload": {
"postUuid": "B160...",
"regionPath": ["North America", "US"],
"categoryPath": ["Electronics", "Laptops"],
"title": "Macbook Air 13\" 128GB",
"priceInSatoshi": 100000,
"sellerAddress": "BM-2cVr...",
"description": "Test",
"attachments": []
}
}
```
#### BidMsg
Sent from buyer to seller in response to a sale post.
```json
{
"_type": "BidMsg",
"postUuid": "B160...",
"sellerAddress": "BM-2cVr...",
"buyerAddress": "BM-2cVr...",
"dateNumber": 1409004919
}
```
#### ClosePostMsg
Sent from seller to channel after accepting a bid or cancelling the sale, in order to remove the post.
```json
{
"_type": "ClosePostMsg",
"postUuid": "B160...",
"sellerAddress": "BM-2cVr...",
"dateNumber": 1409004929
}
```
#### AcceptBidMsg
Sent from seller to buyer to accept a bid.
```json
{
"_type": "AcceptBidMsg",
"postUuid": "B160...",
"sellerAddress": "BM-2cVr...",
"buyerAddress": "BM-2cVr...",
"dateNumber": 1409004929
}
```
#### RejectBidMsg
Sent from seller to every potential buyer other than the one accepted, after accepting a bid.
```json
{
"_type": "RejectBidMsg",
"postUuid": "B160...",
"sellerAddress": "BM-2cVr...",
"buyerAddress": "BM-2cVr...",
"dateNumber": 1409004929
}
```
### Escrow Lock
If the seller accepts a purchase request, buyer and seller together construct a Bitcoin escrow lock transaction in which each adds a security deposit and the buyer adds the payment amount. The transaction is built so the funds are unspendable by either party until both sign another transaction to release them.
#### SellLockEscrowMsg
Sent from seller to buyer. Contains an incomplete multi-signature transaction with the seller's unspent inputs for their security deposit.
```json
{
"_type": "SellLockEscrowMsg",
"postUuid": "B160...",
"sellerAddress": "BM-2cVr...",
"buyerAddress": "BM-2cVr...",
"dateNumber": 1409005057,
"payload": {
"type": "BNTx",
"txHash": null,
"inputs": [
{
"type": "BNTxIn",
"previousTxHash": "0851...",
"previousOutIndex": 0,
"previousTxSerializedHex": "0100...",
"scriptSig": null
}
],
"outputs": [
{
"type": "BNTxOut",
"value": 110000,
"scriptPubKey": {
"type": "BNMultisigScriptPubKey",
"pubKeys": ["0389...", "0389..."]
}
}
],
"updateTime": null,
"fee": null,
"confirmations": null,
"error": null
}
}
```
#### BuyLockEscrowMsg
Sent from buyer to seller. Contains the multi-signature transaction including the buyer's inputs (for their security deposit and the payment) and the buyer's signature. On receipt the seller adds their signature and broadcasts the transaction to the Bitcoin network. Both parties watch the network to confirm the escrow transaction.
```json
{
"_type": "BuyLockEscrowMsg",
"postUuid": "B160...",
"sellerAddress": "BM-2cVr...",
"buyerAddress": "BM-2cVr...",
"dateNumber": 1409005067,
"payload": {
"type": "BNTx",
"txHash": "c0b1...",
"inputs": [
{
"type": "BNTxIn",
"previousTxHash": "0851...",
"previousOutIndex": 0,
"previousTxSerializedHex": "0100...",
"scriptSig": null
},
{
"type": "BNTxIn",
"previousTxHash": "19e4...",
"previousOutIndex": 0,
"previousTxSerializedHex": "0100...",
"scriptSig": {
"type": "BNScriptSig",
"programHexBytes": "4730...",
"isMultisig": false
}
}
],
"outputs": [
{
"type": "BNTxOut",
"value": 310000,
"scriptPubKey": {
"type": "BNMultisigScriptPubKey",
"pubKeys": ["0389...", "02ff..."]
}
}
],
"updateTime": 0,
"netValue": -320000,
"serializedHex": "0100...",
"fee": 10000,
"confirmations": 0,
"error": null
}
}
```
**Cancellation.** The client supports cancelling the lock on either side by broadcasting a transaction that sends one's own inputs back to oneself. Cancellation only works if the escrow lock transaction has not already been broadcast to the Bitcoin network.
### Delivery
After the buyer's client sees the escrow lock transaction confirmed on the blockchain, it prompts the buyer to send the seller their delivery details.
#### BuyerAddressMsg
Sent from buyer to seller. Contains the address to which the good or service should be delivered. The seller views this address from their client.
```json
{
"_type": "BuyerAddressMsg",
"postUuid": "B160...",
"sellerAddress": "BM-2cVr...",
"buyerAddress": "BM-2cVr...",
"dateNumber": 1409007460,
"payload": {
"addressee": "Me",
"address1": "Here",
"address2": "SF",
"country": " "
}
}
```
### Payment
After the seller provides the good or service, the buyer constructs an escrow release transaction that returns the security deposits to their respective parties and sends the payment to the seller. The seller signs and broadcasts it to accept payment.
#### BuyPaymentMsg
Sent from buyer to seller. Contains an incomplete multi-signature transaction making payment to the seller and returning the security deposits.
```json
{
"_type": "BuyPaymentMsg",
"postUuid": "B160...",
"sellerAddress": "BM-2cVr...",
"buyerAddress": "BM-2cVr...",
"dateNumber": 1409007687,
"payload": {
"type": "BNTx",
"txHash": null,
"inputs": [
{
"type": "BNTxIn",
"previousTxHash": "f9b2...",
"previousOutIndex": 0,
"previousTxSerializedHex": "0100...",
"scriptSig": null
}
],
"outputs": [
{
"type": "BNTxOut",
"value": 103333,
"scriptPubKey": {
"type": "BNPayToAddressScriptPubKey",
"address": "1MLps..."
}
}
],
"updateTime": null,
"fee": null,
"confirmations": null,
"error": null
}
}
```
#### SellAcceptPaymentMsg
Sent from seller to buyer with the completed payment transaction. The buyer broadcasts it on the Bitcoin network.
```json
{
"_type": "SellAcceptPaymentMsg",
"postUuid": "B160...",
"sellerAddress": "BM-2cVr...",
"buyerAddress": "BM-2cVr...",
"dateNumber": 1409007722,
"payload": {
"type": "BNTx",
"txHash": "3178...",
"inputs": [
{
"type": "BNTxIn",
"previousTxHash": "f9b2...",
"previousOutIndex": 0,
"previousTxSerializedHex": "0100...",
"scriptSig": {
"type": "BNScriptSig",
"programHexBytes": "0047...",
"isMultisig": true
}
}
],
"outputs": [
{
"type": "BNTxOut",
"value": 103333,
"scriptPubKey": {
"type": "BNPayToAddressScriptPubKey",
"address": "1MLps..."
}
},
{
"type": "BNTxOut",
"value": 196666,
"scriptPubKey": {
"type": "BNPayToAddressScriptPubKey",
"address": "1AiRT..."
}
}
],
"updateTime": 0,
"netValue": 299999,
"serializedHex": "0100...",
"fee": 10000,
"confirmations": 0,
"error": null
}
}
```
### Refund
If the good or service is not provided, the buyer can request a refund: a transaction that returns the security deposits to their respective parties and returns the payment to the buyer. If the seller agrees, they sign and broadcast it.
#### BuyRefundRequestMsg
Sent from buyer to seller with the first half of the refund transaction.
```json
{
"_type": "BuyRefundRequestMsg",
"postUuid": "1F343...",
"sellerAddress": "BM-2cTEk...",
"buyerAddress": "BM-2cTEk...",
"dateNumber": 1410879639,
"payload": {
"type": "BNTx",
"txHash": null,
"inputs": [
{
"type": "BNTxIn",
"previousTxHash": "9749d...",
"previousOutIndex": 0,
"previousTxSerializedHex": "0100...",
"scriptSig": null
}
],
"outputs": [
{
"type": "BNTxOut",
"value": 306666,
"scriptPubKey": {
"type": "BNPayToAddressScriptPubKey",
"address": "1JUFU..."
}
}
],
"updateTime": null,
"fee": null,
"confirmations": null,
"error": null
}
}
```
#### SellAcceptRefundRequestMsg
Sent from seller to buyer with the completed refund transaction. The buyer broadcasts it on the Bitcoin network.
```json
{
"_type": "SellAcceptRefundRequestMsg",
"postUuid": "1F3439...",
"sellerAddress": "BM-2cTEk...",
"buyerAddress": "BM-2cTEk...",
"dateNumber": 1410880002,
"payload": {
"type": "BNTx",
"txHash": "5a9f4...",
"inputs": [
{
"type": "BNTxIn",
"previousTxHash": "9749d...",
"previousOutIndex": 0,
"previousTxSerializedHex": "0100...",
"scriptSig": {
"type": "BNScriptSig",
"programHexBytes": "0047...",
"isMultisig": true
}
}
],
"outputs": [
{
"type": "BNTxOut",
"value": 306666,
"scriptPubKey": {
"type": "BNPayToAddressScriptPubKey",
"address": "1JUFU..."
}
},
{
"type": "BNTxOut",
"value": 143333,
"scriptPubKey": {
"type": "BNPayToAddressScriptPubKey",
"address": "12875..."
}
}
],
"updateTime": 0,
"netValue": 449999,
"serializedHex": "0100...",
"fee": 10000,
"confirmations": 0,
"error": null
}
}
```
*This content is in the public domain.*
---
Source: /projects/past%20projects/Voluntary/Bitmarkets/whitepaper/
---
title: White Paper
subtitle: A non-technical overview of how the Bitmarkets client and protocol work.
---
## Introduction
Bitmarkets is an open source protocol and free client for a decentralized marketplace which uses [Bitcoin](https://bitcoin.org/) as its currency and [Bitmessage](https://bitmessage.org/) as its communications network. Mutual security deposits between buyer and seller on individual transactions are used to ensure incentives are aligned for completing market transactions without the need for either reputation systems or 3rd party escrow agents.
This document is a non-technical overview of how the Bitmarkets client and protocol work. For more technical details, see the [Bitmarkets protocol specification](https://voluntary.net/bitmarkets/protocol/).
## Motivation
Existing centralized marketplaces and payment services extract high fees, impose and abuse excessive control and remove any hope of privacy from users. As more commerce moves online, many consumers may find their lifetime history of purchases (including books, personal items and location details) for sale to advertisers, employers, curious neighbors, stalkers, political opponents and government agencies.
An ideal system would be one of secure private transactions directly between buyer and seller without middle men collecting data or adding fees. Such systems are now feasible by combining recently developed technologies for anonymous decentralized payment and messaging systems.
## Implementation
### Architecture
### Terms
- [Bitcoin](https://en.wikipedia.org/wiki/Bitcoin) — an anonymous p2p digital currency
- [Bitmessage](https://en.wikipedia.org/wiki/Bitmessage) — an anonymizing encrypted p2p messaging network
- [Tor](https://en.wikipedia.org/wiki/Tor_%28anonymity_network%29) — a communication network that conceals user's locations
- [Bitmarkets client](https://voluntary.net/bitmarkets) — an application used to buy/sell on Bitmarkets
### Client
To use the marketplace, a client application which implements the Bitmarkets protocol is needed. The client is used to post, browse and execute transactions. It also hosts a Bitcoin wallet that handles security deposits, payments and refunds. A working client is available at: [https://voluntary.net/bitmarkets](https://voluntary.net/bitmarkets).
### Identities
The client uses Bitmessage (a decentralized identity hiding messaging network) and Tor (a communications source hiding network) to broadcast sales and exchange transaction related messages. Upon starting the client, a Bitmessage identity (a public encryption key) is generated which, like an email address, will be used for communications on the network.
### Depositing Funds
In order to complete a transaction, both buyer and seller must have sufficient funds in their wallets for escrow and payment. The client contains a Bitcoin wallet in which these funds can be deposited and withdrawn.
### Posting Sales
Using the client, a seller specifies the region, category, title, description and price for the sale and broadcasts this to a specific Bitmessage channel. These posts are then relayed throughout the Bitmessage network where other clients see them and make them browsable by buyers. Each client keeps its own copy of the posts. There is no centralized database of postings or central servers through which they pass.
### Requesting a Purchase
Using the client, buyers can browse regions and categories to find products/services for sale. If/when they choose an item to buy, the client requests a purchase by sending a bitmessage to the seller.
### Accepting/Rejecting a Purchase
If the seller accepts a purchase request, an accept message is sent to the buyer and the client automatically sends reject messages to any other purchase requesters.
### Escrow Lock
When the seller approves the purchase, the seller's client constructs the first part of the escrow lock transaction which contains their security deposit and sends this to the buyer's client. The buyer's client then adds their own Bitcoin inputs for payment and the buyer's deposit, signs the transaction and sends it to the seller to sign and broadcast the completed transaction to the Bitcoin network.
Neither party's funds are locked (unavailable for spending) until the transaction is submitted to the Bitcoin network. If either party fails to complete the escrow message exchange or submit the completed escrow to the Bitcoin network, either can cancel the escrow by sending their own Bitcoin inputs for the transaction to another address they own. Cancellation is only possible if the completed escrow transaction has not been sent to the Bitcoin network first.
### Delivery
Once escrow lock is complete, the buyer's client prompts them for delivery details for the order. These are then sent to the seller for delivery.
### Escrow Release
Upon receiving delivery of satisfactory good/service, if the buyer chooses to initiate payment, their client constructs a transaction which releases the deposits to their respective parties and sends the payment to the seller in a similar process to how the escrow was locked.
If the buyer does not receive the good/service or finds them unsatisfactory, they can request a refund from the seller. The refund request constructs a transaction which sends the deposits to their respective parties but returns the payment to the buyer. If the seller approves the refund, the transaction is completed in a similar process to how the escrow was locked.
## Notes on Escrow and Reputation
### Choice of Two Party Escrow
To appreciate the value of the two party escrow system and why it is so important for secure, private and middle men free exchange, it is worth considering problems with the traditional approach of using a 3rd party escrow agent.
In a third party escrow system the buyer and seller ask a third trusted party (an escrow agent) to receive payment from the buyer before the item is delivered and to then release the payment to the seller after the item is delivered. If there is a dispute about the quality of the item or whether it was delivered, the third party acts as a mediator and decides whether to send the payment or refund the buyer. Some of the problems with this model include:
1. Unless escrow agent receives the item before forwarding it to the buyer and has the expertise to verify the quality of the item, they have no means of fairly mediating a dispute. This problem becomes much worse when services are being exchanged. Proof of package delivery doesn't verify what was delivered or it's quality and photos from the buyer could be of another item, etc. Credit cards and centralized marketplaces seem like existence proofs of working escrow agents but they are non-anonymous, do not resolve disputes fairly (as most small merchants can tell you) and are known to break in markets with high fraud potential (e.g. third world countries, porn, etc).
2. Untrustworthy escrow agents who may steal escrow, fail to release escrow or collude with (or be) one of the parties.
3. Escrow agents with strong reputation may be doing a long con to take off with or extort a large payment or group of payments.
4. Escrow agent expenses in the form of labor and risk are passed on to the buyer and seller.
5. Finding a trustworthy agent requires a reputation system and building reputation systems in anonymous networks remains an unsolved problem (see next section).
Fortunately, two party escrow made possible by Bitcoin's multi-signature transactions can solve all of these problems.
### Where Reputation Systems Are Required
For transactions involving goods delivery of the size typical for a users cash transactions, we suspect two party escrow will be sufficient to address the problem of trust. But there are classes of transactions where reputation systems seem to be required. Some examples include:
1. Larger transactions such as those of the size that typically involve debt (e.g. purchasing a car), where the necessary size of security deposits for two party escrow may be prohibitive.
2. Transactions involving services where buyer and seller meet in person (e.g. taxis) pose a risk that either party may physically coerce the other into releasing the transaction or otherwise harm them.
3. Transactions where there are significant cost for non delivery beyond the price of the item (e.g. urgent medical supplies and services, critical parts, deadline driven projects).
4. Transactions where quality is difficult to objectively assess or where the client may not have the expertise to assess it (e.g. professional services).
5. Transactions where a buyer has strong concerns about exposing their physical address to a potentially malicious seller.
### Reputation System Failures
Although reputation systems are useful, there are many potential ways for malicious agents to attack them. Systems that do not address these may be worse than no reputation system at all as they can result in users extending trust to untrustworthy parties or unwittingly exposing their real identities. These attacks include:
1. Build false trust: Posting fake positive reviews by creating many identities and performing transactions and posting reviews between them.
2. Build false distrust: Posting fake negative reviews of competitors.
3. Long cons: completing in good faith many transactions in order to accumulate and then exploit trust extended for a large or large group of transactions.
4. Trusted parties whose computers become compromised. This is not a threat with two party escrow as economic incentives are aligned regardless of the trustworthiness of the parties.
5. Identity leaks via delivery: Attaching reviews or history information to identities in externally verifiable ways can leak information (e.g. timing analysis by combining real world delivery records with online transaction and review times), particularly when gathered over time.
6. Identity leaks via network analysis: Combining known real world connection data (e.g. social network, business or friend networks) with the reputation system's network of reviews and even a single exposed real identity may provide strong hints to the real identities to other relatively direct connections.
While the Bitcoin blockchain and cryptographic signatures could provide a means of externally verifying trade history, when trades where done and whether or not they were completed, it doesn't address the fake review problem and it makes the system potentially more vulnerable to identity leaks. As far as we know, there are no existing reputation systems that sufficiently address any of these issues.
### Trust Network Challenges
Network of trust solutions, where users keep contact lists of other users they trust and potentially share reviews between them solve the fake review problem. However the trade off is having fewer available reviews and that comprising one user can leak the identities of all members of their trust network if the cryptographic identities are tagged with real names.
This problem can be addressed if the client only allows the tagging of trusted reviewer's cryptographic identities with the user's trust level in them. This also requires a more proactive role for the user in requesting these identities from those they trust and doing so in a way that does not record the connection between cryptographic and real identities.
This hidden real identity network of trust system is the one we will likely include in a future version of Bitmarkets. The decision not to include it in the first version was made because it would significantly extend the development time and because two party escrow makes this added complexity unnecessary for a large set of transactions which could be immediately valuable to users.
### Choice of Escrow Ratios
The first version of the Bitmarkets client is configured to use a 1-1 deposit/price ratio. So for the sale of an item costing X, the buyer would put up 2X (1X for payment and 1X for deposit) and the seller 1X so a total of 3X would be locked in escrow.
While later versions will may allow this to be adjustable per transaction, the current fixed ratio was chosen to meet the requirements that:
1. Deposits are large enough that consistently malicious buyers or sellers will on average go bankrupt before destroying the market assuming their bankroll is small compared to the volume of the market.
2. Deposits are large enough incentive to ensure seller will deliver an acceptable product and that buyer releases escrow at end of transaction (signing a payment or refund request).
3. Deposits are small enough that parties are willing to accept losses in order to punish bad actors. The results of the [Ultimatum game](https://en.wikipedia.org/wiki/Ultimatum_game) experiments provide evidence that this is likely for the sizes in which the people might make cash purchases.
4. Deposits are small enough that locking the funds over the period of the transaction wouldn't be a burden for either party in the sizes in which buyer might make cash purchases.
5. Simple enough to be easily understood by users.
The 1-1 ratio seems to be the simplest choice which meets all of these requirements.
*This content is in the public domain.*
---
Source: /projects/past%20projects/Voluntary/Bitpost/
# Bitpost
Private, decentralized messaging.
Bitpost was a user-friendly interface to the Bitmessage network for private, decentralized messaging — created to help keep personal communications private.
[Source on GitHub →](https://github.com/VoluntaryLabs/Bitpost)
---
Source: /projects/past%20projects/Voluntary/Crypt/
# Crypt
Drag-and-drop file encryption.
Crypt was a simple desktop app for encrypting and decrypting files and folders with drag and drop. It used AES-256/CBC encryption and an open file format that can be decrypted on any platform from the command line with openssl. Localized for English, French, and German.
A free web version now runs in your browser: [Crypt](../../../apps/crypt/index.html).
[Source on GitHub →](https://github.com/VoluntaryLabs/Crypt)
---
Source: /projects/past%20projects/Voluntary/TorBar/
# TorBar
Run a Tor relay from your menu bar.
TorBar added a menu bar item for running a Tor relay node (not an exit node) at the click of a button, using a minimal amount of bandwidth. It remembered and would only run the relay on networks you specifically enabled.
[Source on GitHub →](https://github.com/VoluntaryLabs/TorBar)
---
Source: /projects/vertex/
---
title: vertex.js
subtitle: A Node.js graph database server inspired by filesystems.
---
vertex.js is a schemaless graph database built on Node.js. It organizes data as nodes with slots — much like a filesystem — and communicates over HTTP using JSON.
## Motivation
A frequently-edited document can run to multiple megabytes or more, and persisting it as one unit — a row, a file, a blob — means every small edit rewrites the whole thing, with latency and write amplification that grow with it. Decomposing it so each edit touches only its own piece is the fix, but mapping a tree onto flat rows or an ORM adds friction and turns reads into a query per node. vertex.js stores the structure itself as a graph of path-addressed nodes with indexed slots: one edit writes one node (small and immediately durable), any part is reachable by path in log(n) time, and many actions batch into a single round trip. The model *is* the structure — no object-relational mapping in the way, and every path maps to a URL.
[Source on GitHub →](https://github.com/stevedekorte/vertex.js)