CI / quality (push) Successful in 1m18s
Daikoku Futo PA becomes the hub (Tokyo Xtreme Racer structure): park, pick a
mode, drive out the exit gate to start the run, pull back into the PA to end it.
Modes:
- CRUISE free roam, banks session + lifetime kilometres
- DUEL gap-based battle: hold 350 studs for 3s to win
- TIME ATTACK solo flying lap, 4 sector splits, saved personal best
New shared modules:
- Circuit.luau one source of truth for loop geometry. progressOf(pos) gives
lap distance, which drives lap detection, sectors and duel gaps
alike, so the map and gameplay can never drift apart.
- Modes.luau mode definitions + duel/time-attack tuning constants
Server-authoritative throughout: ModeManager samples the car real position on
Heartbeat and the client never reports distance, lap time or gap. Teleport and
respawn jumps are rejected from distance accumulation.
ProfileTemplate v3 adds DistanceStuds, BestLapTime, BestSectors, DuelsWon and
DuelsLost; ProfileStore Reconcile migrates existing saves.
Rename: repo, wally package, local dir and stale NAS asset paths all move off
the old fnf-sim name. NAS folder was already 0-tokyo-wangan-racing, so those
doc paths were broken before this.
91 lines
3.7 KiB
Markdown
91 lines
3.7 KiB
Markdown
# Swapping the placeholder RX-7 for the real mesh (M1)
|
||
|
||
The car is now a **constraint chassis** (springs + steering servos + motor-driven
|
||
wheels — see `src/shared/Chassis.luau` and `CarManager.buildCarModel`). The
|
||
placeholder Body/Cabin blocks are just the visual shell riding on that chassis.
|
||
Making the RX-7 real means **replacing the shell, not the chassis**: weld a
|
||
MeshPart body onto the `Body` part and hide the placeholder blocks.
|
||
|
||
Nothing else (saving, the allowlist, car switching, driving feel, the map,
|
||
anti-cheat) needs to change.
|
||
|
||
## Reference photos
|
||
The real car's reference photos live on the NAS (too large to commit to git):
|
||
|
||
```
|
||
unraid-1:/mnt/user/data/yy-roblox-games/0-tokyo-wangan-racing/001-1993 Mazda RX-7/
|
||
```
|
||
|
||
A single downscaled reference is included at `assets/reference/rx7-reference.png`
|
||
for quick lookups.
|
||
|
||
## Step 1 — Get a mesh into Roblox
|
||
Options, easiest first:
|
||
1. **Buy or find a free RX-7 FD model** on the Roblox Creator Marketplace/toolbox,
|
||
or
|
||
2. **Model it** in Blender from the reference photos and import the mesh
|
||
(`.fbx`/`.obj`) via Studio's 3D importer, or
|
||
3. Commission one.
|
||
|
||
You want the car **body shell as a single MeshPart** (or a small welded Model
|
||
with a clear root). Wheels can be separate meshes (see Step 4). Keep the part
|
||
count low (mobile perf). This step needs Studio — mesh assets upload through
|
||
Roblox's asset pipeline.
|
||
|
||
## Step 2 — Store it where the server can clone it
|
||
Put the shell under `ReplicatedStorage/CarModels/<carId>` (e.g.
|
||
`CarModels/rx7_fc`). Two ways to manage it:
|
||
- **Simple:** keep it in the Studio place file as a real Instance (not synced by
|
||
Rojo). Fine for a single hero car.
|
||
- **Version-controlled:** export the model to a `.rbxm` and reference it from the
|
||
Rojo project so it lives in git. See Rojo's `$path` docs for `.rbxm` model files.
|
||
|
||
## Step 3 — Attach the shell in `buildCarModel`
|
||
In `CarManager.buildCarModel`, after the Body part is created:
|
||
|
||
```lua
|
||
local CAR_MODELS = ReplicatedStorage:FindFirstChild("CarModels")
|
||
|
||
-- inside buildCarModel, after `body` exists:
|
||
local shellTemplate = CAR_MODELS and CAR_MODELS:FindFirstChild(car.Id)
|
||
if shellTemplate then
|
||
local shell = shellTemplate:Clone()
|
||
-- Position the shell over the body, then weld it on.
|
||
shell:PivotTo(body.CFrame * CFrame.new(0, SHELL_Y_OFFSET, 0))
|
||
for _, p in ipairs(shell:GetDescendants()) do
|
||
if p:IsA("BasePart") then
|
||
p.CanCollide = false -- the Body part remains the collider
|
||
p.Anchored = false
|
||
end
|
||
end
|
||
local weld = Instance.new("WeldConstraint")
|
||
weld.Part0 = body
|
||
weld.Part1 = shell.PrimaryPart or shell:FindFirstChildWhichIsA("BasePart")
|
||
weld.Parent = body
|
||
shell.Parent = model
|
||
-- Hide the placeholder blocks:
|
||
body.Transparency = 1
|
||
local cabin = model:FindFirstChild("Cabin")
|
||
if cabin then cabin:Destroy() end
|
||
end
|
||
```
|
||
|
||
Cars without a mesh keep the block look automatically — import one car at a time.
|
||
|
||
## Step 4 (polish) — Real wheel meshes
|
||
The chassis wheels are plain cylinders (`Wheel_FL` … `Wheel_RR`). To make them
|
||
look real, weld a wheel MeshPart (CanCollide=false) onto each cylinder and set
|
||
the cylinder's `Transparency = 1`. The cylinder stays the physics tyre — mesh
|
||
collision on wheels is unreliable and slow.
|
||
|
||
## Sizing gotcha
|
||
The chassis geometry (`Chassis.BODY_SIZE`, `WHEEL_RADIUS`, `TRACK_HALF`,
|
||
`WHEELBASE_HALF`) is tuned for a ~6×12 stud footprint. Scale the imported mesh
|
||
to fit the chassis — don't reshape the chassis around the mesh, or the driving
|
||
feel changes for every car.
|
||
|
||
## Verify
|
||
- `rojo build --output build.rbxl` compiles.
|
||
- In Studio: Play → you spawn in the real RX-7 → it drives → it saves.
|
||
- Non-allowlisted test account still can't select the RX-7 (server enforced).
|