The PA ring road and the expressway mainline were both at ground level, so
the ring parapet stood INSIDE the carriageway (measured -10.0 studs of
vertical overlap: a wall in the middle of the road).
Reference photos of the real Daikoku Futo PA show the expressway flying
OVER the parking apron on pillars, with the PA at ground level and ramps
climbing up to meet it. Rebuilt to match:
- mainline lifted 38 studs through the junction, eased in/out (max 5.26%
grade, within real expressway limits)
- PA ring + apron stay at ground level; legacy PARingPillar props removed
- both on/off ramps regraded over the full rise with smoothstep easing,
segments overlapped 3 studs so wheels can't catch a seam, plus support
pillars and edge kerbs where the deck is high
Verified in-engine against built parts (tools/diag_daikoku.luau), not just
source: clearance now +28.0 studs, both ramps continuous (max step 5.62,
no gaps). tools/plot_daikoku.py renders the plan/section for comparison.
Road markings now follow the 全標協 (Japan Road Marking Association) 2012/2018
standard rather than eyeballed values:
- Lane dividers: 8 m painted / 12 m gap on a continuous 20 m cycle carried
across segment boundaries (previously each segment restarted its own dash
phase, so the rhythm broke at every joint).
- Line widths corrected to 0.15 m lane / 0.20 m edge. The 0.45 m width often
quoted for expressways applies only to the boundary against a 変速車線
(accel/decel lane) -- now used at ramps only, where it belongs.
- Yellow SOLID no-lane-change lines through tunnels and tight curves, per the
standard's rule that the boundary goes solid where overtaking is undesirable.
- 導流帯 gore zebra at ramp junctions: 45 cm bars at 1.0 m pitch.
Signage:
- Guide-sign green corrected to the official #005D35; route badges #007F1F.
- Bilingual Japanese + romaji layout (JP above, romaji below).
- Added 規制標識 speed limit plates posted at 50 km/h -- the C1 Inner Circular
limit. (80 km/h is the Bayshore Route; the two are not the same.)
- Added キロポスト distance markers every 0.5 km.
Lighting/furniture:
- Tunnel lighting changed from blue/teal neon to warm sodium + cool LED runs,
and made continuous rather than every-other-segment.
- Deck lamps on a ~34 m pitch; added reflective delineator posts.
Verified in-engine by headless measurement, not by inspection: 14/14 geometric
assertions pass (dash length/width, edge width, yellow count, zebra pitch,
light colour, sign colour/bilingual, speed+km posts, markings flush with deck).
Sources and the corrections to widely-repeated wrong figures are recorded in
docs/japanese-road-standards.md.
Wheels: WHEEL_RADIUS was 2.0 (4.0-stud tyre on a 19-stud car = 21% of
length; real cars are ~14%). Derived from SHELL_LENGTH instead -> 2.70
dia, ratio 0.142. Suspension drop 2.2 -> 0.85 and GROUND_OFFSET
correspondingly lowered, dropping the body onto the wheels.
Roof fit, three separate bugs:
1. HEAD_ABOVE_SEAT (3.27) was the head CENTRE, not the crown. A default
R15 head is 1.20 tall so the crown is 3.87 up -- the constant was
short by half a head, which is exactly what poked through.
2. The adaptive seat solve measured the roof from the shell's bounding
box top, which includes spoilers/antennas/stray parts (up to +0.74
of junk). Now uses the largest part's top -- the real roofline.
3. wanted/seatLocal is a pan height but HEAD_ABOVE_SEAT is measured from
the seat CENTRE, so every car silently lost Size.Y/2 of clearance
(hyper_x got 0.07 instead of the requested 0.30).
Also lowered SHELL_DROP 0.6 -> 0.25 and let the seat clamp sink to
GROUND_OFFSET - SHELL_DROP, since hyper_x has the lowest roof (4.64)
and had no headroom to give.
Verified live in Play, avatar seated, all five models INSIDE the roof:
hyper_x 0.33, rx7_fc 0.31, silv_s15 0.37, gtr_r34 0.44, hatch_junk 1.09
Reworked the mainline and Daikoku Futo PA against real references (Wikimedia
aerials of Daikoku JCT + OSM way data for the junction) rather than guesswork.
- Divided carriageways: raised median kerb with dark anti-glare fence down the
centre, matching OSM (lanes=2, oneway=yes per carriageway).
- Edge containment: white concrete parapet topped by a dark metal railing --
the signature Shutoko silhouette -- backed by a kill wall so cars cannot
leave the deck.
- Palette corrected from dark grey to Shutoko pale concrete; asphalt lightened
to mid-grey to read correctly in daylight.
- Daikoku Futo PA: replaced the single straight stub ramp with a full loop road
encircling the parking apron (real loop ramps measured at 48m and 121m
radius), elevated on pillars, reachable from BOTH carriageways via on/off
ramps. Median and barriers open at those junctions so the ramps connect.
- Removed the old four-sided perimeter box; the apron now extends under the
ring, so there is no exposed edge to fall off.
Verified by executing MapBuilder headlessly under a stubbed Roblox API:
build ok, 823 road segs, 797 median segs (26 junction gaps), 1594 barriers +
1594 railings + 1594 kill walls, 72 ring segs at constant 389.6 radius,
apron covering the ring, all decks continuous at Y=46.
Car meshes:
- CarManager: clone ReplicatedStorage/CarModels/<carId> as a visual Shell,
weld to Body, hide placeholder blocks. Falls back to procedural CarBody
when no mesh exists, so cars can be converted one at a time.
- Shell parts are Massless/CanCollide=false/CanQuery=false: the constraint
chassis and driving feel are untouched. Verified 0 violating parts live.
- 5 catalogue cars mapped from the 67-model ServerStorage/CarLibrary import.
Suspension (ref docs/CAR_PHYSICS_REFERENCE.txt, devforum 3915628):
- BUG: old k=9000 gave 1.10 studs static sag against 0.90 studs total travel,
i.e. the suspension sat permanently bottomed out and the spring did no work.
- Stiffness now solved from a sag target (70%% of half travel); damping now
derived as a damping ratio c = zeta*2*sqrt(k*m) instead of an unrelated
constant. Old pair worked out to zeta=0.31 (floaty); now 0.55-0.63.
- Measured after fix: ride height 1.97 studs, bounce 0.000, body roll 0.002.
Also: Circuit.inPA square test (PAFloor is 520x520, test was a r=260 circle,
corners falsely reported outside and spuriously armed runs), and void-fall
recovery now moves the character to the spawn pad BEFORE building the car.
The drive loop was armed purely edge-triggered from
humanoid:GetPropertyChangedSignal("SeatPart") plus a one-shot catch-up
applySeat() at init. On cold boot the server seats the player AFTER that
catch-up runs, so the edge is lost, startDriving() never connects, and all
four wheel motors stay at MotorMaxTorque = 0 - the car is completely
undrivable until the player manually gets out and back in.
Replace the one-shot with a 0.5s level-triggered watchdog: if seated in a
DriveSeat with no live heartbeat connection, arm it; if not seated with a
live connection, stop it. Immune to any ordering of bootstrap vs seating.
Verified on cold boot (no manual reseat): all 4 motors uniform, poisoned
torque values restored within 0.6s, car accelerates to MaxSpeed 163 studs/s,
holds it upright, and engine-brakes 163 -> 0.
Sit() dragged the seat's body assembly ~15,700 studs to the client-owned
character while the knuckle/wheel assemblies stayed at the spawn pad,
tearing the car apart; wheels then fell out of the world.
Server-side character:PivotTo() does not stick (client has authority) --
it holds one frame and snaps back, so the pre-seat teleport could not fix
this. Instead: record each part's offset from the body while the model is
intact, take server network ownership, let Sit() and the snap-back settle,
then reassemble every assembly around the body (forced level) and hand
ownership back to the driver.
Verified in Studio: max part separation 6.42 studs (exhaust, normal),
up-vector 1.00, driver seated, drove 98.6 studs at 41 studs/s intact.
Four independent bugs kept the car immobile at the world origin:
1. Wheels collided with their own chassis. Wheel inner edge sits at 2.80
vs body edge 3.00, and wheel top -0.20 vs body bottom -0.70. The
overlap is intentional styling, but in one collision group the wheels
ground against the body: motors spun ~1 rad/s against 20,000 torque.
Wheels now use a CarWheel group that ignores CarChassis.
2. Suspension exploded on spawn. PrismaticConstraint+SpringConstraint
between a 0.6-stud hub and the ballasted body was unstable enough to
fling knuckles ~10,000,000 studs out, past FallenPartsDestroyHeight,
deleting the wheels. Replaced with a rigid welded hub mount.
3. Car spawned at world origin, not the pad. MapBuilder made a SpawnPad
part but no SpawnLocation, so characters spawned at (0,0,0) and Sit()
dragged the car to them. Added a real SpawnLocation and the model is
now pivoted after parenting, with the character teleported first.
4. AntiCheat snapped that legitimate spawn teleport back. An ~8,500-stud
server move reads as a teleport hack. Added AntiCheat.grant() so
server-initiated moves are exempt for a short grace window.
Verified in Studio: 881 studs in 6s, 496 studs/s top speed, 152 deg
steering, braking 33 -> 0 studs/s, chassis stays upright.
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.
- New shared Chassis.luau: geometry constants + per-car tuning from stats
- CarManager.buildCarModel rebuilt: prismatic+spring suspension per corner,
servo-hinge steering on front knuckles, motor-hinge driven wheels,
hidden dense ballast for anti-roll; VehicleSeat kept as input device only
- New client ChassisController: drives motors/servos from seat
ThrottleFloat/SteerFloat per Heartbeat; speed-sensitive steering lock;
engine braking on throttle release
- Driver gets network ownership of every unanchored part (constraint
assemblies are separate; missing one causes stuttery physics)
- Docs: MESH_SWAP.md rewritten for shell-on-chassis workflow; ROADMAP updated
Per user direction, the MVP is now simply driving cars on the expressway.
- Removed RaceManager and Economy modules entirely
- Slimmed Remotes to DataUpdated / SpawnCar / EquipCar
- ProfileTemplate now just remembers the last driven car (v2)
- Cars: dropped price/rep/upgrade fields; kept identity + driving stats
- CarManager: stat-based tuning only; RX-7 allowlist via GameConfig.canDrive
- DataManager: assign default car on join; downgrade RX-7 if de-allowlisted
- Client: replaced garage/economy UI with a simple car picker + respawn
- Docs: GDD/ROADMAP/README rewritten for drive-only; added docs/MESH_SWAP.md
- Gates: stylua clean, selene 0/0, rojo builds