Four fidelity fixes, all verified in a live Studio datamodel (not just built): 1. Wire up the dead `bridge` flag. Circuit.luau marks 474 samples as bridge spans but nothing ever read the field, so every elevated metre rendered as the same slab on square columns. Flagged spans now get a steel box girder (鋼箱桁橋) with stiffener ribs under the deck, carried on a transverse pier cap and round columns at a wider pitch than the viaduct pillars. 2. Ginza 跨道橋. The Ginza section sits below street level in the filled-in 楓川・築地川 river beds and the surviving city bridges cross over it, their piers landing in the median. Adds the crossing decks, parapets and centre piers -- which is the physical reason lane changes are banned there, not just a paint choice. 3. Junction speed plates. 江戸橋JCT内 / 浜崎橋JCT内 / 京橋JCT-旧白魚橋乗継所 are posted 40 km/h, not the 50 km/h mainline limit; plates repeat more often through the short restricted stretches. 4. Shiodome JCT -> Hamazakibashi JCT widened to 3+3 lanes via widthAt(), with a taper (max ~1.7 studs/sample) instead of a step. Every fixed-width consumer -- deck, median, parapets, lane marks, signage offsets, skyline setback, finish line -- now reads widthAt() so nothing floats or clips. Verified headlessly (tools/verify_c1_fixes*.luau): girders 488 / 474 flagged samples, pier caps 85, columns 80 Ginza overpasses 7, piers 7, lateral offset from centreline 0.000 studs speed plates 40km/h x7, 50km/h x8 deck width Shiodome 81.5 vs Kasumigaseki 60.0 seam lip max 0.0006, road-surface raycast step max 0.260, 0 deck holes Two earlier "failures" were bad tests, not bad geometry, and the fixed harnesses document the traps: deck parts are pitched by CFrame.lookAt so Position.Y+Size.Y/2 is not the surface height (and LookVector is -Z, so the end faces are easy to swap), and a seam raycast must filter to the Expressway folder or it hits TunnelCeiling/MedianFence and reports a phantom 19-stud step.
Tokyo Wangan Racing
A Roblox game where you cruise a neon Tokyo expressway at night in cool cars —
starting with a real 1993 Mazda RX-7. The driving feel comes first; everything
else layers on top of it. See docs/ROADMAP.md for what comes next.
Status: Playable. Spawn → drive → switch cars → respawn, plus the full three-mode Wangan loop. CI green on a self-hosted Gitea runner. Built code-first — nothing to hand-build in Studio.
The Daikoku loop (how you play)
Daikoku Futo PA is the hub, exactly like Tokyo Xtreme Racer. You are never "joining a match" — you park, pick how you want to run, and drive out.
IN THE PA ──► pick a mode ──► cross the exit gate ──► RUNNING
▲ │
└──────────────── pull back into the PA ─────────────────┘
| Mode | 日本語 | What it is |
|---|---|---|
| CRUISE | 流し | Free roam. Bank kilometres, meet other drivers. Rivals can pull alongside and challenge you. |
| DUEL | バトル | Hunt rivals. First to open a ~350-stud gap and hold it for 3s wins. No laps, no checkpoints — who breaks first. |
| TIME ATTACK | タイムアタック | Solo flying lap with 4 sector splits and a saved personal best. Rivals can't interrupt you. |
Cruise and Duel both allow challenges, so a cruise can turn into a battle organically — pull alongside someone and the prompt appears for both of you. Time Attack deliberately opts out so a hot lap is never ruined.
Kilometres, lap times and duel records all persist via ProfileStore.
Prerequisites
Toolchain is managed by Rokit. With Rokit on
your PATH:
rokit install # rojo, wally, selene, stylua at pinned versions
wally install # restores ProfileStore into ServerPackages/
| Tool | Purpose |
|---|---|
| rojo | Sync/build code ↔ Roblox place |
| wally | Package manager (ProfileStore) |
| selene | Static analysis / linter |
| stylua | Code formatter |
Build & run
rojo build --output build.rbxl # standalone place file to open in Studio
# OR live-sync into an open Studio place (install the Rojo Studio plugin):
rojo serve
In Studio press Play. You spawn on the expressway in your car. Bottom bar: CARS (switch car) and RESPAWN (re-spawn if you flip). The RX-7 only appears for allowlisted accounts.
Quality gates (CI enforces these on every push)
stylua --check src/ # formatting
selene src/ # lint
rojo build --output build.rbxl # must compile
CI runs on a self-hosted Gitea Actions runner on UNRAID-1 (Ubuntu 24.04 job
container — rojo's binary needs glibc 2.39). Workflow: .gitea/workflows/ci.yml.
Architecture (server-authoritative)
Clients request, the server decides. The RX-7 allowlist and anti-cheat are server-side.
ReplicatedStorage/Shared GameConfig, Cars, ProfileTemplate, Remotes
ServerScriptService/Server init.server + managers (below)
ServerScriptService/ServerPackages ProfileStore (Wally)
StarterPlayerScripts/Client init.client (HUD + car picker)
| Module | Responsibility |
|---|---|
DataManager |
Wraps ProfileStore — session locking, reconcile, Studio mock. Remembers your last car. |
CarManager |
Spawns the equipped car (VehicleSeat-driven placeholder) and enforces the RX-7 allowlist. |
MapBuilder |
Procedurally builds the expressway, spawn pad, neon pylons, kill walls. |
AntiCheat |
Movement sanity check (speed/teleport) — a speed bump, not a fortress. |
The RX-7 demo-lock: GameConfig.RX7AllowedUserIds holds the owner's + son's
Roblox UserIds; only they can select/drive the RX-7 (enforced in CarManager and
the EquipCar remote).
Replacing the placeholder RX-7 with the real mesh
One function changes: CarManager.buildCarModel. Full step-by-step in
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/.