Surfaces, moving support and handling verification
Current evidence cutoff: 15 September 2026, 15:19:56 Vienna Windows build. The actual production bridge fixture passed 79/79 at 13:19:08 UTC after correcting stationary transverse slope creep. Regression then passed surface physics 103/103 at 13:19:17 UTC, bank physics 118/118 at 13:19:25 UTC, and campaign geometry/progression contracts 95/95 at 13:19:32 UTC. Each native fixture reports restored scene/global state. Full-stage moving-bridge solo/traffic runs, final pictures and loading-cycle memory acceptance are still separate.
The two original Wood sections previously remained static road quads despite the passing separate bridge prototype. They now have actual moving deck/approach geometry, and Alps02 has a natural-hollow stone crossing. See PRODUCTION-BRIDGES.md for exact geometry ownership, revisions, the preserved failed first report and the successful expanded production fixture. The surface-prototype results below do not independently establish production bridge acceptance.
The production fixture exposed a real stationary-tire defect: countdown compensated gravity longitudinally, but sideways resistance required continuing slip. Jungle's held stationary bike drifted 1.999301m. Brake-held low-speed static traction now acts through forces in both ground-tangent axes, relative to measured deck velocity, bounded by normal load times grip and available brake force. No world-position binding or velocity assignment is used. It applies below 1m/s without throttle, reaches full effect below 0.35m/s at full brake, and leaves ordinary driving forces above 3.6km/h unchanged. Corrected 15-second Jungle countdown/player-brake tests show 0.011401m maximum lateral error and 0.018828m planar displacement; Canyon shows 0.001254m lateral and 0.006488m planar. The original 1.25m lane gate and additional 0.25m planar-displacement gate passed. Active braking on actual static inclined approaches also passed.
Campaign bank defect and physical-road revision, 15 September 2026
The later eight-rider Alps01 run in 20260915-133548-campaign failed. At simulation time 22.5165s, roughly road distance 939.5m on the right-hand +4m lane, its capsule hit Road surface 336 with a measured normal speed of 9.02m/s and impulse/mass of 7.80m/s. Vertical velocity rose from +0.205m/s immediately before collision resolution to +8.804m/s afterwards. The earlier opponent kick occurred around 9.9s; stability had recovered to 1 before this road impact. This failure is not an accepted normal crash or a reason to relax checkpoint rules.
The original incident, invocation and historical road snapshot remain unchanged. Their hashes, exact collision vectors and the numerical reconstruction are in alpine-bank-diagnosis.json. At stations 375–378 the old bank was +5.099°, +2.706°, −3.765°, −5.157°. In a single 2.499m interval the +4m lane rose 0.389m, giving a 15.54% uphill ramp while the centreline continued downhill at approximately 2.49%. The quad vertices are continuous: the defect is a sharp grade change from the rapid bank reversal, rather than a gap between road chunks. The second triangle's reconstructed normal predicts 9.023m/s against the actual pre-impact velocity, matching the native incident.
RoadBankProfile now constructs campaign bank as a spatial cubic B-spline with C2 continuity. A compact polynomial convolution initially spans a 150m radius; it widens if the coefficient bounds require it. The first derivative is bounded by 0.0015 radians/metre and the second by 0.00004 radians/metre². These are analytic bounds over the whole spline, obtained from first and second coefficient differences; they are not just assertions at a few sample points. Bank magnitude stays within the existing ±0.09 radians. The profile is computed once per course, and evaluation is constant-time. The production road vertices and TrackRuntime.Sample use the same bank profile; AI and suspension guidance therefore see the same bank as the road builder. The collision ribbon remains a tessellated surface with its existing eight strips, so C2 refers to the generating bank profile, not a claim that planar triangle normals are themselves C2.
The change does not alter any centreline coordinates, endpoints, rider forces, gate corridor, steering tuning or recovery conditions. Classic circuits retain their previous bank branch. Explicit historical campaign definitions with the old content revision also retain that old branch, allowing faithful regression reconstruction.
Physical compatibility is separate from the geometry-only number: ordinary current campaign ContentRevision = 1,000,000,000 + geometryRevision. Alps01 therefore has physical revision 1,000,000,002 while its geometry remains revision 2; Alps03 has 1,000,000,003 with geometry revision 3. The later physical bridge integration uses 1,300,000,000 plus geometry revision: Alps02 is now 1,300,000,003, Jungle02/Canyon02 are 1,300,000,001. The decoder handles both namespaces without changing the bank algorithm. Loading an older custom authored bake upgrades its physical revision; a fresh bake stores the new value. CampaignStageSceneEntry includes the appropriate physical revision when resolving edited splines. Old checkpoints and revision-specific best times/challenges no longer match the changed physical road. Previously unlocked tour progress remains governed by the existing progression rules. The original Alps01 points-then-controls float32 hash remains f52127a9cb3e87d78501481a0fea57ea4e44cf1eecfec8f8a19921a6b52a6221; a centreline hash alone is insufficient to establish collision equivalence. Native reports additionally include collider hashes.
Offline reconstruction across all 21 routes passed the profile bounds, with maxima of 0.0013122 radians/metre and 0.000029868 radians/metre². At the identified +4m lane segment the revised road now descends 0.0609m instead of rising 0.3893m. campaign-bank-offline-verification.json records source hashes and per-route values; reproduce with python Tools/Verification/audit-campaign-bank.py. This calculation does not simulate PhysX or count as a post-fix driving pass.
The native entry point is Rampage.Editor.AlpineBankVerification.Run, writing alpine-bank-verification.json. The latest native repeat passed 118/118 at 13:19:25 UTC, including the subsequent stationary-tire correction. It checks all 21 current-versus-historical centreline hashes, physical revision invalidation and classic isolation, then drives actual BikeController bodies through the defect on lanes 0, +4, +5, −4 and −5m. Each lane has a 190km/h initial-seed trial and a 220km/h initial-seed/boost trial. After initial settling and that single declared velocity assignment, the fixture uses only held throttle and steering pursuit: no position correction, speed assignment, brake assist or checkpoint rescue. It requires actual speed and lane at the defect, planted contact, bounded road impact and intact ordered gates. A separately generated historical revision2 road is the old-defect positive control. Scene/catalog/global state restoration passed. This bounded native pass is distinct from full-route traffic and visual acceptance.
Implementation: 15 September 2026. The actual native surface fixture passed 103/103 checks; production-course and human acceptance remain separate.
BikeSurfaceProperties.For(RidingSurface) supplies shared coefficients for the controller and AI. These are deliberate arcade tuning values, not measured tire coefficients. Every named surface remains legal layer-8 road, so changing asphalt to snow does not reject checkpoints or generate a road-departure haptic pulse. Boost remains available on a grounded legal road and receives the same surface-dependent propulsion scaling.
| Surface | Lateral grip | Drive traction | Brake traction | Extra rolling deceleration / speed, 1/s |
|---|---|---|---|---|
| Asphalt | 1.00 | 1.00 | 1.00 | 0 |
| Gravel | 0.78 | 0.90 | 0.82 | 0.022 |
| Earth | 0.74 | 0.86 | 0.80 | 0.027 |
| Sand | 0.60 | 0.72 | 0.68 | 0.055 |
| Snow | 0.58 | 0.76 | 0.63 | 0.028 |
| Wood | 0.88 | 0.96 | 0.92 | 0.010 |
| Off-road shoulder | 0.65 | 0.55 | 0.78 | 0.025 |
Each axle uses its actual contacted surface. Their coefficients are averaged and smoothly approached with a 0.14s response. Braking distance, coast-down, engine force, corner allowance and slip correction therefore all change together. The existing aerodynamic drag, shared gear/RPM authority, 1,500rpm upshift entry and transmission safety remain intact. Section speed recommendations inform brake assist and AI; the player is not hard-clamped to a section speed.
Suspension damping and tire slip subtract the actual contacted rigidbody's GetPointVelocity. The separate MovingBridgeDeck uses one kinematic body for visible planks and collision, with 0.10m vertical amplitude at 0.16Hz and 1.2° roll at 0.12Hz. The controller does not attach or teleport the rider to the deck. Actual production bridge geometry must omit the overlapping static road collider; the physics fixture explicitly disables that overlap. Fresh checkpoint loads can call the deck's deterministic ResetMotion().
Native fixture
Execute Rampage.Editor.SurfacePhysicsVerification.Run in a root-coordinated headless Editor session. Output: Docs/QA/surface-physics-verification.json.
The 21 independent trials invoke the actual controller and bridge motion in isolated preview PhysicsScenes:
- Six surfaces each receive a drive/boost/side-impulse trial, a full-brake trial from 80km/h and a three-second coast trial from 80km/h.
- Stopping distances and coast speed are compared against asphalt. Initial velocity is assigned only before those independent measurement windows; no correction occurs during a trial.
- The transition trial physically drives asphalt → snow → gravel → wood and verifies smooth coefficients, legal road progress and no material-change rumble.
- Stationary and moving riders use the actual oscillating 40m deck. They must measure real support velocity, retain contact and cross both boundaries without crash or recovery. The stationary trial spans complete motion cycles.
The fixture also checks native scene/global-state restoration. Its haptics checks use the production event state with actual support flags; they do not establish what a physical controller feels like.
The latest Unity 6000.3.24f1 repeat at 13:19:17 UTC passed 103/103, with restored global/scene state, 100% road/support contact and zero crashes, recoveries, resets or haptic pulses in all 21 trials. It includes the bounded stationary-tire correction. Results are in surface-physics-verification.json; values below are from that report.
| Surface | Stop distance from 80km/h | Stop time | Speed after 3s coasting | Side slip 0.5s after 3.6m/s impulse |
|---|---|---|---|---|
| Asphalt | 8.80m | 0.83s | 71.36km/h | 0.04m/s |
| Gravel | 10.58m | 1.02s | 66.94km/h | 0.12m/s |
| Earth | 10.80m | 1.03s | 65.98km/h | 0.14m/s |
| Sand | 12.38m | 1.20s | 60.81km/h | 0.27m/s |
| Snow | 13.61m | 1.30s | 65.79km/h | 0.30m/s |
| Wood | 9.51m | 0.90s | 69.32km/h | 0.08m/s |
These short stopping distances reflect the pre-existing strong arcade brakes. The test confirms relative surface behavior, not real motorcycle tire/ABS calibration. The stationary bridge trial physically measured about 0.10m/s support motion through full oscillations; the moving rider crossed both deck boundaries and continued 320.27m without loss of road contact. Production bridge scenery and AI traversal still need their own complete-route run.
The first rendered Alps01 trial (campaign-report.json) subsequently failed progress acceptance after a real crest launch. Its incident buffer records contact loss near route1880m at128km/h, height reaching7.015m, crossing the next required gate1918.715m above the existing4.5m gate corridor, then landing around1961m after roughly2.3seconds. It recorded no crash or recovery. This is evidence of a route/approach problem that the bounded surface fixtures did not cover; it does not justify granting the missed gate. The world/AI investigation and a complete rerun remain required.
Surface-aware tire audio and bounded rear-contact particulate feedback are described separately in SURFACE-FEEDBACK.md. Their managed/source checks do not count as physical route completion.
Ten full Blackpine input trials
HandlingQA runs only with -rampageTest handling -rampageHandlingQA. Use -rampageHandlingCase N, -rampageHandlingReport <directory> and an optional -rampageSeconds 260. It self-disables the ordinary replay harness and drives through virtual gamepad events plus the actual RampageApp.Input.Read path. No player BikeAI, direct force or position correction is used. Start with case 0 to calibrate the test driver before interpreting the matrix as acceptance evidence.
| Case | Riders | Line | Requested fps | Transmission | Deliberate gaps |
|---|---|---|---|---|---|
| 0 | 1 | Centre | 60 | Auto | None |
| 1 | 1 | −3.5m | 60 | Auto | None; B supplements strong braking |
| 2 | 1 | +3.5m | 30 | Auto | None |
| 3 | 1 | Smooth ±3m variation | 30 | Manual RT/LT | None |
| 4 | 1 | Centre | 144 requested | Auto | Main-thread/render stalls of 100/250/500ms |
| 5 | 8 | Centre | 60 | Auto | None |
| 6 | 8 | −2.8m | 30 | Auto | None; B supplements strong braking |
| 7 | 8 | +2.8m | 60 | Auto | None |
| 8 | 8 | Centre | 60 | Manual RT/LT | None |
| 9 | 8 | Smooth ±3m variation | 60 | Auto | Hold the last input for 100/250/500ms while physics continues |
Gaps occur at 20/40/60 race seconds and record measured durations. Main-thread stalls include Unity's existing maximumDeltaTime cap; they do not pretend that 500ms of physics is simulated during a blocked frame. Requested 144fps may exceed available performance; actual mean/max frame time and rendered-frame coverage are reported.
Each case requires two ordered laps, no unexpected pause/recovery, no unexplained crash, intact production profile, restored isolated QA files and at least 98% rendered-frame coverage. Actual physical damage crashes are listed with their reason and incident history instead of being silently reclassified. Four-hertz telemetry includes real commands, body velocity, lateral error, slip, ground flags and gate state. The incident buffer covers all eight bikes. Output is handling-report.json plus handling-telemetry.jsonl and an incidents directory.
The actual Bike.ManualTransmission must now match the planned mode throughout every measured frame. Manual cases3/8 additionally require a rising command request, a real accepted upshift event and an observed gear above first. Reporting a planned manual mode alone cannot pass. Per-frame counters distinguish command requests from accepted up/down shifts; per-upshift RPM observations are telemetry only, since a slow rendered frame can miss the exact physical1500rpm engagement tick. The trace now also includes the processed virtual-device controls, actual bike mode and applied gas/brake values alongside the submitted command. The A/B/downstick/X mapping, manual RT/LT and shoulder combat mappings are unchanged.
The serial Windows starter is Tools/handling-test.ps1 -Case 0 -GraphicsApi Direct3D12 -DeviceIndex 1 (device index is explicit, not a claim about the machine's adapter order). It captures build hashes, profile main/backup/temp hashes, native graphics logs, display-reset events and the owned process exit; a missing/partial report cannot pass. Its current 36/36 pure guard checks passed in handling-runner-verification.json, reproduced with Tools/handling-runner-test.ps1, including rejected absent/mismatched manual-mode and shift evidence. Those guard checks launch no player and do not constitute a driving run.
Case 0 actually completed two ordered Blackpine laps in 20260915-033624-601-handling0: 6,800.02m in142.67seconds, zero crashes/recoveries/errors, maximum lateral error1.566m and slip0.208m/s. All3,170 measured frames rendered; the runner confirmed exit0 and unchanged/restored profiles. Although60fps was requested, measured mean frame time was45.006ms, about22.22fps on this Intel adapter run. This is a completed functional test at that actual frame rate, not a60fps performance pass. The first trace omitted the nested command struct because it lacked Unity serialization metadata; subsequent harness builds use an explicit serializable command snapshot. The remaining nine cases are still pending.
These are autonomous regression trials. Human steering, physical Xbox haptics, visual surface readability, bridge presentation and overall drive feel still require player acceptance. Source compilation alone does not complete this matrix.
Native production Alpine crest regression
Rampage.Editor.AlpineCrestVerification.Run is a separate editor entry point, with results written to Docs/QA/alpine-crest-verification.json. It loads current authored campaign routes, invokes the production road/shoulder builder, and simulates a player Rigidbody in an isolated preview PhysicsScene. Scenery and distant terrain are omitted. Each trial declares one initial speed seed, then uses held throttle and steering-only pursuit commands; it never corrects position or velocity during driving. Boost and default brake-assist variants are explicit. The worst convex crest of each Alps stage is tested, together with the historical Alps01 launch zone when distinct.
The test records route/collider hashes, fine-scale and20m-scale convex curvature, actual speed at the crest, road-relative height, contact loss, lateral error, horizontal acceleration demand, brakes, ordered gates and any crash/recovery. Assist-off variants assert zero applied braking; existing4.5m gate height and ordered-gate rules remain intact. Tight horizontal turns can fail a full-speed combined road trial independently of vertical alignment; the measured lateral/demand fields must be reviewed before attributing such a failure to suspension. Catalog and native scene state are restored, and no profile or default physics scene is used.
The first actual native run produced 57 passing and31 failing assertions across16 trials, with scene/catalog state restored. Its unchanged report is preserved as alpine-crest-verification-initial.json. This is not an overall acceptance pass.
| Tested zone | Stable complete trials | Actual observation |
|---|---|---|
| Alps01 worst convex crest | 4/4 | Actual199–214km/h at the crest, 100% contact, maximum height0.650m, no missed gates or recovery. |
| Alps01 historical launch zone | 1/4 | Default assist completes with100% contact and maximum lateral2.61m. Unassisted held-gas trials arrive around183–188km/h at the marked crest after leaving the preceding bend; they run43–52m sideways off the route and fall. Their positive height never exceeds0.665m, so the prior7m upward launch does not recur. |
| Alps02 worst convex crest | 0/4 | All variants fail, including default assist. At about94km/h, while still contacting the road, vertical velocity increases from5.15 to13.40m/s; both contacts then disappear near2376.85m. This produces a5.46m flight in the assisted trial and requires correction. |
| Alps03 worst convex crest | 3/4 | Unassisted normal-speed and assisted trials complete. The boost trial retains100% contact but goes9.36m laterally out of the legal corridor and misses gates. |
The variant suffixes130/210/215 describe initial velocity seeds, not a maintained speed. Held throttle subsequently changes speed through the real drivetrain. High-speed sideways exits do not justify weakening checkpoint rules. The initial Alps02 assisted failure reproduced upward acceleration while still supported, consistent with the separately identified tight graded inner-lane geometry.
The second actual native run at02:41:21UTC again produced 57 passing and31 failing assertions, but used the corrected Alps02 geometry. Its unchanged report is preserved as alpine-crest-verification-after-fillets.json. Alps02 is revision2, length4412.709m, with different route/collider hashes; measured maximum horizontal curvature corresponds to a38.39m radius, and minimum full-width forward ratio is positive0.8601. These measurements exclude a stale copy of the previous5.83m-radius road as the explanation for the identical aggregate count.
The automatically selected Alps02 crest moved from2375m to1210m. Therefore this rerun does not establish physical regression coverage of the original hairpin; that historical area needs its own retained fixture window. In the new default-assist trial, the rider first exits a preceding horizontal bend: around1089–1102m it holds about129.4km/h and full steering while lateral error grows3.28→6.04m, with uninterrupted road contact and approximately0.61m road-relative height. The measured local horizontal curvature requires roughly31–32m/s² at that speed, above the controller's27m/s² yaw allowance. The assist's recorded brake output implies a target near113km/h, but its current positive speed offsets and remaining throttle permit a higher equilibrium. This is a trace-supported control diagnosis, not proof from a subsequent corrected run.
The rider leaves the assist's7m lateral eligibility corridor near1108m, misses a gate at1115.57m, and only loses contact near1157m while almost19m beside the route. The resulting maximum height3.652m and0.35s flight follow the lateral departure; they are not a repeat of the earlier supported-road launch. This trial completes the measured window with97.52% contact and zero crashes/recoveries, but fails the unchanged corridor and ordered-gate checks. No grip, suspension, steering force or gate tolerance was changed for this analysis.
The implemented correction samples the current horizontal curve over±5m and then at10m intervals through a speed-dependent110–210m braking horizon. Its approach envelope uses the current/future surface brake traction, configured brake deceleration and downhill gravity. A12m approach margin covers steering/braking response. Convex-crest and authored section speed limits remain part of the envelope.
BikeBrakeAssist.Apply now removes engine drive at the target instead of preserving the previous positive speed offsets. Brake feedback includes actual coast acceleration from gravity, drag and rolling resistance, so downhill gravity does not require sustained overspeed to obtain holding brake. The command target reserves0.35m/s below the geometric envelope. Assistance is bounded to75% braking; stronger rider braking is preserved. It changes gas/brake commands only, without increasing lateral forces, altering steering, attaching the bike to the route or granting gates. Disabling brake assist still bypasses this path.
The actual production helper passed 51/51 managed checks in brake-assist-verification.json, reproduced with Tools/Verification/brake-assist-verification.ps1. These include the reported129.4-versus113km/h case, manual-command preservation, finite/invalid targets, smooth command transitions and36 explicitly simplified longitudinal trials spanning uphill/flat/downhill, asphalt/snow brake capacity, ordinary/boost-like engine force and30/60/144Hz integration. They establish command behavior and scalar convergence, not native road or full-stage acceptance. All75 Runtime and17 Editor source files also compiled offline without warnings/errors.
The native entry point remains Rampage.Editor.AlpineCrestVerification.Run. It additionally retains the historical Alps02 window centred at2375m even when the worst selected crest changes, while leaving the original corridor/contact/gate assertions unchanged. New trace samples distinguish requested throttle, actual applied throttle and the assist's geometric target speed.
The post-correction native run at03:00:28UTC is preserved unchanged as alpine-crest-verification-assist-fixed.json, SHA256 e6d39c12cac3acc2a8bb746e2c9cb8b9dae51f5463f307470d4d7735feaae48b. Its raw total is 65 passing and44 failing assertions across20 trials; adding the historical Alps02 window increased the assertion count. alpine-crest-assist-summary.json is explicitly a derived grouping of that report, not another executed test.
| Scope | Actual result | Interpretation |
|---|---|---|
| Five default-assist route windows | 20/20 assertions; 5/5 complete trials | All contact, corridor, ordered-gate and no-recovery requirements pass. |
| Fifteen unassisted held-gas/boost stress trials | 41/85 assertions; 5/15 complete trials satisfy every assertion | The driver deliberately does not brake for horizontal bends. Failures remain recorded;200km/h hairpin cornering is not a production acceptance requirement. |
| Road fixture presence and state restoration | 4/4 | Native road colliders exist and preview scenes/catalog/global state are restored. |
| Default-assist window | Speed at window marker | Maximum lateral error | Height range | Road contact / airborne time |
|---|---|---|---|---|
| Alps01 worst crest | 199.22km/h | 0.345m | 0.590–0.649m | 100% /0s |
| Alps01 historical launch | 141.72km/h | 1.651m | 0.582–0.664m | 100% /0s |
| Alps02 new worst crest | 156.67km/h | 1.974m | 0.582–0.693m | 100% /0s |
| Alps02 historical hairpin | 124.57km/h | 1.961m | 0.590–0.693m | 100% /0s |
| Alps03 worst crest | 81.25km/h | 0.610m | 0.593–0.650m | 100% /0s |
All five assisted windows record zero missed-gate steps, crashes, recoveries and resets. Marker speeds are not assumed to be the tightest bend's speed. The assist now completes both Alps02 windows using physical travel with no positional correction. On the unchanged Alps02 road/collider hashes, all paired unassisted samples before lateral departure (50/41/40 samples across the three original variants, both on-road and within3m) have exactly unchanged distance, speed, height and lateral values. This supports the intended bypass when assistance is disabled. Alps01/03 route hashes differ slightly between the two native runs, so their traces are not claimed as a pure single-change comparison.
The repair is closed for these bounded assisted physical regressions. Full campaign stages, the remaining nine Blackpine input trials and human driving feel remain separate acceptance work. No suspension, grip or gate relaxation is planned merely to make the deliberately unbraked hairpin stress trials pass.