Compare commits
5
Commits
6e50f9718d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae5b26b3eb | ||
|
|
25d1e37750 | ||
|
|
83b3bf80c3 | ||
|
|
74899a7197 | ||
|
|
dc0df9122e |
@@ -0,0 +1,139 @@
|
|||||||
|
# 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.
|
||||||
@@ -0,0 +1,357 @@
|
|||||||
|
# PROJECT-BRIEF.md — What we're building and where everything is
|
||||||
|
|
||||||
|
> **Read this first, before `CLAUDE.md`.**
|
||||||
|
> `CLAUDE.md` tells you *how to operate the tools safely*.
|
||||||
|
> This file tells you *what the game is, why it's built this way, and where the
|
||||||
|
> resources live*. You need both.
|
||||||
|
>
|
||||||
|
> Verified against the live repo, the NAS, and Gitea on **2026-08-06**. Every
|
||||||
|
> path and number below was checked, not recalled.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. The game in one paragraph
|
||||||
|
|
||||||
|
**Tokyo Wangan Racing** is a Roblox night-driving game set on a neon Tokyo
|
||||||
|
expressway, built around a real 1993 Mazda RX-7 the owner actually owns. It is
|
||||||
|
modelled on **Tokyo Xtreme Racer**, not on lap-racing games: **Daikoku Futo PA
|
||||||
|
(the parking area) is the hub.** You park, choose how you want to run, and drive
|
||||||
|
out onto the Wangan. You never "join a match."
|
||||||
|
|
||||||
|
The core bet: **driving feel is the whole game.** Racing, economy, and
|
||||||
|
progression are all deliberately deferred until the car is genuinely fun to just
|
||||||
|
drive around. Do not add game layers on top of a car that doesn't feel good yet.
|
||||||
|
|
||||||
|
### The three modes
|
||||||
|
|
||||||
|
Selected in the PA, armed when you cross the exit gate, ended when you pull back
|
||||||
|
into the PA. Defined in `src/shared/Modes.luau`.
|
||||||
|
|
||||||
|
| Mode | 日本語 | What it is | Challenges? | Timed? |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **CRUISE** | 流し | Free roam, bank kilometres, meet drivers | yes | no |
|
||||||
|
| **DUEL** | バトル | Open a gap and *hold* it — who breaks first | yes | no |
|
||||||
|
| **TIME ATTACK** | タイムアタック | Solo flying lap, sector splits, personal best | **no** | yes |
|
||||||
|
|
||||||
|
Cruise and Duel both allow challenges, so a cruise turns into a battle
|
||||||
|
organically. **Time Attack opts out on purpose** so a hot lap is never ruined —
|
||||||
|
that's a design decision, not an oversight. Don't "fix" it.
|
||||||
|
|
||||||
|
### The circuit
|
||||||
|
|
||||||
|
Closed loop, **~49,400 studs (13.8 km)**, samples evenly spaced 60 studs apart so
|
||||||
|
the loop closes seamlessly. Roughly a 2:46 lap at 300 km/h. Scale is
|
||||||
|
**1 stud = 0.28 m**. Coordinates: `+X = east`, `+Z = north`.
|
||||||
|
|
||||||
|
Landmark zones (themed, **not** geographically accurate): Daikoku Futo PA,
|
||||||
|
Rainbow Bridge, Odaiba (Unicorn Gundam + ferris wheel), Yamate-style tunnel,
|
||||||
|
Tokyo Tower replica, Tatsumi PA, bilingual green signage (大黒PA, レインボーブリッジ,
|
||||||
|
お台場, 辰巳PA).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Where you are in the plan
|
||||||
|
|
||||||
|
Source of truth: `docs/ROADMAP.md` and `docs/GDD.md`. Current position:
|
||||||
|
|
||||||
|
- **M0 — drive-only MVP: DONE.** Spawn, drive, switch cars, respawn, persistence.
|
||||||
|
- **M1 — make driving feel GOOD: IN PROGRESS. This is the current work.**
|
||||||
|
- done: constraint chassis (springs, prismatic suspension, servo steering,
|
||||||
|
motor wheels, engine braking), chase camera, two-layer engine sound
|
||||||
|
- **not done: import the real RX-7 mesh** (`docs/MESH_SWAP.md`)
|
||||||
|
- **not done: real 13B rotary audio** from the owner's actual car
|
||||||
|
(`docs/ROTARY_SOUND.md`) — current loops are stand-ins
|
||||||
|
- **not done: tune accel curve + grip** — needs a Studio playtest
|
||||||
|
- **M2 — more cars + map polish:** map v3 and ambience done; more meshes pending.
|
||||||
|
- **M3+ — racing, economy, hardening, traffic AI:** deliberately not started.
|
||||||
|
|
||||||
|
> **M1's exit test:** *the RX-7 is genuinely fun to just drive around.*
|
||||||
|
> Ship nothing else until that's true. If asked to build a racing/economy
|
||||||
|
> feature, flag that it's M3/M4 and confirm before starting.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Repo layout and architecture
|
||||||
|
|
||||||
|
Repo: `~/roblox/tokyo-wangan-racing` (Git Bash path on this box).
|
||||||
|
|
||||||
|
**Server-authoritative: clients request, the server decides.** The RX-7
|
||||||
|
allowlist and anti-cheat are server-side. Keep the client dumb.
|
||||||
|
|
||||||
|
```
|
||||||
|
ReplicatedStorage/Shared src/shared GameConfig, Cars, Circuit, Modes,
|
||||||
|
Chassis, Remotes, ProfileTemplate
|
||||||
|
ServerScriptService/Server src/server init.server + managers
|
||||||
|
ServerScriptService/ServerPackages ServerPackages ProfileStore (Wally)
|
||||||
|
StarterPlayerScripts/Client src/client HUD, car picker, controllers
|
||||||
|
```
|
||||||
|
|
||||||
|
Mapping is declared in `default.project.json` (Rojo).
|
||||||
|
|
||||||
|
| Module | Responsibility |
|
||||||
|
|---|---|
|
||||||
|
| `src/server/MapBuilder.luau` | **3,479 lines — the big one.** Procedurally builds the entire expressway, PA, ramps, barriers, signage. |
|
||||||
|
| `src/server/CarManager.luau` | Spawns the equipped car; enforces the RX-7 allowlist |
|
||||||
|
| `src/server/ModeManager.luau` | Mode selection, exit-gate arming, mode end |
|
||||||
|
| `src/server/DataManager.luau` | ProfileStore wrapper — session lock, reconcile, Studio mock |
|
||||||
|
| `src/server/AntiCheat.luau` | Speed/teleport sanity check — a speed bump, not a fortress |
|
||||||
|
| `src/server/Ambience.luau` | Atmosphere haze, bloom, 22:00→03:00 clock drift |
|
||||||
|
| `src/shared/Circuit.luau` | The 1,050-line circuit spline — sample positions, PA geometry |
|
||||||
|
| `src/shared/Chassis.luau` | Shared chassis tuning constants |
|
||||||
|
| `src/client/ChassisController.luau` | Drives the chassis client-side (driver owns network physics) |
|
||||||
|
| `src/client/CameraController.luau` | Damped chase cam, flat horizon, speed FOV kick |
|
||||||
|
| `src/client/SoundController.luau` | Two-layer engine crossfade + tyre squeal |
|
||||||
|
|
||||||
|
**Cars:** RX-7 (restricted), City Runabout (public default), Silvia S15, Skyline
|
||||||
|
GT-R R34, stylized hypercar. The **RX-7 is demo-locked** to the owner + his son
|
||||||
|
via `GameConfig.RX7AllowedUserIds`, enforced in `CarManager` and the `EquipCar`
|
||||||
|
remote. Stats only affect feel — no prices, no unlocks in the MVP.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Resources you can reach — and how
|
||||||
|
|
||||||
|
### 4a. Live Roblox Studio (this machine, local)
|
||||||
|
|
||||||
|
Two channels, different jobs. **Use [A] to verify what [B] did.**
|
||||||
|
|
||||||
|
```
|
||||||
|
[A] MCP bridge -> READ run Luau in the datamodel, screenshot the viewport
|
||||||
|
[B] Rojo -> WRITE push .luau source into Studio (~1 s)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `mcp__roblox_studio__execute_luau` — `datamodel_type` **must be `"Edit"`**,
|
||||||
|
never `"Server"` (Server edits a throwaway Play copy that dies on stop).
|
||||||
|
- `mcp__roblox_studio__screen_capture` — Studio's **3D viewport only**. Not the
|
||||||
|
desktop, not Explorer, not Blender.
|
||||||
|
- Rojo: `rojo serve default.project.json --port 34872`, then **somebody must
|
||||||
|
click Rojo → Connect in Studio.** It never auto-connects, and an ESTABLISHED
|
||||||
|
socket on 34872 is idle heartbeat, *not* a sync session.
|
||||||
|
|
||||||
|
Full operational detail, version pins, and the failure decoder: **`CLAUDE.md`**.
|
||||||
|
|
||||||
|
#### Rojo must run natively on Windows — never over an SSH tunnel
|
||||||
|
|
||||||
|
**Rojo runs on the Windows box, in the Windows checkout.** Studio, Rojo and the
|
||||||
|
repo it serves all live on the same machine. Start it with:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
cd /d %USERPROFILE%\roblox\tokyo-wangan-racing
|
||||||
|
%USERPROFILE%\.local\bin\rojo.exe serve default.project.json --port 34872
|
||||||
|
```
|
||||||
|
|
||||||
|
A scheduled task `RojoTWR` does exactly this — `schtasks /Run /TN "RojoTWR"`.
|
||||||
|
(Launching via `start` over SSH does **not** work: the process dies when the SSH
|
||||||
|
session exits. Use the task.)
|
||||||
|
|
||||||
|
**Rojo does not stay up on its own.** The root cause was found on 2026-08-06:
|
||||||
|
**Windows OpenSSH kills the entire process tree when the SSH session exits.**
|
||||||
|
Anything started over SSH with `Start-Process` — including Rojo — dies seconds
|
||||||
|
later. This presents as "Rojo was running a minute ago and now the plugin won't
|
||||||
|
connect," and it is *not* a plugin fault.
|
||||||
|
|
||||||
|
Proof: `rojo procs: 1` inside the SSH session, `rojo procs: 0` from a new
|
||||||
|
session moments later.
|
||||||
|
|
||||||
|
The fix is to hand ownership to **Task Scheduler**, which is not part of the SSH
|
||||||
|
process tree:
|
||||||
|
|
||||||
|
```
|
||||||
|
schtasks /Run /TN "RojoTWR" start rojo detached (survives SSH exit)
|
||||||
|
%USERPROFILE%\rojo-guard.ps1 idempotent watchdog
|
||||||
|
schtasks /Query /TN "RojoTWR-Guard" runs the watchdog every 3 minutes
|
||||||
|
%USERPROFILE%\rojo-guard.log only logs real events, silent when healthy
|
||||||
|
```
|
||||||
|
|
||||||
|
Repo copy of the watchdog: `tools/win/rojo-guard.ps1`.
|
||||||
|
|
||||||
|
> **Never start Rojo with `Start-Process`/`start` over SSH.** Always go through
|
||||||
|
> `schtasks /Run /TN "RojoTWR"`. The guard itself follows this rule internally.
|
||||||
|
|
||||||
|
The guard is deliberately conservative:
|
||||||
|
- if `rojo` already owns 34872 it exits 0 and does nothing (safe mid-session —
|
||||||
|
it will not disturb a live Studio connection)
|
||||||
|
- if the port is held by **anything else** (e.g. an `sshd` tunnel) it refuses to
|
||||||
|
start and logs `PORT HIJACKED`, rather than masking the bug
|
||||||
|
- if `ServerPackages` is missing it logs that instead of letting Rojo silently
|
||||||
|
exit on startup
|
||||||
|
|
||||||
|
Verified end-to-end: killed `rojo`, waited, and the scheduler restored it
|
||||||
|
unattended (`Last Result: 0`, port owned by `rojo`).
|
||||||
|
|
||||||
|
**Clicking Rojo → Connect in Studio is still manual and always will be.** The
|
||||||
|
guard keeps the *server* alive; it cannot press the button. `Rojo_autoReconnect`
|
||||||
|
is `false` in the plugin settings, so after a drop you must reconnect by hand
|
||||||
|
(or flip that setting in the plugin).
|
||||||
|
|
||||||
|
> ⚠️ **Do not reverse-tunnel a Linux Rojo to port 34872.** It was tried
|
||||||
|
> (`ssh -N -R 34872:127.0.0.1:34872`) and it is actively harmful: the port
|
||||||
|
> answers, `/api/rojo` returns valid JSON, and everything *looks* healthy — but
|
||||||
|
> it is serving the **Linux** working tree. Studio then syncs files that are not
|
||||||
|
> the ones being edited on Windows, and `netstat` shows the port owned by
|
||||||
|
> **sshd** rather than `rojo`. If port 34872 is owned by anything other than a
|
||||||
|
> process literally named `rojo`, that is the bug.
|
||||||
|
>
|
||||||
|
> Diagnostic that settles it in one line:
|
||||||
|
> ```powershell
|
||||||
|
> $l=(netstat -ano | Select-String ":34872.*LISTENING"); (Get-Process -Id (($l[0] -split "\s+")[-1])).ProcessName
|
||||||
|
> ```
|
||||||
|
> Expect `rojo`. If you get `sshd`, kill the tunnel.
|
||||||
|
|
||||||
|
#### `ServerPackages` is gitignored — Rojo will not start without it
|
||||||
|
|
||||||
|
`/Packages/`, `/ServerPackages/` and `wally.lock` are **gitignored**, so a fresh
|
||||||
|
clone has no dependencies and Rojo **refuses to start**, exiting immediately:
|
||||||
|
|
||||||
|
```
|
||||||
|
[ERROR rojo] Rojo project referred to a file using $path that could not be
|
||||||
|
turned into a Roblox Instance by Rojo. File $path: ServerPackages
|
||||||
|
```
|
||||||
|
|
||||||
|
That error means *missing Wally deps*, not a broken project file. There is only
|
||||||
|
one dependency (`ProfileStore`, 19 files / 316 KB). Fix with `wally install`, or
|
||||||
|
copy the folder from a machine that already has it. **Rojo exiting on startup is
|
||||||
|
silent from Studio's side — it just looks like Rojo "won't connect."**
|
||||||
|
|
||||||
|
### 4b. Reference material on the NAS (1.5 GB, verified present)
|
||||||
|
|
||||||
|
Host `unraid-1` (10.10.10.43), reachable over SSH. Base path:
|
||||||
|
|
||||||
|
```
|
||||||
|
/mnt/user/data/yy-roblox-games/0-tokyo-wangan-racing/
|
||||||
|
```
|
||||||
|
|
||||||
|
| Folder | Contents (verified counts) |
|
||||||
|
|---|---|
|
||||||
|
| `001-original-images/001-1993-Mazda-RX-7/` | **12 files** — the real car, for the mesh swap |
|
||||||
|
| `001-original-images/002-1993-Nissan-Skyline-GT-R/` | **38 files** |
|
||||||
|
| `003-google-earth-images/` | **12 files** — `daikoku-futo-01..12.png`, aerial geometry |
|
||||||
|
| `004-video-reference/` | `aerial/`, `contact-sheets/`, `docs/`, `entrance-routes/` |
|
||||||
|
| `002-completed-models/` | empty — finished models land here |
|
||||||
|
|
||||||
|
> ⚠️ **The README currently prints a stale path** for the RX-7 reference
|
||||||
|
> (`001-1993 Mazda RX-7/` with spaces, at the top level). The real path is
|
||||||
|
> `001-original-images/001-1993-Mazda-RX-7/` with hyphens. Use the real one.
|
||||||
|
|
||||||
|
Copy a file over rather than working blind:
|
||||||
|
```bash
|
||||||
|
scp unraid-1:'/mnt/user/data/yy-roblox-games/0-tokyo-wangan-racing/003-google-earth-images/daikoku-futo-01.png' /tmp/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4c. Reference material committed in-repo
|
||||||
|
|
||||||
|
- `reference/ramps/` — 12 integrity-checked photos of Daikoku/Oi/Heiwajima/
|
||||||
|
Shibaura PAs, plus `INDEX.md`, `BUILD_SPEC.md`, and OSM/satellite scripts.
|
||||||
|
**All CC BY / CC BY-SA — reference-only for shape study. Do not trace directly
|
||||||
|
into shipped assets without attribution.**
|
||||||
|
- `docs/daikoku-reference-spec.md` — measured spec for the PA entrance geometry
|
||||||
|
- `docs/japanese-road-standards.md` — real road-geometry standards
|
||||||
|
- `docs/CAR_PHYSICS_REFERENCE.txt`, `docs/MESH_SWAP.md`, `docs/ROTARY_SOUND.md`
|
||||||
|
- `assets/audio/` — current stand-in RX-7 loops (idle / rev / whistle)
|
||||||
|
- `audio-work/` — raw recordings from real car meets, for the rotary audio work
|
||||||
|
- `c1_georeference.json` — georeferencing data behind the circuit
|
||||||
|
|
||||||
|
### 4d. Git / CI
|
||||||
|
|
||||||
|
Remote: `ssh://[email protected]:2222/jesse/tokyo-wangan-racing.git`
|
||||||
|
(self-hosted Gitea on UNRAID-1, SSH key auth already working from this box).
|
||||||
|
|
||||||
|
CI runs on a **self-hosted Gitea Actions runner** (`.gitea/workflows/ci.yml`),
|
||||||
|
Ubuntu 24.04 job container — rojo's binary needs glibc 2.39.
|
||||||
|
|
||||||
|
### 4e. Existing tooling — check before writing new probes
|
||||||
|
|
||||||
|
`tools/` already holds **109 files, 58 of them diagnostic/verification scripts**
|
||||||
|
(`diag_*.luau`, `verify_*.luau`, `capture_*.py`, `check_*.luau`). There is a
|
||||||
|
very high chance a probe already exists for what
|
||||||
|
you need — `verify_auxlane.luau`, `verify_shared_ramps.luau`,
|
||||||
|
`check_live_source.luau`, `scripts/verify_map.luau`, and capture helpers for
|
||||||
|
flyovers/ramps/defects. **Look there first.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Quality gates — CI enforces all three
|
||||||
|
|
||||||
|
```bash
|
||||||
|
stylua --check src/ # formatting
|
||||||
|
selene src/ # lint
|
||||||
|
rojo build --output build.rbxl # must compile
|
||||||
|
```
|
||||||
|
|
||||||
|
Conventions:
|
||||||
|
- Tunables go in `GameConfig`, not scattered magic numbers.
|
||||||
|
- Add a save key by adding it to `ProfileTemplate` (auto-reconciles). Never
|
||||||
|
remove a key without a migration.
|
||||||
|
- Test in Studio after every server change (ProfileStore uses a mock in Studio).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. The things that will bite you
|
||||||
|
|
||||||
|
These are compressed from hard-won experience. The long versions live in
|
||||||
|
`CLAUDE.md` — but these three matter most:
|
||||||
|
|
||||||
|
### Never regenerate hand-deleted work
|
||||||
|
**The single most important rule on this project.** If the user deleted
|
||||||
|
something in Studio, the *source* must honor it or the next rebuild silently
|
||||||
|
resurrects it — and they will catch it immediately.
|
||||||
|
|
||||||
|
Live example, currently in `MapBuilder.luau`:
|
||||||
|
```lua
|
||||||
|
AUX_SUPPRESS_FROM = 525 -- user deleted this aux-lane stretch
|
||||||
|
AUX_SUPPRESS_TO = 550
|
||||||
|
AUX_BLEND = 5 -- smoothstep; a hard cut caused a 26-stud fence jog
|
||||||
|
```
|
||||||
|
**Do not remove that suppression.** Hand-placed assets live in
|
||||||
|
`workspace.PAProps`, which the builder never touches. Before any rebuild, ask:
|
||||||
|
*does this regenerate something removed by hand?*
|
||||||
|
|
||||||
|
### Measure before reasoning, and re-measure after
|
||||||
|
Write a **read-only probe**, get real numbers, *then* form a theory. A code
|
||||||
|
change is not proof — verify against the bug's signature. Enumerate names, not
|
||||||
|
just totals: a raw count hides a partial removal.
|
||||||
|
|
||||||
|
### Beat the `require()` cache
|
||||||
|
After a Rojo sync the datamodel still holds **old bytecode** — no error, no
|
||||||
|
warning, and you will measure the previous build and believe it:
|
||||||
|
```lua
|
||||||
|
local tmp = game.ServerScriptService.Server.MapBuilder:Clone()
|
||||||
|
tmp.Parent = game.ServerScriptService
|
||||||
|
local MapBuilder = require(tmp) -- fresh bytecode, guaranteed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verified-good baseline (2026-08-06) — regression-check against these
|
||||||
|
|
||||||
|
| metric | value |
|
||||||
|
|---|---|
|
||||||
|
| total parts | 16,788 |
|
||||||
|
| PAWestSlip | 60 segs, 34.4° total bend, worst 1.4°, 0 kinks >5° |
|
||||||
|
| PAEastSlip | 60 segs, 30.7° total bend, worst 1.3°, 0 kinks >5° |
|
||||||
|
| AuxLane parts | 30 — **samples 525–550 deliberately absent (user-deleted)** |
|
||||||
|
| barrier coverage | 823 samples, 0 unguarded |
|
||||||
|
| fence jogs >10 studs | 0 |
|
||||||
|
|
||||||
|
### Never fabricate
|
||||||
|
If Studio is closed, say "Studio is closed." Never invent a screenshot, a part
|
||||||
|
count, or a measurement you didn't obtain. `{'error': '', 'isError': True}` on
|
||||||
|
every MCP call means **Studio is closed** — not that your script is broken.
|
||||||
|
Reporting a blocker honestly always beats a plausible-looking fake.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. First five minutes in a new session
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tasklist | findstr /I "Roblox" # 1. is Studio even running?
|
||||||
|
claude mcp list # 2. bridge registered + connected?
|
||||||
|
curl -s http://127.0.0.1:34872/api/rojo # 3. rojo serving?
|
||||||
|
cd ~/roblox/tokyo-wangan-racing && git status --short && git log --oneline -5
|
||||||
|
```
|
||||||
|
|
||||||
|
Then read `docs/ROADMAP.md` to confirm what milestone is active, and check
|
||||||
|
`tools/` for an existing probe before writing a new one.
|
||||||
|
|
||||||
|
**Working state as of 2026-08-06:** clean tree, 0 uncommitted files, HEAD at
|
||||||
|
`dc0df91`. (An older note in `START-HERE.md` warns about "36 uncommitted files"
|
||||||
|
on the Linux box — that has since been committed and pushed. Trust `git status`
|
||||||
|
over that note.)
|
||||||
@@ -102,4 +102,5 @@ the `EquipCar` remote).
|
|||||||
|
|
||||||
One function changes: `CarManager.buildCarModel`. Full step-by-step in
|
One function changes: `CarManager.buildCarModel`. Full step-by-step in
|
||||||
`docs/MESH_SWAP.md`. Reference photos live on the NAS at
|
`docs/MESH_SWAP.md`. Reference photos live on the NAS at
|
||||||
`unraid-1:/mnt/user/data/yy-roblox-games/0-tokyo-wangan-racing/001-1993 Mazda RX-7/`.
|
`unraid-1:/mnt/user/data/yy-roblox-games/0-tokyo-wangan-racing/001-original-images/001-1993-Mazda-RX-7/`
|
||||||
|
(12 files). See `PROJECT-BRIEF.md` for the full reference-asset inventory.
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# rojo-guard.ps1 -- keep Rojo alive for Tokyo Wangan Racing.
|
||||||
|
# Idempotent: starts Rojo only if it is not already listening on 34872.
|
||||||
|
# Safe to run every few minutes from Task Scheduler.
|
||||||
|
|
||||||
|
$ErrorActionPreference = "SilentlyContinue"
|
||||||
|
$repo = Join-Path $env:USERPROFILE "roblox\tokyo-wangan-racing"
|
||||||
|
$exe = Join-Path $env:USERPROFILE ".local\bin\rojo.exe"
|
||||||
|
$port = 34872
|
||||||
|
$log = Join-Path $env:USERPROFILE "rojo-guard.log"
|
||||||
|
|
||||||
|
function Log($m) { "$(Get-Date -f 'yyyy-MM-dd HH:mm:ss') $m" | Add-Content $log }
|
||||||
|
|
||||||
|
# Is something already LISTENING on the port?
|
||||||
|
$listening = netstat -ano | Select-String ":$port\s.*LISTENING"
|
||||||
|
|
||||||
|
if ($listening) {
|
||||||
|
$ownerPid = ($listening[0] -split "\s+")[-1]
|
||||||
|
$owner = (Get-Process -Id $ownerPid).ProcessName
|
||||||
|
if ($owner -eq "rojo") { exit 0 } # healthy, nothing to do
|
||||||
|
Log "PORT HIJACKED by '$owner' (pid $ownerPid) -- not starting. Kill it first."
|
||||||
|
exit 1 # sshd tunnel or similar: do NOT mask it
|
||||||
|
}
|
||||||
|
|
||||||
|
# Dependencies must exist or rojo exits instantly.
|
||||||
|
if (-not (Test-Path (Join-Path $repo "ServerPackages"))) {
|
||||||
|
Log "ServerPackages MISSING -- rojo would exit on startup. Run 'wally install'."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Launch DETACHED via Task Scheduler.
|
||||||
|
# Do NOT use Start-Process here: if this script is ever invoked over SSH,
|
||||||
|
# Windows OpenSSH kills the whole process tree on session exit and rojo dies
|
||||||
|
# seconds later. schtasks hands ownership to the scheduler, which survives.
|
||||||
|
schtasks /Run /TN "RojoTWR" | Out-Null
|
||||||
|
Start-Sleep -Seconds 4
|
||||||
|
|
||||||
|
if (Get-Process rojo -EA SilentlyContinue) { Log "started rojo on $port" }
|
||||||
|
else { Log "FAILED to start rojo" }
|
||||||
Reference in New Issue
Block a user