# CLAUDE.md -- Tokyo Wangan Racing Guidance for Claude Code running **natively on `windows-gpu`**, the same machine as Roblox Studio. Pure ASCII on purpose (PowerShell 5.1 reads non-ASCII as ANSI and desyncs the parser). --- ## 1. Read this before touching MapBuilder `src/server/MapBuilder.luau` generates the map procedurally. The user also edits geometry **by hand in Studio**. Those two facts collide, and that collision is the single most important thing about this repo: > **A rebuild regenerates anything the source still describes.** > If the user deleted geometry by hand and you rebuild without teaching the > source about that deletion, you silently put it back. The user WILL notice, immediately, and will say so ("you just added back the X that I deleted"). When that happens: **stop editing, run a read-only probe, report exact numbers.** Do not "fix" it with another rebuild. ### The live invariant: aux-lane suppression ```lua local AUX_SUPPRESS_FROM = 525 -- samples 525..550 have NO aux lane local AUX_SUPPRESS_TO = 550 -- because the user deleted that run by hand local AUX_BLEND = 5 -- smoothstep width ramp, do not hard-cut ``` - **Do not remove or widen this range.** Re-enabling it resurrects deleted geometry. - `AUX_BLEND = 5` is not cosmetic. A hard cut produced a **26-stud lateral fence jog**. The smoothstep removes it. - The parapet is force-closed across the same range (~line 1031). Normally the parapet OPENS along a speed-change lane because the aux lane re-fences the outer edge -- with no aux lane there is nothing to re-fence with, leaving the deck edge bare. That was the measured barrier gap at samples 527..548. Any change touching aux lanes, parapets, or barriers must re-run `tools/probe_regression.luau` and match the baseline below. --- ## 2. Verified baseline (commit 6e50f97) Measured in the live datamodel, not inferred from source: | Metric | Value | |---|---| | total parts | 16788 | | PAWestSlip | 60 segs, 34.4 deg total bend, worst 1.4, 0 kinks >5 | | PAEastSlip | 60 segs, 30.7 deg total bend, worst 1.3, 0 kinks >5 | | AuxLane parts | 30 (samples 525-550 deliberately absent) | | barrier coverage | 823 samples, 0 unguarded | | fence jogs >10 studs | 0 | `AuxLane = 30` is the **user's hand-edited count**, not the source's original 52. If a probe reports 52, a rebuild has undone the suppression -- that is a regression, not progress. --- ## 3. Two channels, different jobs ``` [A] MCP bridge (roblox_studio) -> READ run Luau in Studio, measure, screenshot [B] Rojo -> WRITE push .luau source into Studio (~1 s) ``` **Use [A] to verify what [B] did. Never assume a sync landed.** Rojo syncs **scripts, not built geometry**. Editing MapBuilder replaces the ModuleScript instantly, but `workspace.Map` still holds parts from a PREVIOUS `build()` run. The map looks stale until you re-run the build. Do not tell the user "you'll see it live" about map geometry. **Rojo version must match the Studio plugin.** The plugin auto-updates from the Marketplace without asking. Server older than plugin fails the handshake with a cryptic `attempt to index number with 'protocolVersion'`. Both are pinned at **7.7.0** (`rokit.toml`). If the user reports a connect error, check this first. --- ## 4. Workflow ```bash make check # stylua --check + selene + rojo build make build # rojo build --output build.rbxl make serve # rojo serve (Studio: Rojo -> Connect) make fmt # stylua src/ ``` Probe scripts in `tools/` are **read-only** -- they measure the live datamodel and never build. Run them through the MCP bridge. | Script | Answers | |---|---| | `probe_regression.luau` | full baseline check vs the table above | | `verify_auxlane.luau` | is the 525-550 suppression intact? | | `verify_slips.luau` | slip-road bend angles / kink count | | `probe_barriergap.luau` | any unguarded deck edge? | | `probe_jog.luau` | lateral fence jogs | --- ## 5. Hard-won rules - **Measure, then fix.** Do not infer a bug from symptoms. One inverted convention can produce three unrelated-looking symptoms; fixing each symptom leaves the root cause intact. - **A metric derived from a constant cannot fail.** Ask: would this number differ between the broken and fixed build? If not, it is not a test. - **An empty result set must FAIL, never pass.** Count what you actually exercised; `if tested == 0 then return "NOTHING TESTED -- FAILURE" end`. - **Verification in Play mode proves nothing.** Play is a disposable sandbox discarded on stop. Verify in Edit mode. - **require() caches stale bytecode after a Rojo edit.** The cache still holds old bytecode even though `.Source` is new -- no error, no warning. Clone the module and require the clone. - **Enumerate part names, don't just count.** A raw total hides partial removals (deleted the tall barrier, missed the kerb). Tally what should be gone vs what should remain, and report both. - **AutoRecovery places are silently stale.** After a crash Studio reopens `*_AutoRecovery_1.rbxl` holding pre-crash state. Check the title bar. - **Creator Store assets: 50+ scripts or 1M+ tris is zero-tolerance.** Get exact counts before inserting anything. --- ## 6. Shell Git Bash (`C:\Program Files\Git\bin\bash.exe`) is present -- prefer it. PowerShell over a non-interactive pipe mangles quoting. `&` is not a statement separator in PowerShell. When PowerShell is unavoidable non-interactively, use `-EncodedCommand` with UTF-16LE base64. `StudioMCP.exe` lives inside the versioned Studio install and **moves on every update**. Never hardcode it; `~/claude-tools/studio-mcp.bat` resolves it dynamically.