CI / quality (push) Failing after 1m4s
Verified by killing rojo and confirming the guard restored it with 34872 owned by 'rojo'. Guard refuses to start if the port is held by anything else (sshd tunnel) rather than masking that bug.
344 lines
15 KiB
Markdown
344 lines
15 KiB
Markdown
# 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.** It was observed dying repeatedly (Studio
|
||
restarts, machine sleep, the one-shot task simply ending). A watchdog handles
|
||
this now:
|
||
|
||
```
|
||
%USERPROFILE%\rojo-guard.ps1 idempotent restart script
|
||
schtasks /Query /TN "RojoTWR-Guard" runs it every 3 minutes
|
||
%USERPROFILE%\rojo-guard.log only logs real events, silent when healthy
|
||
```
|
||
|
||
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 by killing `rojo` and confirming the guard restored it with the 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.)
|