Pure ASCII (PS 5.1 reads non-ASCII as ANSI and desyncs the parser).
Records the aux-lane suppression invariant, the verified 6e50f97 baseline,
the read/write channel split, and the probe scripts that check each metric.
5.6 KiB
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
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 = 5is 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
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
.Sourceis 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.rbxlholding 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.