Commit Graph
43 Commits
Author SHA1 Message Date
jesse 6e50f9718d Aux-lane suppression + PA geometry fixes; probe/verify tooling
CI / quality (push) Failing after 1m9s
MapBuilder.luau
  - Suppress the aux lane across samples 525-550 (AUX_SUPPRESS_FROM/TO),
    honoring the user's hand deletion in Studio so a rebuild no longer
    regenerates it.
  - Smoothstep the width ramp over AUX_BLEND=5 samples on each side. A hard
    cut produced a 26-stud lateral fence jog; the blend removes it.
  - DO NOT remove this suppression. Re-enabling it resurrects geometry the
    user deleted by hand.

rokit.toml
  - rojo 7.5.1 -> 7.7.0 to match the auto-updating Studio plugin.
    Server older than plugin fails the handshake with
    "attempt to index number with 'protocolVersion'".

tools/
  - 34 read-only probe/verify/capture scripts used to measure the datamodel
    instead of reasoning from source.

Verified-good baseline at this commit:
  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 >10st  0
2026-08-06 08:27:47 -04:00
jesse c297b20aed Disable PA on/off ramps; restore mainline to pre-ramp state
Three iterations of the Daikoku PA ramps never read as a real
interchange. Measured why, rather than tuning further:

  * the entry and exit decks INTERSECTED -- 2547 sample pairs within
    30 studs horizontally at <12 studs dY, worst case 0.5 studs apart
    at dY=0.0. On screen that is the bowtie tangle of white rails.
  * both real Daikoku ramps turn the SAME way (clockwise; the guide
    video says "right, right"). This build mirrored them into each
    other.
  * the real arc wraps OUTSIDE the lot and forms its north/east
    boundary. This build cut across the apron interior.

Gated behind BUILD_PA_RAMPS=false rather than deleted -- the radius
was already correct (470 studs vs an OSM-measured mean of 469) and
the merge lands tangent at 0.1-0.2 deg divergence. Both worth keeping.

Also removed two leftovers that would have outlived the ramps:

  * rampOpen punched holes in the mainline's outer parapet, railing
    and kill wall for ramps that no longer exist -- a barrier gap with
    a drop behind it. Sealed via RAMP_EDGE_OPENINGS=false. Verified:
    Barrier/EdgeRail/KillWall = 1646 each = 823 samples x 2 edges,
    0 road samples without a guard within 90 studs.
  * gore zebra hatching painted 1024 bars + 64 boundary lines marking
    a junction that is not there. Sealed via RAMP_GORE_MARKINGS=false.

Verified after a cache-busting fresh rebuild (Studio's require() cache
otherwise serves the previous map and reports a false pass):
  parts 22628 -> 15988
  all ramp part classes = 0
  Road = 823 (unchanged), spawn pad + PASpawn intact
  clip probe TRUSTWORTHY (control +11/-0), CLIPPING=0

Research and rebuild spec kept for when this is revisited:
reference/ramps/BUILD_SPEC.md, INDEX.md (10 CC-licensed Commons
photos), docs/daikoku-reference-spec.md (OSM circle fits).
2026-08-03 21:15:25 -04:00
jesse 1d23c187ad Daikoku PA: two perimeter ramps, far side served by the median opening
Rework the PA junction to match the reference drawing: yellow = PA -> highway
(entry), red = highway -> PA (exit). Entry and exit swap sides (entry now
EAST, exit WEST) and both trunks hug the PA perimeter instead of running
concentric spirals.

The simplification: both carriageways sit at the same deck level, adjacent,
so the median opening IS the far-side access path. The under-deck crossing
and merge runs are deleted outright -- 8 runs collapse to 4, and the
girder-soffit ceiling logic that constrained the crossing goes with them.
Part count 24,210 -> 21,691.

The median opening is now load-bearing rather than cosmetic (it is the only
route from the far carriageway to the ramps), so it widens from 6 to 9
samples per junction.

Orientation is a single flip-able constant (ORIENT) so a mirrored result is
a one-line correction, not a rewrite.

Measured on the built map (not asserted):
  - 600 entry + 600 exit ramp parts
  - max grade 8.00% / 8.27%, under the 9% limit
  - 0 collisions with piers/masts/girders (558 structural parts)
  - segment continuity <= 2.5 studs; trunk->merge handoff <= 1.7 studs
  - merge lands at dY 0.0 (exact deck level), 33 studs lateral
  - median open at junctions: 0 parts vs. control ~7 on plain mainline

Two traps worth recording, both caught by controls:

1. Studio's require() cache silently rebuilt STALE code. The first "passing"
   build reported 17,575 parts and zero ramps, and the map contained
   PAOffRamp/PAOnRamp parts whose names exist nowhere in the current source.
   Every measurement taken before this was describing an old map. Added
   tools/rebuild_fresh.luau, which clones the module to bust the cache; use
   it rather than requiring MapBuilder directly.

2. A reported "median never opens" bug was a bad probe, not a defect. It
   counted `Barrier` (the outer edge parapet, which must NEVER open) instead
   of `Median_<i>`. Re-run against the right name: control 7, junctions 0.

Both were only visible because the probes carry controls -- a 0-hit result
with no control is vacuous. The verification harnesses are kept in tools/
with their controls built in.
2026-08-03 11:53:40 -04:00
jesse badba0f7a4 Fix support columns: cylinder axis transposition + floating ramp piers
Two structural bugs found by measuring built parts, not by eye.

1. PierColumn (pre-existing, all 80 instances). Enum.PartType.Cylinder
   puts the circular faces on the local X axis, so Size.X is the length
   ALONG the axis and Size.Y/Z are the diameter. The code passed
   Size = (8, colH, 8) and then rotated 90deg about Z -- building a disc
   8 studs long and colH wide. Measured: world vertical extent 8.0 studs
   for a column that should stand 35.4, leaving every viaduct pier
   floating with a 13.7-stud gap down to the ground. Only the Size
   components were transposed; the rotation was already right.
   After: extent 35.4, base Y=0.0.

2. PARampPillar. Two problems.
   a) Built as 6x6 square blocks. The underside footage (honmoku_jct
      sheet3 -- mislabeled, it is actually shot beneath Daikoku itself)
      shows every column carrying the ramp decks is a smooth cylindrical
      concrete pier. Now Cylinder, same axis convention as PierColumn.
   b) Height was always segMid.Y - (ringY - 1), cutting the column off
      at Y=45, the underside of the PA apron slab. Correct only while
      the ramp is over the slab; the measured helix climbs up to 1182
      studs beyond the slab edge, where nothing exists at Y=45. Base is
      now chosen per-pillar: apron level over the slab, ground (Y=-1)
      beyond it, matching the established viaduct Pillar convention.
      After: all bases at Y=-1, 0 pancakes, min height/width ratio 1.7.

Ramp geometry, chevron alignment and gore zebra all unchanged:
0 misaligned panels (worst 0.06/0.08 studs), 832 gore bars, merge
mouths 24.0/23.8 studs from the nearest bar.
2026-08-03 06:33:59 -04:00
jesse 6c753505f0 Daikoku ramps: chevrons are barrier panels, not deck paint
Corrected against 4K dusk onboard footage of the junction
(jct_tsubasa_dusk, t=110..140s), which shows the previous pass got the
chevrons wrong in kind, not just in placement.

What the footage actually shows: red-and-white chevron delineators are
VERTICAL PANELS mounted on the outside face of the concrete barrier,
running continuously along the outside of each curve. The first pass
built them as flat red slabs lying on the road surface. On the deck they
were nearly invisible from the driver's seat, which defeats the point --
these panels are the main thing that makes a helix legible from inside
the car. Now modelled as 0.5 x 2.0 x segLen panels on the barrier face,
alternating red/white per segment (Roblox part granularity can't put a
diagonal stripe inside a single panel).

Also fixes a placement bug found by measurement. Offsetting the panel
along the raw radial (segMid - O) is correct on the circular arc but
wrong on the straight tangent tail, where the radial is no longer
perpendicular to travel: the panel line walked diagonally across the
deck instead of tracking its edge, up to 33 studs of along-travel error
over the last ~9 segments. Panels now always offset along the true
perpendicular (rightV); the radial only picks which side is outboard.

Measured on the built geometry:

  PAOnRamp   84 segs   0 misaligned   worst along-travel 0.06 studs
  PAOffRamp  64 segs   0 misaligned   worst along-travel 0.08 studs

(excluding the last 3 segments of each ramp, the merge mouth, which
carries no kerb or chevron by design so the merge stays open)

144 panels total, 72 red / 72 white. Ramp geometry is unchanged: grades
still 1.48% / 1.95%, rotation 344 / 282 deg.

Barrier colour was checked at the same time and left alone -- the green
cast in the night footage is sodium-vapour lighting on pale concrete,
not paint. C_PARAPET stays as is.
2026-08-03 05:45:17 -04:00
jesse f4f95ec9cc Daikoku PA: replace straight ramps with measured helix ramps
The PA on/off ramps were straight sloped decks running directly from the
ring gate to the mainline. Two measured problems:

  1. Grade. Straight ramps came out at 10.6% (entry) and 13.6% (exit).
     Japanese expressway ramps are held to roughly 6% maximum.
  2. Shape. The real Daikoku ramps are helixes -- they spiral down around
     a vertical axis. That is the interchange's single most recognisable
     feature and it was entirely absent.

Radii are measured, not chosen: an OSM Overpass pull of the interchange's
curved ways, each fitted with a Kasa circle fit. The two cleanest fits
(rms < 1.5 m, i.e. genuinely circular rather than merely curved) are
147 m and 124 m -> 525 and 443 studs at 3.571 studs/m.

Construction leaves the mainline tangentially (no step change in heading
at speed), wraps the circle while descending, then runs the tangent line
from circle to ring gate. Junction resolution now also captures the
mainline heading, which the tangential join requires.

Measured on the built geometry in Studio:

           segs   path            drop   grade   rotation
  entry     84    3775 studs      56     1.48%   344 deg
  exit      64    2866 studs      56     1.95%   282 deg

Grade drops 10.6% -> 1.48% and 13.6% -> 1.95%. Continuity is clean
(max Y step 0.99 / 1.31 studs across seams). Verified collision-free
against all mainline samples and the PA ring: closest approach to the
PA centre is 414 / 406 studs against PA_RADIUS 260.

Also adds red shoulder chevrons on the outside of each curve (72 parts),
which is what makes a helix legible from inside the car, and fixes the
kerb loop's merge-mouth test, which referenced the now-removed STEPS.
2026-08-03 03:11:06 -04:00
Hermes 4a702a4fdb docs(daikoku): measured reference spec for PA entrance geometry
Real-world numbers gathered from OSM (Overpass), Esri aerial imagery, and
frame-sampled onboard video. Replaces estimation with measurement.

Key findings:
  * Loop radii MEASURED via Kasa circle fit on OSM ways: 27.5-204 m,
    mean 131 m (469 studs). Two cleanest fits (rms<1.5) are the true
    helixes at 147 m (525 studs) and 124 m (441 studs).
  * 6 of 8 curved ramps turn CLOCKWISE - matches the video captions
    'right, right and you will reach the PA'.
  * FIVE deck layers in the interchange (OSM layer 1-5). Current
    PA_LIFT=56 models only one; faithful build wants 3+ overhead.
  * Real PA footprint 181x174 m (parking) / 203x227 m (rest_area).
    Current APRON 210x200 m already matches well - the divergence is
    SHAPE (square vs rounded wedge), not extent.
  * Scale clarified: PA work uses M=3.571 studs/m, NOT the 4.3 in
    c1_georeference.json (different mapping, 366 m median error).

Video reference archived to NAS 004-video-reference/ (364 MB, 4 routes).
2026-08-03 02:44:46 -04:00
jesse 16cba4ac32 feat(pa): clear generated PA dressing for Creator Store assets
Three changes, all behind flags rather than deletions so the surveyed
geometry is recoverable:

1. Stall paint off (DRAW_STALL_PAINT=false). Row placement math and the
   PACapacity record still compute; they just emit no parts.
2. PARing deck removed. It laid a 44-wide circular band of road deck around
   the PA, measured flush with the apron (both tops y=47.00) and entirely
   inside the apron footprint -- redundant pavement reading as a leftover
   ring where the old barriers were. The arc loop still runs; junctions,
   ramps and flares are still positioned off it.
3. Generated PA structures off (DRAW_PA_STRUCTURES=false): PAHonkan,
   PANibankan, PAToiletNorth, PAKioskWest, PAHutWest + glazing, and the
   props (Lawson, toilet block, fountain, smoking shelters, motorcycle bay,
   EV chargers) and their two signs. OSM survey positions preserved in code
   as the placement reference for the store assets.

Kept: PALowCarBump (gameplay, the 段差 anti-車高短 bumps) and the main
大黒PA sign.

Verified in live Edit datamodel after rebuild: all 13 removed part names
plus *Glazing report absent; PAFloor=1, PAPerimeter=78, PALowCarBump=3,
PARampFlare=2 remain; apron continuity 361/361 raycast sample points hit
solid ground, holes=0 -- no gap left where the ring deck was. 17385 -> 17277.
2026-08-03 01:23:46 -04:00
jesse 3a74e15f6e fix(pa): square perimeter barrier instead of circular ring edge
The remaining barrier was emitted per ring-road ARC segment, so it inherited
the ring's circle (radius ~412 about PA centre). The parking area is a SQUARE
828x828 apron, so an inscribed circle bulges past the lot at the four corners
and cuts inside it at the four edge midpoints -- it sliced across the parking
area rather than following its perimeter, which is the 'second ring'.

PARingEdge/PARingKerb are gone. Replaced with PAPerimeter: four straight runs
along the apron edge, 40-stud segments, 3.0 tall, with gaps opened where the
ramps cross the boundary so the lot is not sealed.

Verified in live Edit datamodel: PAPerimeter n=78, X -4536..-3711 (span 825),
Z -7882..-7057 (span 825) against the 828-stud slab -- square, matching the
apron. PARingEdge ABSENT, PARingKerb ABSENT.
2026-08-03 00:42:48 -04:00
jesse fed0c0ed20 fix(pa): single ring barrier - drop the inner kerb too
The previous commit only swapped the outer parapet+rail for a low edge and
KEPT the inner kerb, leaving two concentric barriers (69 outer + 64 inner)
still ringing the lot. From the driver's seat that is the same ring barrier
the user asked to ditch, and reporting it as 'gone' was wrong.

Inner edge now gets nothing: the parking area and its circulation road are
one continuous surface. Only the outer edge keeps a barrier, since behind
it is a drop off the structure.

Verified in live Edit datamodel: PARingKerb absent from the barrier
inventory (was 64), PARingEdge 69 @ 3.0 studs, parts 17874 -> 17810.
2026-08-03 00:33:41 -04:00
jesse e997f0f73d fix(pa): flush stall paint, drop ring parapet, complete ramp kerbs
Stall markings were 2.2-stud-tall slabs at DECK_Y+0.1, standing 1.2 studs
proud of the tarmac -- 434 concrete ridges across the parking rows. They
are paint, not kerbs: now 0.12 thick, sunk to sit 0.06 proud (verified
against PAFloor at y=47.0). Under tyre resolution.

Ring barrier: the outer edge was a 2.2-wide parapet + metal rail ringing
the whole lot. Replaced with a single 3.0-stud concrete edge (69 parts);
inner stays a 1.2 kerb. PARingWall/PARingRail are gone.

Ramp kerbs: the guard was still a height test (h > 2) after the earlier
h > 8 fix, and segment 0 of both ramps sits at h = 0.5..0.8 -- so the
barrier still began one segment up the ramp and visibly ended in mid-air
at the foot. Per-side probe confirmed seg0 had no kerb on EITHER side.
Height test removed entirely; only the merge mouth is now left open.

Verified in live Edit datamodel: PAOnRamp 13 segs / PAOffRamp 10 segs,
uncovered = merge segment only (seg12, seg9).
2026-08-03 00:23:03 -04:00
jesse 1e41de92fd fix(map): clear PA_LIFT over Honkan; complete ramp kerbs; open the merge
PA_LIFT 38 -> 56. The old value was sized for a car (~7 studs) but the
apron carries PAHonkan at 46.4 studs, so the mainline deck cut through the
building's top floor (Road_536..539). Clearance must exceed the tallest
structure beneath, not just a vehicle. LIFT_RAMP 18 -> 26 so the longer
climb keeps the same grade. Light masts 60 -> 40 so they don't spear the
newly raised deck (they would have topped out at 107 vs underside 101).

Ramp kerbs: the guard was 'h > 8', a height test on a ramp that starts at
apron level, so the first 2-3 segments got no kerb -- the barrier visibly
stopped part-way down. Now 'h > 2'. Conversely the kerb ran all the way to
the mainline end, leaving a 2.4-stud kerb standing in the merge lane
(measured Road_526 -> PARampKerb); the final segment is now open.

Verified in a live Studio datamodel: Honkan roof 93.4 vs deck underside
101.0 (7.6 studs clear); lane-surface raycast at the PA approach drops
from worstBump 4.751 to 0.254 studs.
2026-08-02 23:29:55 -04:00
jesse 6b0e3b75e4 tools: viewport capture + cache-bust rebuild harness
screen_capture DOES work via the bridge (capture_id='ScreenCapture_1');
the prior assumption that the Studio viewport couldn't be captured from
this box was wrong. capture_shots.py sets the Edit camera per shot and
pulls a jpg for each angle.

rebuild_cachebust.luau documents the require() cache trap: pushing new
.Source does not invalidate the per-instance module cache, so a rebuild
silently runs the OLD code and the diagnostic reports already-fixed
failures. Swapping in Clone()s at the same paths forces a fresh compile.
2026-08-02 16:30:20 -04:00
jesse c8dabea53a Daikoku PA: lift mainline over the apron, rebuild both ramps
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.
2026-08-02 15:56:13 -04:00
jesse 8808b919c3 Rebuild Daikoku Futo PA from measured real-world data
The PA was a placeholder: 9 painted stripes and 4 lights on a slab.
Rebuilt it against sourced figures rather than recollection.

Sources:
  * OpenStreetMap way 59608205 (大黒ふ頭パーキングエリア) -- the apron is
    181 m x 174 m / 1.6 ha, i.e. essentially SQUARE, not circular.
  * ja.wikipedia 大黒パーキングエリア -- 首都高速で最大のPA, opened
    1989-09-27. Capacity 小型 341 + 大型 59 + 身障者用 4 = 404, now
    reproduced EXACTLY (11 rows x 31, 20/20/19, 1 row x 4).
  * 本館 on the west side, ベイサイドテラス二番館 on the north, per the
    same article. EV rapid charger (first on a Japanese expressway,
    2008-10-10) and the 段差 bumps retrofitted to deter lowered cars.

Stall dimensions (小型 2.5x5.0 m, 大型 3.3x12 m, 6 m aisles) are
conventional JP parking-design values and are commented as NOT
primary-sourced, unlike the 全標協 road markings. Building footprints
are art-directed to the aerial and flagged as such.

tools/verify_daikoku_pa.luau asserts capacity, containment, block
separation and building placement in-engine: 20 passed, 0 failed.

Two bugs found and fixed by that harness:
  * The hand-placed 小型 Z origin overlapped the 大型 bays by 42 studs.
    The origin is now DERIVED from the 二番館 frontage; clearance is
    61.4 studs. The first version of the overlap test compared
    consecutive head lines (always 0 for back-to-back rows), which was
    meaningless and hid this.
  * push_mapbuilder.py lost a byte whenever a chunk began with a
    newline -- Lua long-bracket literals swallow a newline directly
    after the opening bracket (assembled=65158 vs 65159). It now emits
    a deliberate leading newline; the push verifies match=true.
2026-08-01 19:26:48 -04:00
jesse 7d368d5444 Render C1 structural variety: girder bridges, Ginza overpasses, JCT speeds, 3+3 widening
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.
2026-08-01 18:10:00 -04:00
jesse 967385261d Rebuild road markings, signage and lighting to verified JP expressway spec
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.
2026-08-01 17:08:39 -04:00
jesse 85730f0038 Fix monster-truck stance and head-through-roof clipping
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
2026-08-01 02:08:31 -04:00
jesse 37a83e0e8f Map: divided expressway with median, edge parapets+railings, Daikoku Futo PA ring road
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.
2026-07-31 19:46:53 -04:00
Hermes 0c5ecd2487 Real car meshes + physics-derived suspension + inPA/void fixes
CI / quality (push) Failing after 56s
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.
2026-07-31 19:04:32 -04:00
jesse f70a0b06bb fix(client): stop the chassis watchdog dying on a nil seat during respawn
CI / quality (push) Failing after 47s
The stale-loop check indexed seatPart without a nil guard:

    not seatPart:IsDescendantOf(workspace)

On respawn CarManager destroys the old car, so humanoid.SeatPart is nil for a
frame while heartbeatConn is still live. That path then indexed a nil value,
the error propagated out of the task.spawn thread, and the watchdog - the only
self-healing path in the controller - DIED PERMANENTLY. The car was undrivable
from then on, and the bug looked intermittent because the edge-triggered
SeatPart signal still covered the simple nil -> seat transition.

Nil-check seatPart before indexing, and wrap the whole watchdog body in pcall
so no future error can ever silently kill the loop again (warn instead).

Verified: 5 consecutive respawns then drives to MaxSpeed 163 studs/s at road
level (up=1.00), TIME_ATTACK sessionKm advances 0.003 -> 0.169 km.
2026-07-31 16:15:55 -04:00
jesse c8809efa7b fix(client): re-arm chassis when the drive loop is bound to a stale car
CI / quality (push) Failing after 47s
Respawning left the car undrivable. CarManager.spawn() destroys the old car
and builds a new one, but the client's Heartbeat connection survived and kept
driving the DESTROYED car's motors. Because heartbeatConn stayed non-nil, the
cold-start watchdog concluded the chassis was already armed and never armed
the new car - torque stayed at the idle brake value and the car never moved.

Track the seat the live loop is bound to (drivingSeat) and treat the loop as
stale when that seat isn't the one we're sitting in, or its car has left the
workspace. On stale, stop and re-arm against the new car.

Verified: after 4 respawn cycles the car accelerates 49 -> 163 studs/s,
travels 273 studs, stays upright at road level.
2026-07-31 16:00:06 -04:00
jesse 3ae71f9dd3 fix(client): level-triggered watchdog arms chassis on cold start
CI / quality (push) Failing after 53s
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.
2026-07-31 15:52:12 -04:00
jesse 45069188be fix(CarManager): repair car torn apart on spawn by client-owned character
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.
2026-07-31 13:00:33 -04:00
jesse d45f35736a fix: car now drives - collision groups, rigid hubs, pad spawn, anticheat grace
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.
2026-07-31 10:04:47 -04:00
jesse 766fce5088 test: add automated map verification harness
Eight geometric assertions against the built map, runnable from Linux over
the Studio MCP bridge. Each check tests the world that exists rather than
trusting that a build reported success.

Check 1 is the important one: it fresh-requires Circuit and asserts
823/200/474. Studio caches ModuleScripts, so a stale Circuit will happily
build a plausible-looking map of the wrong circuit -- which is exactly what
happened before. The shape checks are meaningless without it.

Found and fixed while writing this: a Sound with a doubled rbxassetid://
prefix in the imported CarLibrary chassis.

Probe note: pillar tops are flush with the deck by design (0.00 clearance),
so the car-envelope probe must ride fully above the road surface or it
reports its own supports as intrusions.
2026-07-31 08:25:57 -04:00
jesse 0dbd7420d4 map: signature crossings + fix skyline setback clipping the road
buildBridges() adds the two landmark structures the generated road was
missing, driven off the existing per-sample tunnel/bridge flags in
Circuit.luau (200 tunnel / 474 bridge samples, recorded at trace time):

  * NihonbashiViaduct  - box girder carried on piers over the historic
    stone bridge, the C1's most recognisable stretch.
  * Shiodome cable-stayed span - twin H-towers with fanned stays.

Also fixes buildSkyline() placing geometry inside the racing surface:

  * the setback test compared the building CENTRE against 220 studs, so a
    220-wide tower still reached 110 studs inward - through the tunnel
    bore walls. Now tests the outer face (near - w/2) against
    ROAD_WIDTH/2 + 60.
  * it strided samples by 3, skipping the closest approach on tight
    corners. Now tests every sample.
  * NeonSign hangs off the +X face and was never clearance-checked at
    all; it now gets its own test.

Verified in Studio against a full rebuild (823 samples -> 2468 road
parts): 400 tunnel walls / 200 ceilings for the 200 flagged samples,
621 buildings placed, and raycasts over every road part report 0
headroom and 0 lateral obstructions. Minimum building clearance 47.8
studs.
2026-07-31 02:13:55 -04:00
Hermes 1f888b3693 map: rebuild circuit on real Shuto C1 geometry from OpenStreetMap
CI / quality (push) Successful in 56s
Replace the invented rounded-rectangle circuit with the actual Shuto
Expressway Inner Circular Route (relation 4256008) traced from OSM and
projected to studs at 1 stud = 0.28 m.

- Circuit.luau: 823 evenly-spaced samples (60 studs), 49,388 studs /
  13.83 km lap. Real junction names (Tanimachi, Miyakezaka, Kasumigaseki,
  Ginza, Kyobashi, Edobashi, Hitotsubashi), real tunnel + viaduct
  sections. Deck height smoothed to max 4.3% grade.
- MapBuilder.luau: builds road, guardrails, kill walls, lit tunnel bores,
  viaduct pillars, junction gantry signs, Daikoku-style PA, and Tokyo
  landmarks (Tower, Skytree, Imperial Palace moat, Station, Ginza,
  Shiodome, Roppongi Hills, National Diet) at true relative positions.
- Contract names (SpawnPad, FinishLine, KillWall) unchanged, so gameplay
  code is untouched.
2026-07-30 21:26:49 -04:00
jesse be52290093 Add three-mode Wangan loop; rename project to Tokyo Wangan Racing
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.
2026-07-30 20:16:04 -04:00
Hermes 681c3462c6 Rename: Tokyo Wangan Racing (was Fast & Furious: Tokyo Expressway)
- Title in README + GDD, UI/log prefixes FnF -> Wangan
- Rojo project name -> TokyoWanganRacing
- Fix undefined `H` in CarManager seat CFrame (selene error) -> Chassis.BODY_SIZE.Y
2026-07-30 20:03:52 -04:00
jesse e0bee80795 CarBody: sculpted per-car visual body kits (cars-first priority)
CI / quality (push) Successful in 1m5s
- New src/server/CarBody.luau: wedge noses, raked glass, pop-up lights (RX-7),
  spoilers (lip/wing/GT), tail styles, exhaust tips, underglow, accent neon,
  alloy rims. All parts Massless+welded: zero physics impact.
- CarManager: replaced the old glass-cube cabin with CarBody.build().
2026-07-22 08:50:19 -04:00
jesse 0fc0d0ae22 Map v3: Shuto Night Loop — Tokyo landmark circuit
CI / quality (push) Successful in 55s
The straight strip becomes a closed rounded-rect loop (~7,400 studs/lap)
built from centerline samples, Shuto-C1 style. Landmark zones:

- Daikoku Futo PA: drivable meet lot off the south straight (guardrail
  gap entrance), painted bays, flood-light towers, bilingual sign —
  cars now SPAWN at Daikoku like a proper meet
- Rainbow Bridge over the east straight: white suspension towers with
  beacons, cross beams, rainbow-colored rail lights
- Odaiba beyond the bridge: blocky Unicorn Gundam (glowing eyes, horn,
  pink psycho-frame strips) + 16-cabin neon ferris wheel
- Yamate-style tunnel enclosing the whole north straight
- Tokyo Tower replica (orange/white tiers, two lit decks, antenna) west
- Tatsumi PA overlook pad NE
- City-core skyline inside the loop + outer ring skipping landmark zones
- Bilingual green highway signs via SurfaceGui (JP + EN)

Gameplay contract preserved: SpawnPad/FinishLine/KillWall names intact,
kill walls hug every road edge except the Daikoku entrance.
2026-07-22 02:38:47 -04:00
jesse dae30fd22e RX-7 audio v2: turbo whistle + exhaust from the action-cam library
CI / quality (push) Successful in 1m4s
- Scanned b-action-cam-footage/clipchamp (99GB, 5 videos of the FD):
  extracted audio NAS-side via linuxserver/ffmpeg container (stream-copy),
  scanned 69min return drive per-second for engine tonality + whistle
  prominence (1.5-9kHz peak vs band median)
- New rev loop: 985-993s highway pull — 0.0Hz pitch drift over 8s,
  whistle ~1.7kHz at +33dB, exhaust + turbo captured together (seam 0.001)
- New whistle-only loop: same pull high-passed 1.2kHz — isolated spool
- SoundController: optional per-car Whistle layer that rides boost
  (throttle held at speed): fades in past 25% speed, pitch 0.9->1.35
- ROTARY_SOUND.md updated; three .ogg uploads needed in Studio now
2026-07-22 02:26:45 -04:00
jesse 1891f3f11d RX-7 rotary loops: extracted from the owner's real car footage
CI / quality (push) Successful in 49s
- Sourced from c-finished-videos/2026-07-10 sessions (the actual FD)
- Scanned 6 min of audio per-second for RMS/dominant-freq/spectral
  flatness; picked the most tonally-stable stretches (idle: 272-288s
  @ ~99Hz flatness<0.02; rev: 319.5-327s @ ~105Hz, louder)
- Zero-crossing trim + 150ms equal-power tail-to-head crossfade =
  seamless loops (seam jump 0.001 / 0.005); normalized to -3dBFS
- Shipped as assets/audio/rx7_idle_loop.ogg + rx7_rev_loop.ogg (236KB);
  wav masters mirrored to NAS 001-1993 Mazda RX-7/sound/
- ROTARY_SOUND.md updated: only Studio upload + 2 asset IDs remain
2026-07-22 01:59:29 -04:00
jesse b2cc917942 Sound: two-layer engine architecture + rotary pipeline for the RX-7
CI / quality (push) Successful in 43s
- SoundController rebuilt around idle+rev loop crossfading (real racing
  game approach): idle layer barely pitch-shifts (keeps engine character),
  rev layer carries the speed range; crossfade window 5%-45% of top speed
- Per-car CAR_SOUNDS table so the RX-7 gets real 13B rotary loops while
  other cars keep the generic set
- docs/ROTARY_SOUND.md: full record->loop->upload pipeline for capturing
  the owner's REAL RX-7 (extract audio from video, zero-crossing cut +
  equal-power crossfade seam, normalize, ogg, Studio upload, wire IDs)
- Toolbox rotary rips evaluated and rejected: unauditionabe off-platform,
  short/unloopable, murky rights; the real car beats any rip
2026-07-22 01:57:16 -04:00
jesse 2ae3bf8bdb M1/M2: engine + tyre audio, night-city ambience
CI / quality (push) Successful in 56s
SoundController (client):
- Looping engine sound on the car body: pitch 0.7->1.9 tracks speed
  (curved so low speed feels torquey), volume swells with throttle
- Tyre squeal loop faded by lateral slip (velocity along body RightVector)
  and hard braking from speed; always playing at 0 volume to avoid pops
- 3D sounds with rolloff so other players hear cars pass
- Asset IDs from safe sources (Roblox Resources engine loop,
  ProSoundEffects squeal); swap in SOUNDS table after audition

Ambience (server):
- Atmosphere (purple-grey haze, glare) so neon reads in the air
- BloomEffect for neon pop + subtle cool ColorCorrection
- Slow ClockTime ping-pong 22:00->03:00 over 10 min: always night,
  but the sky breathes
2026-07-22 01:52:30 -04:00
jesse e94bb8fcda M1/M2: chase camera + expressway map v2
CI / quality (push) Successful in 52s
CameraController (client):
- Scriptable chase cam only while in the DriveSeat; restores default on exit
- Frame-rate independent damped follow with look-ahead aim point
- Flat horizon (ignores car pitch/roll) so bumps don't shake the view
- Speed-based FOV kick (quadratic ramp) for sense of pace
- Cleans up on Died/despawn

MapBuilder v2:
- Highway extended 2400 -> 3200 studs with distinct feature zones
- Gantry arches (seg 2), enclosed neon-lit tunnel (seg 3), jump ramp +
  landing pair on the right lane (seg 4, left lane stays flat)
- Procedural neon skyline towers flanking both sides (deterministic seed,
  same city every server)
- End kill-wall so you can't sail off the far edge
2026-07-22 01:09:44 -04:00
jesse b3a1044df6 M1: constraint chassis (springs, servo steering, motor wheels)
CI / quality (push) Successful in 54s
- 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
2026-07-22 00:20:59 -04:00
jesse 07a67204ca Scope cut: drive-only MVP (remove racing + economy)
CI / quality (push) Successful in 1m1s
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
2026-07-22 00:11:11 -04:00
jesse 5fc03d611d CI: run on ubuntu-24.04 (rojo 7.5.1 needs glibc 2.39, 22.04 has 2.35)
CI / quality (push) Successful in 1m11s
2026-07-22 00:04:37 -04:00
jesse ceece10a86 Add owner + son Roblox UserIds to RX-7 allowlist
CI / quality (push) Failing after 14m51s
2026-07-21 23:53:00 -04:00
jesse e9aec28af3 Restrict RX-7 to allowlisted UserIds (owner + son demo lock)
CI / quality (push) Failing after 1m0s
- GameConfig: RX7AllowedUserIds allowlist + isRX7Allowed/starterCarFor helpers
- ProfileTemplate: no hard-coded starter; granted per-player by UserId
- DataManager: grants RX-7 to allowlisted players, public car to everyone else
- Economy.buyCar + CarManager.spawn: server-side refuse RX-7 for non-allowlisted
- Cars: RX-7 flagged Restricted; client dealership hides it from non-owners
- Gates: stylua clean, selene 0/0, rojo builds
2026-07-21 23:39:59 -04:00
jesse 9a2f2d4dc9 Initial MVP: drivable RX-7 vertical slice + server-authoritative systems
CI / quality (push) Failing after 5m53s
- Rojo + Wally project; ProfileStore (loleris) for safe saves
- Shared: GameConfig, Cars (RX-7 free starter), ProfileTemplate, Remotes
- Server: DataManager, Economy, CarManager, RaceManager, MapBuilder, AntiCheat
- Client: neon HUD + Garage/Dealership UI (buy/equip/upgrade), race/daily
- Procedural Tokyo expressway (spawn pad, finish line, neon pylons, kill walls)
- CI workflow (Gitea/GitHub), Makefile, selene+stylua clean, rojo builds
- Docs: README, GDD, realistic solo ROADMAP
2026-07-21 23:30:53 -04:00