@inventure71/paddockjs 4.2.0 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/INSTALL_AND_UPDATE.md +24 -4
  3. package/README.md +31 -2
  4. package/docs/advanced_ai_pace.md +131 -0
  5. package/docs/advanced_physics_rebuild.md +166 -0
  6. package/docs/architecture.md +109 -30
  7. package/docs/cleanup_coverage.md +284 -0
  8. package/docs/component_inventory.md +31 -15
  9. package/docs/composable-layouts.md +2 -0
  10. package/docs/concepts.md +12 -1
  11. package/docs/custom_model_controller.md +1 -1
  12. package/docs/data_contract.md +33 -10
  13. package/docs/data_model.md +4 -2
  14. package/docs/efficiency_cleanup_2026-10-13.md +80 -0
  15. package/docs/grid_walk.md +84 -0
  16. package/docs/index.md +1 -0
  17. package/docs/keyboard_driving_fix.md +118 -0
  18. package/docs/limit_driving_2026-10-14.md +88 -0
  19. package/docs/maintainability_audit_2026-09-08.md +146 -0
  20. package/docs/racing_ai_improvements.md +145 -0
  21. package/docs/rules.md +43 -13
  22. package/docs/sense_contract.md +4 -1
  23. package/docs/system_specs.md +38 -11
  24. package/docs/testing-reliability-guide.md +5 -0
  25. package/docs/v11_improvements.md +108 -0
  26. package/package.json +24 -13
  27. package/src/api/MountedSimulatorComponents.js +101 -0
  28. package/src/api/PaddockSimulatorController.js +56 -81
  29. package/src/app/BrowserExpertAdapter.js +0 -6
  30. package/src/app/BrowserExpertExternalRenderer.js +72 -50
  31. package/src/app/ExternalTrackSnapshot.js +50 -0
  32. package/src/app/F1SimulatorApp.js +242 -297
  33. package/src/app/HostLifecycleEmitter.js +93 -0
  34. package/src/app/banners/raceDataBannerController.js +9 -4
  35. package/src/app/camera/cameraConstants.js +1 -1
  36. package/src/app/camera/cameraController.js +10 -13
  37. package/src/app/camera/cameraGeometry.js +1 -1
  38. package/src/app/controls/SimulatorViewControls.js +45 -0
  39. package/src/app/controls/workbenchBindings.js +64 -0
  40. package/src/app/controls/workbenchDisclosure.js +41 -0
  41. package/src/app/domBindings.js +21 -13
  42. package/src/app/gridWalk/GridWalkController.js +180 -0
  43. package/src/app/gridWalk/gridWalkCamera.js +88 -0
  44. package/src/app/gridWalk/gridWalkControls.js +24 -0
  45. package/src/app/gridWalk/gridWalkCrew.js +246 -0
  46. package/src/app/gridWalk/gridWalkLabels.js +104 -0
  47. package/src/app/gridWalk/gridWalkLayout.js +46 -0
  48. package/src/app/gridWalk/gridWalkNavigation.js +106 -0
  49. package/src/app/gridWalk/gridWalkProjectBanner.js +43 -0
  50. package/src/app/gridWalk/gridWalkSession.js +50 -0
  51. package/src/app/layoutSupport.js +2 -0
  52. package/src/app/raceOverlayClearanceSupport.js +21 -7
  53. package/src/app/readouts/carOverviewRenderer.js +9 -4
  54. package/src/app/readouts/readoutFormatters.js +1 -8
  55. package/src/app/readouts/telemetryRenderer.js +1 -3
  56. package/src/app/readouts/timingTowerRenderer.js +48 -50
  57. package/src/app/rendering/appAssets.js +8 -10
  58. package/src/app/rendering/carRenderer.js +27 -79
  59. package/src/app/rendering/displayUtils.js +0 -6
  60. package/src/app/rendering/expertSensorRenderer.js +1 -1
  61. package/src/app/rendering/replayGhostRenderer.js +4 -1
  62. package/src/app/rendering/trackRenderer.js +2 -2
  63. package/src/app/runtime/frameLoop.js +18 -17
  64. package/src/app/runtime/pixiStartupLifecycle.js +94 -0
  65. package/src/app/runtime/runtimeTiming.js +4 -1
  66. package/src/app/runtime/runtimeVisibility.js +7 -2
  67. package/src/config/cssValues.js +1 -1
  68. package/src/config/defaultOptions.js +12 -173
  69. package/src/config/restartOptions.js +1 -0
  70. package/src/config/runtimeSeed.js +10 -0
  71. package/src/config/simulatorDefaults.js +106 -0
  72. package/src/config/telemetryModules.js +20 -0
  73. package/src/config/theme/cssTokens.js +85 -0
  74. package/src/config/theme/themeCss.js +72 -0
  75. package/src/config/theme/themeInputs.js +155 -0
  76. package/src/config/theme/themeResolver.js +172 -0
  77. package/src/config/theme/themeSelectors.js +69 -0
  78. package/src/config/theme/themeTokens.js +257 -0
  79. package/src/config/theme/themeUtils.js +15 -0
  80. package/src/config/themeOptions.js +14 -830
  81. package/src/config/timingGapMode.js +0 -2
  82. package/src/config/uiOptions.js +53 -0
  83. package/src/data/championship.js +16 -158
  84. package/src/data/championshipDefaults.js +87 -0
  85. package/src/data/driverData.js +10 -20
  86. package/src/data/entryIdentity.js +67 -0
  87. package/src/data/entryPresentation.js +23 -0
  88. package/src/data/entryTypes.d.ts +160 -0
  89. package/src/data/index.d.ts +13 -259
  90. package/src/data/ratingScale.js +14 -0
  91. package/src/data/teamTypes.d.ts +23 -0
  92. package/src/data/trackTypes.d.ts +101 -0
  93. package/src/data/vehicleData.js +12 -23
  94. package/src/environment/actions.js +3 -2
  95. package/src/environment/controllerLoop.js +57 -44
  96. package/src/environment/controllerScheduler.js +30 -0
  97. package/src/environment/driverObservations.js +260 -0
  98. package/src/environment/driverReset.js +51 -0
  99. package/src/environment/episode.js +0 -1
  100. package/src/environment/evaluation.js +6 -90
  101. package/src/environment/evaluationTracker.js +100 -0
  102. package/src/environment/events.js +23 -0
  103. package/src/environment/index.d.ts +21 -140
  104. package/src/environment/metrics.js +3 -17
  105. package/src/environment/observationSchema.js +104 -0
  106. package/src/environment/observationVector.js +16 -115
  107. package/src/environment/observations.js +16 -316
  108. package/src/environment/options.js +5 -24
  109. package/src/environment/renderFrames.js +21 -0
  110. package/src/environment/resetOptions.js +41 -0
  111. package/src/environment/results.js +125 -0
  112. package/src/environment/rewards.js +10 -16
  113. package/src/environment/runtime.js +17 -300
  114. package/src/environment/scenarioOptions.js +90 -0
  115. package/src/environment/scenarioPlacements.js +70 -0
  116. package/src/environment/scenarioPresets.js +78 -0
  117. package/src/environment/scenarios.js +5 -236
  118. package/src/environment/scratch.js +18 -0
  119. package/src/environment/sensorOptions.js +20 -0
  120. package/src/environment/sensors/index.js +2 -556
  121. package/src/environment/sensors/indexedRayBands.js +0 -18
  122. package/src/environment/sensors/nearbyCars.js +18 -15
  123. package/src/environment/sensors/opponentRadar.js +3 -2
  124. package/src/environment/sensors/pitConnectorProximity.js +20 -0
  125. package/src/environment/sensors/rayBandTrace.js +1 -37
  126. package/src/environment/sensors/rayBatch.js +81 -0
  127. package/src/environment/sensors/rayConfig.js +26 -0
  128. package/src/environment/sensors/rayGeometry.js +0 -8
  129. package/src/environment/sensors/rayGuards.js +0 -6
  130. package/src/environment/sensors/rayOutputs.js +75 -0
  131. package/src/environment/sensors/rayQuery.js +110 -0
  132. package/src/environment/sensors/rayScratch.js +146 -0
  133. package/src/environment/sensors/sensorTargets.js +12 -4
  134. package/src/environment/sensors/surfaceRays.js +6 -61
  135. package/src/environment/sensors/trackRays.js +7 -80
  136. package/src/environment/simulationSetup.js +30 -0
  137. package/src/environment/specs.js +15 -109
  138. package/src/index.d.ts +34 -259
  139. package/src/index.js +28 -6
  140. package/src/placeholder/index.js +1 -8
  141. package/src/rendering/displayLifecycle.js +6 -0
  142. package/src/rendering/proceduralTrackAsset.js +1 -1
  143. package/src/rendering/renderSnapshot.js +3 -0
  144. package/src/rendering/track/finishLineRenderer.js +1 -1
  145. package/src/rendering/track/gridRenderer.js +1 -1
  146. package/src/rendering/track/offsetStrokeSafety.js +2 -2
  147. package/src/rendering/track/pitLaneRenderer.js +1 -1
  148. package/src/rendering/track/trackMaterialRenderer.js +4 -4
  149. package/src/rendering/track/trackRenderConstants.js +0 -1
  150. package/src/rendering/track/trackRenderGeometry.js +1 -7
  151. package/src/simulation/collisionGeometry.js +5 -11
  152. package/src/simulation/driver/advancedPathControls.js +53 -0
  153. package/src/simulation/driver/advancedRacePace.js +112 -0
  154. package/src/simulation/driver/advancedRacingControls.js +61 -0
  155. package/src/simulation/driver/advancedRacingEnvelope.js +255 -0
  156. package/src/simulation/driver/advancedRacingSteering.js +76 -0
  157. package/src/simulation/driver/advancedRacingTiming.js +16 -0
  158. package/src/simulation/driver/arcadeRacePace.js +83 -0
  159. package/src/simulation/driver/arcadeRacingControls.js +111 -0
  160. package/src/simulation/driver/arcadeRacingEnvelope.js +63 -0
  161. package/src/simulation/driver/driverControlConstants.js +0 -2
  162. package/src/simulation/driver/driverController.js +14 -3
  163. package/src/simulation/driver/edgeRecovery.js +0 -1
  164. package/src/simulation/driver/racingControls.js +10 -406
  165. package/src/simulation/driver/racingDriverProfile.js +48 -0
  166. package/src/simulation/driver/racingLinePlan.js +48 -15
  167. package/src/simulation/driver/rejoinControls.js +58 -111
  168. package/src/simulation/driver/safetyCarControls.js +12 -0
  169. package/src/simulation/driver/trafficScan.js +89 -6
  170. package/src/simulation/pit/pitCarPlacement.js +29 -0
  171. package/src/simulation/pit/pitFlow.js +8 -30
  172. package/src/simulation/pit/pitIntent.js +4 -3
  173. package/src/simulation/pit/pitOccupancy.js +2 -2
  174. package/src/simulation/pit/pitQueue.js +2 -18
  175. package/src/simulation/pit/pitRouteMovement.js +1 -36
  176. package/src/simulation/pit/pitRouting.js +5 -81
  177. package/src/simulation/pit/pitService.js +3 -32
  178. package/src/simulation/pit/pitServiceConstants.js +1 -0
  179. package/src/simulation/pit/pitServiceProgress.js +34 -0
  180. package/src/simulation/pit/pitSnapshots.js +8 -4
  181. package/src/simulation/pit/pitState.js +1 -5
  182. package/src/simulation/race/classification.js +1 -8
  183. package/src/simulation/race/raceFinish.js +3 -8
  184. package/src/simulation/race/raceOrder.js +2 -13
  185. package/src/simulation/race/raceProgress.js +6 -20
  186. package/src/simulation/race/raceSetup.js +1 -1
  187. package/src/simulation/race/raceStep.js +1 -1
  188. package/src/simulation/race/redFlag.js +2 -4
  189. package/src/simulation/race/safetyCar.js +3 -8
  190. package/src/simulation/race/stalledDnf.js +2 -4
  191. package/src/simulation/raceSimulation.js +6 -38
  192. package/src/simulation/replay/replayGhosts.js +44 -32
  193. package/src/simulation/rules/collisionSteward.js +0 -14
  194. package/src/simulation/rules/penaltyActions.js +37 -0
  195. package/src/simulation/rules/penaltyLedger.js +8 -1
  196. package/src/simulation/rules/rulesReview.js +1 -7
  197. package/src/simulation/simMath.js +4 -0
  198. package/src/simulation/snapshots/raceSnapshots.js +30 -11
  199. package/src/simulation/timing/drsState.js +8 -0
  200. package/src/simulation/timing/drsTiming.js +2 -4
  201. package/src/simulation/timing/lapTelemetry.js +7 -4
  202. package/src/simulation/timing/sectorPerformance.js +3 -3
  203. package/src/simulation/timing/timingLines.js +12 -6
  204. package/src/simulation/track/pitLaneAccess.js +2 -2
  205. package/src/simulation/track/pitLaneLayout.js +4 -135
  206. package/src/simulation/track/pitLaneModel.js +70 -0
  207. package/src/simulation/track/pitLaneState.js +0 -1
  208. package/src/simulation/track/pitServiceLayout.js +121 -0
  209. package/src/simulation/track/proceduralTrack.js +77 -0
  210. package/src/simulation/track/spatialQueries.js +1 -25
  211. package/src/simulation/track/trackBuilder.js +54 -0
  212. package/src/simulation/track/trackConstants.js +0 -22
  213. package/src/simulation/track/trackMath.js +4 -29
  214. package/src/simulation/track/trackModel.js +4 -241
  215. package/src/simulation/track/trackModelCache.js +105 -0
  216. package/src/simulation/track/trackNearestQueries.js +1218 -0
  217. package/src/simulation/track/trackQueryConstants.js +3 -0
  218. package/src/simulation/track/trackQueryConstruction.js +368 -0
  219. package/src/simulation/track/trackQueryIndex.js +12 -1933
  220. package/src/simulation/track/trackQuerySegments.js +174 -0
  221. package/src/simulation/track/trackQueryStats.js +12 -0
  222. package/src/simulation/track/trackRayQueries.js +165 -0
  223. package/src/simulation/vehicle/advancedContactResponse.js +89 -0
  224. package/src/simulation/vehicle/advancedTireForces.js +92 -0
  225. package/src/simulation/vehicle/advancedVehicleModel.js +65 -0
  226. package/src/simulation/vehicle/advancedVehiclePhysics.js +127 -0
  227. package/src/simulation/vehicle/contactResolution.js +20 -6
  228. package/src/simulation/vehicle/mainTrackWheelSurface.js +2 -13
  229. package/src/simulation/vehicle/pitWheelSurface.js +0 -12
  230. package/src/simulation/vehicle/runoffResponse.js +2 -4
  231. package/src/simulation/vehicle/sampledWheelSurface.js +266 -0
  232. package/src/simulation/vehicle/vehicleGeometry.js +0 -56
  233. package/src/simulation/vehicle/vehicleKinematics.js +10 -0
  234. package/src/simulation/vehicle/vehiclePhysics.js +35 -194
  235. package/src/simulation/vehicle/vehicleSnapshots.js +13 -4
  236. package/src/simulation/vehicle/vehicleState.js +2 -1
  237. package/src/simulation/vehicle/wheelSurface.js +13 -432
  238. package/src/simulation/vehicle/wheelSurfaceCache.js +55 -0
  239. package/src/simulation/vehicle/wheelSurfaceSummary.js +101 -0
  240. package/src/styles.css +173 -19
  241. package/src/ui/bannerTemplates.js +4 -2
  242. package/src/ui/cameraControlsTemplate.js +4 -2
  243. package/src/ui/gridWalkTemplate.js +29 -0
  244. package/src/ui/htmlEscaping.js +8 -0
  245. package/src/ui/shellTemplate.js +3 -0
  246. package/src/ui/telemetryDrawerTemplate.js +4 -2
  247. package/src/ui/telemetryTemplates.js +3 -22
  248. package/src/ui/templateUtils.js +2 -8
  249. package/src/ui/viewControlsTemplate.js +21 -0
@@ -1,5 +1,11 @@
1
1
  # Architecture
2
2
 
3
+ ## Grid Walk presentation boundary
4
+
5
+ `src/app/gridWalk/` owns optional Grid Walk presentation. `GridWalkController` composes the lifecycle and scene without owning UI action handlers. `GridWalkSession` captures assigned display poses and transition state without changing or stepping the race. `GridWalkCamera` retains one longitudinal world position and a fixed grid axis; `GridWalkNavigation` normalizes pointer, wheel and keyboard input into that single camera action. `GridWalkControls` owns in-scene entry-selector actions, `GridWalkLabels` owns measured, collision-free label lanes and focus restoration, `GridWalkProjectBanner` composes the shared race banner template/presenter and enabled/muted policy, and `GridWalkLayout` measures chrome/safe scene bounds and restores covered Race UI. Camera framing and label visibility use the same path after selection and manual browsing. Car taps select on release; staging snaps display headings while Race retains smoothing. `GridWalkCrew` creates varied per-car mechanic groups and visitor geometry once, animates transforms with cosmetic randomness independent of the race, and maintains continuous coverage with irregular visitor spacing, varied lateral paths and bounded individual pace/sway, and extends paths beyond the full permitted camera envelope by filling only new offscreen coverage without repositioning existing visitors. `src/ui/gridWalkTemplate.js` composes the semantic surfaces; `src/data/entryPresentation.js` normalizes host metadata/URLs. Runtime visibility, ticker, resize and cleanup paths remain shared. Race, physics and environment modules do not depend on Grid Walk.
6
+
7
+ `src/ui/viewControlsTemplate.js` and `src/app/controls/SimulatorViewControls.js` own the external Grid Walk / Race component, its startup availability and synchronized active state. `ui.viewControls` selects either the default two-button switch or the single Start race / Stop race action, including on restart; both variants delegate to the same view lifecycle. The composable controller exposes `mountViewControls()`; shell and telemetry-drawer templates compose it above their scenes. A standalone race canvas owns no mode-switch markup. Grid Walk consumes wheel input only for actual camera movement and excludes the Race zoom handler while active, allowing host-page scrolling at a grid boundary and browser zoom for modifier wheel input. See [Grid Walk](grid_walk.md).
8
+
3
9
  ## Package Boundary
4
10
 
5
11
  PaddockJS is the simulator package. A host website imports it and passes data.
@@ -22,6 +28,33 @@ The host owns:
22
28
  - Page placement.
23
29
  - Host build and deployment.
24
30
 
31
+ ## Repository Demo Boundaries
32
+
33
+ The repository has one browser integration website in `site/`, excluded from the published simulator package. It installs the repository through its package boundary and owns one dependency graph, Vite server and build.
34
+
35
+ - `site/src/demo/` owns the product tour at `/`, including the live Grid Walk/Race experience, composable examples, presets and feature inventory. Its runtime, UI and data modules retain separate integration, interaction and content responsibilities.
36
+ - `site/src/showcase/` owns detailed `/guide/` integration examples and `/labs/` regression fixtures, Policy Runner transport and diagnostics. Private geometry and presentation-metric imports remain limited to explicitly labeled repository diagnostic tools.
37
+ - The shared route inventory and shell own navigation across Demo, Guide, Labs and Reference. Reference pages are generated from the repository Markdown docs during development/build. Legacy root-level showcase routes redirect to canonical routes without dropping query parameters or fragments.
38
+ - `site/scripts/check-feature-coverage.mjs` compares the product catalogue with public declarations. `smoke/demo-smoke.mjs` exercises the product tour and `smoke/browser-smoke.mjs` retains the complete Guide/Labs matrix; both use the same `site/dist` build.
39
+
40
+ Host navigation, filters, keyboard input, copy controls, explanatory layout and transport stay inside the website rather than package modules. The main demo's scrollable output hands vertical scrolling to the page at its edges. Playable driving keys are scoped to its focused, visible, running root, release held input on focus loss/pause, and leave normal page keyboard scrolling and browser shortcuts available. Product examples use `@inventure71/paddockjs`, `/environment`, `/data` and `/placeholder`. Repository diagnostics do not establish additional supported package exports.
41
+
42
+ `tools/release/` remains a separate private package for the supported Changesets toolchain. Release audits cover the package, single website and release tools. The package itself retains Node 20.19 support; release commands use their supported newer Node version.
43
+
44
+ Ordinary demo races and the neutral headless example select `arcade`, matching the package default. `site/src/demo/data/demoOptions.js` owns the shared browser choice; `headlessLab.js` owns its separate environment example. The lazily launched `expertLab.js` explicitly selects `advanced` in the Advanced physics chapter (`#expert`, retained for existing links). Its visible limitation notice describes the experimental planar handling model separately from the ordinary demo experience.
45
+
46
+ Each demo runtime owns its mounted controllers and releases them on initialization failure or disposal. Preset changes serialize mounts on their shared root and discard superseded selections. Expert disposal also invalidates pending mounts. The control deck reads public snapshot, theme, timing, and pit getters so embedded package controls and host controls stay synchronized.
47
+
48
+ Policy Runner routes compose a page coordinator with session/replacement/action ownership, playback, live transport, view and training-configuration modules under `site/src/showcase/policyRunner/`. Replacement and actions share one serialized queue; allocation performs its initial reset directly. Request/session generations suppress stale mounts, packets, callbacks and training resets. Live model-sense views consume the attached frame's mapped observation, and detach invalidates captured callbacks. `src/app/ExternalTrackSnapshot.js` restores compact JSON sample tables, omitted pit bounds and display query indexes around supplied external geometry. It retains one decoded geometry per attachment, invalidates on geometry changes/detach, and never regenerates tracks or derives model observations. Playable and private collision diagnostics are lazy route modules; ordinary routes do not load private collision geometry. Demo page interactions own listeners, timers, asynchronous launches and late mount cleanup.
49
+
50
+ ## Mounted component ownership
51
+
52
+ `src/api/MountedSimulatorComponents.js` owns composable DOM surfaces, per-surface layout-support subscriptions, remount/rerender cleanup and the composite query/theme root. `PaddockSimulatorController` owns public commands, resolved options and startup/cancellation. It supplies current options and mount availability to the component registry; mounting a different surface into an occupied root remains rejected. Replacing a surface releases its previous layout support and removes the previous DOM, and destroying the controller clears all owned surfaces. The all-in-one mount retains its separate root reservation because its lifecycle owns a complete shell rather than rearrangeable surfaces.
53
+
54
+ ## Verification host lifecycle
55
+
56
+ Both Chromium smoke runners compose `smoke/previewServer.mjs` for free-port allocation, bounded HTTP readiness and owned preview process cleanup. Readiness also times out when a server accepts a connection without answering. Teardown signals the owned process group, waits for graceful exit and escalates to forced termination if required, including surviving descendants after the npm launcher exits; exit listeners and deadline timers are released. Scenario assertions remain in their respective engineering-showcase and product-demo runners so failures retain clear feature ownership.
57
+
25
58
  ## Main Flow
26
59
 
27
60
  All-in-one flow:
@@ -83,6 +116,14 @@ The environment runtime consumes per-step events directly from the simulation du
83
116
 
84
117
  Browser expert mode reuses the same environment runtime around the visual app's existing `RaceSimulation` instance. When `expert.enabled` is true, the visual ticker does not advance simulation time automatically; `simulator.expert.reset()` and `simulator.expert.step(actions)` are the only simulation-advance entry points.
85
118
 
119
+ ## Shared Data and Presentation Rules
120
+
121
+ Shared `src/data/entryTypes.d.ts` and `trackTypes.d.ts` own the root/data/environment declaration contracts, including entry presentation. `championshipDefaults.js` owns bundled championship fixtures; `entryIdentity.js` owns timing codes, team identity and number formatting; `championship.js` composes pairing and resolved-identity validation. CSS-free data normalization imports leaf color/token helpers rather than the browser theme resolver.
122
+
123
+ `src/data/ratingScale.js` owns common rating bounds, finite-number validation, clamping, and linear conversion. Driver and vehicle data classes retain their stat definitions, inverse-direction rules, and domain-specific error labels. `src/config/telemetryModules.js` owns telemetry defaults and normalization for both resolved options and raw template callers. `src/ui/htmlEscaping.js` owns dependency-free escaping shared by templates, readouts, and the standalone placeholder; it does not import the browser runtime.
124
+
125
+ Snapshot serializers own copies of supported mutable fields. Penalty snapshots copy consequence entries and used-compound/wheel-offset arrays; full car snapshots copy nested team `pitCrew` and `pitCrewStats`; pit snapshots copy nested service-profile `pitCrew`. Optional field shapes remain intact. Editing these snapshots cannot change live service duration or crew metadata, and later live mutations cannot alter earlier snapshots. The explicitly documented live track reference remains a separate contract.
126
+
86
127
  ## Public Entry
87
128
 
88
129
  `src/index.js` is the public package entry.
@@ -103,40 +144,45 @@ Responsibilities:
103
144
  `src/environment/` owns the browser-free expert environment:
104
145
 
105
146
  - `index.js` and `index.d.ts` expose the public subpath API.
106
- - `runtime.js` owns environment-loop `reset()`, `step(actions)`, `resetDrivers(placements)`, `getObservation()`, and `getState()` around an injected race-simulation host. It also owns partial reset option merging, preserving omitted nested plain-object groups while replacing arrays and explicit scenario placement maps. It owns per-driver episode bookkeeping for batched environments and formats result state as `full`, `minimal`, or `none` without changing simulator stepping. `resetDrivers()` reapplies normal runoff/barrier classification to the selected cars after placement and before result construction, clears only selected driver episode terminal state, and does not rewind global `info.step`. Reset-only observation scope affects the returned observation and metrics maps, while full `info.drivers` remains scoped to each controlled driver's own episode state. Reward callbacks are transition-only and run from `step(actions)`, not reset/read result construction.
147
+ - `runtime.js` owns environment-loop `reset()`, `step(actions)`, `resetDrivers(placements)`, `getObservation()`, and `getState()` around an injected race-simulation host. It delegates partial reset option merging to `resetOptions.js`, preserving omitted nested plain-object groups while replacing arrays and explicit scenario placement maps. `simulationSetup.js` owns simulation/scenario creation, `driverReset.js` owns controlled pit/reset mutations, `results.js` owns observation/metric/reward/result assembly, `renderFrames.js` owns observer-only external frames, and `scratch.js` owns reusable buffer cleanup. It owns per-driver episode bookkeeping for batched environments and formats result state as `full`, `minimal`, or `none` without changing simulator stepping. `resetDrivers()` reapplies normal runoff/barrier classification to the selected cars after placement and before result construction, clears only selected driver episode terminal state, and does not rewind global `info.step`. Reset-only observation scope affects the returned observation and metrics maps, while full `info.drivers` remains scoped to each controlled driver's own episode state. Reward callbacks are transition-only and run from `step(actions)`, not reset/read result construction.
107
148
  - `race/stalledDnf.js` owns the optional stalled off-track retirement rule. It consumes current wheel/track surface state after vehicle physics, ignores pre-start/red-flag/finished/pit-controlled states, tracks per-car stopped time below the configured speed threshold, and converts eligible stuck cars into the existing DNF path with `reason: 'stalled-off-track'`. Other modules should consume the resulting `outOfRace`/`dnf` state instead of reimplementing stalled detection.
108
- - `options.js` validates `controlledDrivers`, scenario participants, reset placement config, frame skip, action policy, sensor config, and episode limits.
149
+ - `options.js` validates `controlledDrivers`, scenario participants, reset placement config, frame skip, action policy, sensor config, and episode limits. Controlled IDs are validated before scenario membership. Explicit participant lists are checked during scenario resolution; `all` and `controlled-only` include validated controlled IDs by construction. Filtering normalized drivers preserves their order without a second membership validation pass.
109
150
  - `actions.js` maps normalized public controls onto simulator steering/throttle/brake controls plus pit intent and optional target compound requests. It treats steering, throttle, and brake as required vehicle-control fields so broken model output is reported instead of silently becoming zeroed controls.
110
- - `scenarios.js` owns environment reset placement presets, absolute placements, and relative traffic layouts. It applies them through simulator state APIs during environment creation/reset only; it must not create policy-time teleport or assisted-control paths.
111
- - `observations.js`, `observationVector.js`, `sensors.js`, and `events.js` build sensor-style observations, versioned fixed-schema vectors, track lookahead/curvature, ray/nearby-car readings, and global/per-driver events. `observations.js` owns object observation assembly and direct compact-source preparation, while `observationVector.js` is the single owner of model-facing vector order, schema entries, and typed-array formatting for both object-backed and direct vector paths. Observation output formatting is separate from object/vector construction so compact training loops can request vector-only results while `getObservationSpec()` remains the schema source. Vector-only mode uses a direct vector path and can return `Float32Array` buffers without building the full public object observation; schema-free ray values are appended into the final vector instead of first allocating per-ray value arrays. When a loop requests vector-only, schema-free, no-state output, `runtime.js` uses the internal `snapshotTraining()` path instead of the heavier public observation snapshot; returned state still uses public snapshot shapes. `runtime.js` also owns reusable observation scratch for environment results, and `observations.js` uses it to keep controlled-driver car lookup maps and event-grouping containers stable across headless reset/step builds while no-scratch direct calls retain fresh lookup containers. Environment-owned simulations always use the internal track query index because repeated track/surface/ray queries otherwise dominate step cost. `sensors.js` is a compatibility barrel; nearby-car scans, ray defaults, ray config normalization, track rays, surface rays, indexed ray-band intersections, car rays, body senses, contact-patch senses, boundary senses, opponent radar, and ray geometry live under `src/environment/sensors/`. Expensive surface-ray channels are opt-in and appear in the vector schema whenever requested. Replay-ghost sensor type flags are also part of compact ray/nearby-car vectors so vector-only consumers can distinguish real cars from opt-in references. Driver rays use a precision-aware cached origin state, shared per-observation ray target context, and optional per-batch scratch storage for ray-detectable target arrays and target wrappers, rich ray result containers, shared indexed-query objects, filtered car targets, reusable ray-boundary offset/distance/segment-id containers, reusable tracer result containers, validated boundary samples, and bounded sampled-recovery samples. Scratch reuse is internal to environment/render loops; exported sensor calls without scratch continue returning fresh arrays/objects. Driver `precision: 'driver'` road-edge, kerb, and illegal-surface channels use direct indexed ray-band intersections for safe main-track geometry, including track edge, kerb outer edge, gravel edge, and runoff edge. Normal driver candidates are validated against driver-precision track state before they become model-facing values; validated off-track recovery boundaries can also remain on the direct path. The ray-band tracer owns boundary-distance scratch and the returned road-edge/kerb/illegal-surface result shell through the per-ray shared query so direct model-facing rays reuse their offset list, distance list, segment-id list, boundary result object, and channel result objects instead of rebuilding those containers per ray. Pit-lane-origin side rays whose current pit-road frame proves the lateral road boundary resolve directly from pit-road normal/width geometry with no nearest-track sampling; ambiguous recovery, pit-connector, and unproven pit-lane-origin rays stay inside the ray-band tracer as a single bounded sampled-recovery pass for all requested road/surface channels. Debug precision also uses that tracer-owned route with additional sampled refinement for diagnostic distances; only missing-index cases use the older sampled fallback path. The `batch-training` interaction profile now uses that same tracer-owned road/surface path for safe main-track rays, including kerb/illegal-surface-only batches that synthesize same-pose origin state from `progress`/`signedOffset` and pass a main-track-only tracer mode so pit-connector-adjacent training rays stay on direct indexed geometry instead of sampled connector recovery. Destroyed/out-of-race cars keep the active ray schema but short-circuit to miss-valued rays before creating track ray contexts, which prevents terminal cars in batch training from repeatedly querying far-out geometry. Ray precision defaults to the model-facing `driver` contract; `debug` precision is diagnostic-only and must not be silently substituted into policy inputs or model-sense visualization.
151
+ - `scenarios.js` composes reset placement normalization (`scenarioOptions.js`), preset definitions (`scenarioPresets.js`) and mutations (`scenarioPlacements.js`). Together they own absolute placements and relative traffic layouts and apply them through simulator state APIs during environment creation/reset only; it must not create policy-time teleport or assisted-control paths.
152
+ - `observations.js`, `observationSchema.js`, `observationVector.js`, `sensors.js`, and `events.js` build sensor-style observations, versioned fixed-schema vectors, track lookahead/curvature, ray/nearby-car readings, and global/per-driver events. `observations.js` orchestrates output formatting, `driverObservations.js` owns object observation assembly and direct compact-source preparation, and `sensorOptions.js` supplies the effective per-driver sensor contract to both observations and specs; `observationSchema.js` owns field names, order, units, and scales shared by observations and specs. `observationVector.js` owns numeric encoding and typed-array formatting for both object-backed and direct vector paths; schema-free calls do not construct schema metadata. Surface fields follow canonical kerb-then-illegal-surface order regardless of channel option order. Observation output formatting is separate from object/vector construction so compact training loops can request vector-only results while `getObservationSpec()` remains the schema source. Vector-only mode uses a direct vector path and can return `Float32Array` buffers without building the full public object observation; schema-free ray values are appended into the final vector instead of first allocating per-ray value arrays. When a loop requests vector-only, schema-free, no-state output, `runtime.js` uses the internal `snapshotTraining()` path instead of the heavier public observation snapshot; returned state still uses public snapshot shapes. `runtime.js` also owns reusable observation scratch for environment results, and `observations.js` uses it to keep controlled-driver car lookup maps and event-grouping containers stable across headless reset/step builds while no-scratch direct calls retain fresh lookup containers. Environment-owned simulations always use the internal track query index because repeated track/surface/ray queries otherwise dominate step cost. `sensors.js` and `sensors/index.js` preserve compatibility exports. `rayScratch.js` owns batch pools/caches, `rayQuery.js` owns shared execution/channel resolution, `rayOutputs.js` owns rich/vector encoding, and `rayBatch.js` composes public rich and direct-vector calls; nearby-car scans, ray defaults, ray config normalization, track rays, surface rays, indexed ray-band intersections, car rays, body senses, contact-patch senses, boundary senses, opponent radar, and ray geometry live under `src/environment/sensors/`. Expensive surface-ray channels are opt-in and appear in the vector schema whenever requested. Replay-ghost sensor type flags are also part of compact ray/nearby-car vectors so vector-only consumers can distinguish real cars from opt-in references. Driver rays use a precision-aware cached origin state, shared per-observation ray target context, and optional per-batch scratch storage for ray-detectable target arrays and target wrappers, rich ray result containers, shared indexed-query objects, filtered car targets, reusable ray-boundary offset/distance/segment-id containers, reusable tracer result containers, validated boundary samples, and bounded sampled-recovery samples. Scratch reuse is internal to environment/render loops; exported sensor calls without scratch continue returning fresh arrays/objects. Driver `precision: 'driver'` road-edge, kerb, and illegal-surface channels use direct indexed ray-band intersections for safe main-track geometry, including track edge, kerb outer edge, gravel edge, and runoff edge. Normal driver candidates are validated against driver-precision track state before they become model-facing values; validated off-track recovery boundaries can also remain on the direct path. The ray-band tracer owns boundary-distance scratch and the returned road-edge/kerb/illegal-surface result shell through the per-ray shared query so direct model-facing rays reuse their offset list, distance list, segment-id list, boundary result object, and channel result objects instead of rebuilding those containers per ray. Pit-lane-origin side rays whose current pit-road frame proves the lateral road boundary resolve directly from pit-road normal/width geometry with no nearest-track sampling; ambiguous recovery, pit-connector, and unproven pit-lane-origin rays stay inside the ray-band tracer as a single bounded sampled-recovery pass for all requested road/surface channels. Debug precision also uses that tracer-owned route with additional sampled refinement for diagnostic distances; only missing-index cases use the older sampled fallback path. The `batch-training` interaction profile now uses that same tracer-owned road/surface path for safe main-track rays, including kerb/illegal-surface-only batches that synthesize same-pose origin state from `progress`/`signedOffset` and pass a main-track-only tracer mode so pit-connector-adjacent training rays stay on direct indexed geometry instead of sampled connector recovery. Destroyed/out-of-race cars keep the active ray schema but short-circuit to miss-valued rays before creating track ray contexts, which prevents terminal cars in batch training from repeatedly querying far-out geometry. Ray precision defaults to the model-facing `driver` contract; `debug` precision is diagnostic-only and must not be silently substituted into policy inputs or model-sense visualization.
112
153
  - `rayChannels.js` owns the canonical ray channel list, reusable scalar channel-flag containers, and legal-surface predicate used by the ray-band tracer. Scratch-backed driver rays write requested-channel dispatch into the per-ray shared query instead of allocating channel `Set` instances, and runtime benchmarks require zero channel-set allocations plus stable channel-flag containers on the common direct ray path. Scratch-backed ray batches also cache normalized raw ray option objects and keep ray-option normalization on scalar loops, so rich/vector/append sensor passes reuse the same normalized rays and channel arrays. Scratch-backed car-ray target discovery also keeps the all-target wrapper array and each self-filtered per-car target array stable, using scalar writes rather than callback-driven list rebuilding before the exact footprint checks.
113
- - `metrics.js` owns reward-neutral per-driver facts such as progress delta, legal progress delta, off-track/severe-cut state, destroyed state, low-speed state, spin/backwards/instability state, lap completion, lap time, and contact count. Contact metrics dedupe the identifiers on each normalized physical contact event so one event increments each involved driver once. `runtime.js` owns reusable metric scratch for environment result builds, and `metrics.js` uses it to keep previous/current car lookup maps plus contact-count grouping containers stable across headless reset/step builds while no-event builds skip contact-count containers. It must not encode or choose rewards.
154
+ - `events.js` owns driver-ID extraction and deduplication into caller-owned scratch. Observations own event distribution; metrics own contact filtering/counting. Their scratch containers remain separate.
155
+ - `metrics.js` owns reward-neutral per-driver facts such as progress delta, legal progress delta, off-track/severe-cut state, destroyed state, low-speed state, spin/backwards/instability state, lap completion, lap time, and contact count. Contact metrics dedupe the identifiers on each normalized physical contact event so one event increments each involved driver once. `runtime.js` owns reusable metric scratch for environment result builds, and `metrics.js` uses it to keep previous/current car lookup maps plus contact-count grouping containers stable across headless reset/step builds while no-event builds skip contact-count containers. It must not encode or choose rewards. `events.js` owns shared physical-contact classification and deduplicated event membership; metrics, evaluation and starter rewards consume that same definition.
114
156
  - `recorder.js` owns neutral rollout transition export. It snapshots observations, actions, rewards, next observations, terminal flags, and info at record time and does not interpret rewards or policies.
115
- - `controllerLoop.js` owns browser/headless controller orchestration around an injected runtime. It may lazily reset a fresh runtime before scheduled playback, but public `reset()` remains an explicit playback stop. Controller contexts report current `actions` separately from `previousActions`, where previous means the last action map actually applied before the current physics step. It maintains an observation cache so reset-only `resetDrivers()` results can stay small without handing null observations to the next batch decision for non-reset controlled drivers. Scheduled playback catches controller/runtime failures, stops the loop, and exposes the thrown value through loop stats.
116
- - `local-preview/src/policyRunner/controllers.js` owns the browser Policy Runner HTTP policy-server transport. Its protocol is intentionally compact: reset sends static specs/configuration plus `previousActionFields` and `metricFields` once, while per-decision JSON contains only `driverIds` plus aligned numeric `vectors`, compact previous-action tuples, compact metric tuples, and events. It must not reintroduce full observation objects, schemas, snapshots, or static track metadata into `/policy/decide-batch`; those belong in reset/init metadata or explicit debug/export surfaces.
117
- - `evaluation.js` owns deterministic evaluation cases and metrics. It can run a supplied policy against fixed seeds/scenarios, internally requests the minimal snapshot needed when compact no-state base options are supplied, and reuses environment metric semantics for legal surfaces and contacts. It must not update models or define reward objectives.
157
+ - `controllerLoop.js` owns browser/headless controller orchestration around an injected runtime. It may lazily reset a fresh runtime before scheduled playback, but public `reset()` remains an explicit playback stop. Controller contexts report current `actions` separately from `previousActions`, where previous means the last action map actually applied before the current physics step. It maintains an observation cache so reset-only `resetDrivers()` results can stay small without handing null observations to the next batch decision for non-reset controlled drivers. Scheduled playback catches current controller/runtime failures, stops the loop, and exposes the thrown value through loop stats. `controllerScheduler.js` owns scheduler adapters. Each loop generation owns an AbortSignal supplied to hooks; stop/full reset/partial reset invalidate pending work and abort cooperative requests. Obsolete completions/errors cannot mutate resumed playback. Controllers may ignore cancellation, but stale actions still cannot step the replacement generation.
158
+ - `site/src/showcase/policyRunner/controllers.js` owns the browser Policy Runner HTTP policy-server transport. Its protocol is intentionally compact: reset sends static specs/configuration plus `previousActionFields` and `metricFields` once, while per-decision JSON contains only `driverIds` plus aligned numeric `vectors`, compact previous-action tuples, compact metric tuples, and events. It must not reintroduce full observation objects, schemas, snapshots, or static track metadata into `/policy/decide-batch`; those belong in reset/init metadata or explicit debug/export surfaces.
159
+ - `evaluation.js` owns deterministic evaluation cases and `evaluationTracker.js` owns accumulated metrics and explicit lap scope. Evaluation can run a supplied policy against fixed seeds/scenarios, internally requests the minimal snapshot needed when compact no-state base options are supplied, and reuses environment metric semantics for legal surfaces and contacts. It must not update models or define reward objectives.
118
160
  - `workerProtocol.js` owns the JSON-serializable wrapper used by external bridges. It forwards reset/step/spec/state messages to an environment instance, including `getState` output selection, and does not choose Python, Gymnasium, PettingZoo, or storage infrastructure.
119
161
 
120
162
  The environment subpath must not import `src/index.js`, package CSS, PixiJS, `F1SimulatorApp`, or DOM-specific code.
121
163
 
122
164
  `src/simulation/rulesConfig.js` is the public rules normalization facade. Defaults live in `src/simulation/rules/ruleDefaults.js`, ruleset presets in `rulesetPresets.js`, merge/value helpers in `ruleConfigMerge.js`, module normalization in `moduleConfig.js`, and penalty consequence normalization in `penaltyConfig.js`. Race modules should be added through those focused files first, then consumed by simulation logic through the normalized `race.rules` object.
123
165
 
124
- `src/simulation/track/trackModel.js` is the canonical track-model facade. Procedural centerline generation, validation, sample rebuilding, start-straight normalization, sectors, DRS zones, pit-lane layout/access/state, spatial query indexing, and spatial queries live in focused `src/simulation/track/*` modules. `src/simulation/trackModel.js` remains a compatibility re-export for tests or older internal imports, but new production code should import the canonical track module. `buildTrackModel()` attaches a non-enumerable internal `track.queryIndex` after samples, DRS, sectors, and pit-lane geometry are finalized, and cached built models freeze their static geometry so consumers cannot mutate shared pit-lane data between builds. Race simulations clone the built track to own mutable timing/pit state and always attach a fresh runtime index fork. `trackQueryIndex.js` owns the internal indexed-query orchestration over typed centerline segment data, arc-length buckets, local segment neighborhoods, surface band thresholds, and deterministic nearest/ray query selection. Generic uniform grid operations live in `trackQueryGrid.js`, scratch buffer/epoch reuse lives in `trackQueryScratch.js`, query diagnostics live in `trackQueryStats.js`, and pit-lane road/box grid construction plus pit candidate lookups live in `trackQueryPitIndex.js`. Public callers still use `nearestTrackState()` and the same snapshot shapes; the index is internal infrastructure and must not leak into JSON snapshots or host contracts. `trackSnapshotJson.js` now owns the compact JSON-only track serializer: in-memory snapshots keep the full live track object, while `JSON.stringify(snapshot.track)` reuses a cached rounded public geometry view, decimates exported track samples into a schema-plus-array table, and drops engine-only pit bounds, pit connector bounds, pit-crew metadata, and duplicated pit-box/service-area team fields so repeated full-state transport does not pay to serialize query helpers, repeated object keys, or redundant static pit metadata. Indexed nearest lookup treats progress hints only as optimization bounds, resolves equal-distance centerline segment ties deterministically inside the index, and proves finite-point nearest projections through the tight segment grid before falling back to a deterministic global indexed segment scan only if the grid cannot produce candidates. Low-density cell exact queries are certified by precomputed ring-1 segment neighborhoods plus ring-2 cell lower bounds before arc buckets run, so gravel-adjacent nearest lookups stay on the grid-owned proof path unless the local cell density or lower-bound check requires broader refinement. Outside-runoff nearest lookups use the sparse exact segment-grid walk directly before arc buckets, so far-out recovery and destroyed-car classifications stay on a grid-owned exact proof instead of seeding that same exact result through arc candidates first. Hinted local arc projections now scan deduped arc-bucket candidates directly instead of materializing transient candidate arrays before projection, while local segment-neighborhood projections reuse an already-known centerline segment id to stay on a smaller exact neighborhood for connector and runoff fast paths, write their final projections into reusable target objects instead of allocating a fresh projection result on every exact local query, and solve batched wheel-center neighborhoods through one shared segment scan so connector local-refresh queries do not walk the same exact neighborhood four separate times. Missing indexes are programmer errors on built runtime tracks. Ray-boundary queries walk the tight segment grid along the ray corridor instead of querying the expanded recovery grid, which keeps compact training-track profiles from producing thousands of false candidate segments per ray. The model explicitly straightens the start/finish window so the starting grid, pit-entry approach area, and immediate post-line exit area are geometrically straight instead of merely choosing the least-curved generated segment. The pit lane is generated next to that start straight for every built track and exposes entry/exit connectors, explicit procedural road centerlines that overlap the track surface, connect on the lane-facing side of the main track, are tangent to the track at merge points and to the main pit lane at pit-lane endpoints, a straight main fast lane sized from the team/box run plus bounded entry/exit buffers, a parallel working lane, one shared service area per team, queue geometry behind each service area, two garage boxes per team, and connector-only runtime bounds used to fence pit-aware runoff and query helpers without broadening the whole pit-straight AABB. `src/rendering/proceduralTrackAsset.js` is now only the Pixi container lifecycle wrapper; main-circuit drawing, pit-lane drawing, grid rendering, finish-line rendering, material constants, render geometry, and offset-stroke safety live under `src/rendering/track/`. `F1SimulatorApp` overlays the dynamic pit-lane status light because open/closed/red-flag state belongs to race control, not the static track texture.
166
+ `src/simulation/track/trackModel.js` is the canonical track-model facade. `trackBuilder.js` owns geometry assembly, `proceduralTrack.js` owns generation attempts and validation retries, and `trackModelCache.js` owns immutable definitions, model freezing and the eight-entry procedural LRU cache. `pitServiceLayout.js` owns service/queue/garage geometry while `pitLaneLayout.js` retains lane placement and composition; `pitLaneModel.js` owns runtime pit-model cloning. Procedural centerline generation, validation, sample rebuilding, start-straight normalization, sectors, DRS zones, pit-lane layout/access/state, spatial query indexing, and spatial queries live in focused `src/simulation/track/*` modules. `src/simulation/trackModel.js` remains a compatibility re-export for tests or older internal imports, but new production code should import the canonical track module. `buildTrackModel()` attaches a non-enumerable internal `track.queryIndex` after samples, DRS, sectors, and pit-lane geometry are finalized, and cached built models freeze their static geometry so consumers cannot mutate shared pit-lane data between builds. Cached grid cell arrays and their buckets are frozen with the other static geometry. Internal typed index buffers are shared and treated as immutable; they are not a supported mutation surface. Race simulations clone the built track to own mutable timing/pit state and always attach a fresh runtime index fork with separate mutable query scratch and statistics. `trackQueryIndex.js` preserves the indexed-query export surface. `trackQueryConstruction.js` owns index construction and runtime forks; `trackQuerySegments.js` owns segment storage, projection and precomputed neighborhoods; `trackNearestQueries.js` owns deterministic nearest/hinted/local/batch selection; `trackRayQueries.js` owns ray traversal and its bounded cache. Their specialized scalar/batch kernels and candidate ordering remain distinct. Each runtime centerline wrapper is frozen and shares immutable geometry buffers, but references its own query statistics; cached geometry must never receive another simulation’s diagnostic counts. Generic uniform grid operations live in `trackQueryGrid.js`, scratch buffer/epoch reuse lives in `trackQueryScratch.js`, query diagnostics live in `trackQueryStats.js`, and pit-lane road/box grid construction plus pit candidate lookups live in `trackQueryPitIndex.js`. Public callers still use `nearestTrackState()` and the same snapshot shapes; the index is internal infrastructure and must not leak into JSON snapshots or host contracts. `trackSnapshotJson.js` now owns the compact JSON-only track serializer: in-memory snapshots keep the full live track object, while `JSON.stringify(snapshot.track)` reuses a cached rounded public geometry view, decimates exported track samples into a schema-plus-array table, and drops engine-only pit bounds, pit connector bounds, pit-crew metadata, and duplicated pit-box/service-area team fields so repeated full-state transport does not pay to serialize query helpers, repeated object keys, or redundant static pit metadata. Indexed nearest lookup treats progress hints only as optimization bounds, resolves equal-distance centerline segment ties deterministically inside the index, and proves finite-point nearest projections through the tight segment grid before falling back to a deterministic global indexed segment scan only if the grid cannot produce candidates. Low-density cell exact queries are certified by precomputed ring-1 segment neighborhoods plus ring-2 cell lower bounds before arc buckets run, so gravel-adjacent nearest lookups stay on the grid-owned proof path unless the local cell density or lower-bound check requires broader refinement. Outside-runoff nearest lookups use the sparse exact segment-grid walk directly before arc buckets, so far-out recovery and destroyed-car classifications stay on a grid-owned exact proof instead of seeding that same exact result through arc candidates first. Hinted local arc projections now scan deduped arc-bucket candidates directly instead of materializing transient candidate arrays before projection, while local segment-neighborhood projections reuse an already-known centerline segment id to stay on a smaller exact neighborhood for connector and runoff fast paths, write their final projections into reusable target objects instead of allocating a fresh projection result on every exact local query, and solve batched wheel-center neighborhoods through one shared segment scan so connector local-refresh queries do not walk the same exact neighborhood four separate times. Missing indexes are programmer errors on built runtime tracks. Ray-boundary queries walk the tight segment grid along the ray corridor instead of querying the expanded recovery grid, which keeps compact training-track profiles from producing thousands of false candidate segments per ray. The model explicitly straightens the start/finish window so the starting grid, pit-entry approach area, and immediate post-line exit area are geometrically straight instead of merely choosing the least-curved generated segment. The pit lane is generated next to that start straight for every built track and exposes entry/exit connectors, explicit procedural road centerlines that overlap the track surface, connect on the lane-facing side of the main track, are tangent to the track at merge points and to the main pit lane at pit-lane endpoints, a straight main fast lane sized from the team/box run plus bounded entry/exit buffers, a parallel working lane, one shared service area per team, queue geometry behind each service area, two garage boxes per team, and connector-only runtime bounds used to fence pit-aware runoff and query helpers without broadening the whole pit-straight AABB. `src/rendering/proceduralTrackAsset.js` is now only the Pixi container lifecycle wrapper; main-circuit drawing, pit-lane drawing, grid rendering, finish-line rendering, material constants, render geometry, and offset-stroke safety live under `src/rendering/track/`. `F1SimulatorApp` overlays the dynamic pit-lane status light because open/closed/red-flag state belongs to race control, not the static track texture.
125
167
 
126
168
  `src/simulation/rules/` owns focused rule calculation helpers. Each stewarded rule should have its own function and, once non-trivial, its own file. `raceSimulation.js` should orchestrate timing/state and call those helpers; rule modules should return events or penalty payloads rather than mutating the whole simulation directly.
127
169
 
128
- `src/api/PaddockSimulatorController.js` owns composable mounting.
170
+ `src/api/PaddockSimulatorController.js` exposes composable mounting and runtime commands; `MountedSimulatorComponents.js` owns surface rendering, composite querying and layout-support lifetimes.
129
171
 
130
172
  Responsibilities:
131
173
 
132
174
  - Resolve host options once for component templates and runtime.
133
- - Render each independently mounted component into its host root.
175
+ - Render each independently mounted component into one owned host root, clearing a surface's previous root when it is replaced and rejecting ambiguous root reuse across different surfaces.
134
176
  - Build a composite DOM query surface for `F1SimulatorApp`.
135
- - Initialize and control the shared simulator runtime through `start()`, `restart()`, `destroy()`, and state methods.
177
+ - Initialize and control the shared simulator runtime through retry-safe, concurrent-call-coalesced `start()`, `restart()`, `destroy()`, and state methods.
136
178
  - Expose explicit safety-car methods for callers that want to deploy or release the safety car without using package-rendered buttons.
137
179
 
138
180
  ## Runtime App
139
181
 
182
+ `pixiStartupLifecycle.js` releases Pixi's default inline touch-action and automatic default-event cancellation after initialization. Package CSS owns canvas gesture policy: native touch scrolling in Race, drag ownership only in active Grid Walk. Workbench wheel bindings cancel only an actual zoom change; Grid Walk cancels only an actual pan. Both defer to browser zoom, prior host handling and non-cancelable scrolling, and their listeners share runtime abort ownership. Global visibility scroll listeners remain passive; no body/document scroll lock is applied.
183
+
184
+ DOM bindings group each overview surface with its own identity nodes, fields and render cache. Repeated timing, restart, zoom and overview controls share runtime commands while retaining independent mounted state. Timing buttons use native click activation, including keyboard selection. Host Open actions use the same callback failure isolation as other host callbacks.
185
+
140
186
  `src/app/F1SimulatorApp.js` owns browser runtime orchestration.
141
187
 
142
188
  Responsibilities:
@@ -151,22 +197,32 @@ Responsibilities:
151
197
  - Restart behavior for race data, seed, and track changes. Asset URL changes and browser expert mode changes are intentionally outside restart because texture loading and ticker ownership are part of initialization.
152
198
  - Lifecycle cleanup, including partial-init failure cleanup and destruction of replaced PixiJS display children without destroying shared textures.
153
199
 
200
+ `src/app/HostLifecycleEmitter.js` owns host callback failure isolation, leader-lap transition tracking, race-finish delivery, and event de-duplication. It receives the same authoritative snapshots produced by both normal ticker frames and expert-driven frames. Event identity includes stable simulation identifiers so distinct same-time penalties or contacts are not collapsed merely because their timestamps match.
201
+
202
+ Race callback batches capture the current simulation identity and lifecycle generation. A host unmount, simulation replacement or lifecycle reset invalidates the batch after each callback, before remaining event/finish delivery or lap bookkeeping can write obsolete state into the successor race. Standalone error/loading callbacks retain their normal failure-isolated delivery, including cleanup errors after destruction.
203
+
204
+ `src/app/runtime/pixiStartupLifecycle.js` owns pending Pixi application resources. Destruction during asynchronous renderer initialization requests release after initialization settles, rather than calling uninitialized application plugins. Runtime startup checks cancellation after renderer/asset awaits and ready callbacks; late assets cannot restore disposed state. Independent cleanup continues after a disposer failure and reports errors through `onError` with `phase: 'destroy'`, while preserving the original initialization failure. `frameLoop.js` stops a frame after a lifecycle callback destroys the app or replaces its simulation, preventing stale snapshots from rendering into the replacement race.
205
+
206
+ Pixi initialization uses a stopped, private ticker. `runtimeTiming.js` applies the package's 60 FPS ceiling to the actual Pixi ticker before visible playback starts, so high-refresh displays do not redraw unchanged scene state between simulator updates. Fixed-step catch-up still accounts for elapsed wall time; headless stepping and explicit expert rendering retain their own control flow.
207
+
154
208
  `src/app/camera/` owns camera state, camera constants, camera-safe-area measurement, track/pit-lane bounds fitting, zoom limits, mode availability, rotation, and target/frame calculation for overview, leader, selected, driver, show-all, and pit cameras.
155
209
 
156
210
  `src/app/rendering/` owns PixiJS rendering surfaces:
157
211
 
158
212
  - `appAssets.js` loads package-owned textures and applies fallback textures.
159
- - `carRenderer.js` owns car sprites, hit areas, service countdown labels, and safety-car sprite rendering.
213
+ - `carRenderer.js` owns car sprites, hit areas, service countdown labels, one-time participant interaction markers, and safety-car sprite rendering.
160
214
  - `trackRenderer.js` renders dynamic DRS zone overlays around the procedural track asset.
161
215
  - `drsTrailRenderer.js` owns DRS trail histories and drawing.
162
216
  - `pitLaneStatusRenderer.js` owns pit-lane open/closed light rendering and redraw keys.
163
217
  - `expertSensorRenderer.js` owns opt-in expert sensor-ray drawing.
164
- - `replayGhostRenderer.js` owns translucent replay ghost overlays. Replay ghosts are not interactive car hit areas and must not feed timing rows or selected-car behavior.
165
- - `displayUtils.js` owns shared Pixi display cleanup and color/angle helpers.
218
+ - `replayGhostRenderer.js` owns translucent replay ghost overlays. Halo geometry is rebuilt only when tint or effective opacity changes; pose and sprite/label updates remain per-frame. Replay ghosts are not interactive car hit areas and must not feed timing rows or selected-car behavior.
219
+ - `displayUtils.js` owns color/angle helpers. `src/rendering/displayLifecycle.js` owns shared display-child destruction for both app and procedural track renderers, preserving shared textures and texture sources.
220
+
221
+ `src/app/controls/workbenchBindings.js` owns DOM gestures and listener disposal through an explicit AbortSignal and narrow command callbacks. It receives DOM references rather than an app/simulation owner. `workbenchDisclosure.js` owns drawer/timing disclosure classes, labels, visibility policy, `aria-*` and `inert` presentation; the app retains dynamic race selection/options, drawer state, camera invalidation and layout orchestration. Overlay clearance synchronization writes class/style state only when values change, preventing its own MutationObserver from scheduling redundant layout passes.
166
222
 
167
223
  `src/app/readouts/` owns generated-DOM readout rendering:
168
224
 
169
- - `timingTowerRenderer.js` owns timing tower rows, runtime gap-mode labels/toggle state, waved-flag labels, and timing penalty badges. A single simulator may mount more than one timing tower through standalone and embedded composable surfaces, so timing row rendering fans out through plural timing-list bindings while keeping row markup/caching in this focused renderer. DOM-capable timing lists use keyed row updates that reuse existing `li`/`button` nodes and update only changed text, classes, styles, and penalty badges; non-DOM test stubs keep the string `innerHTML` fallback.
225
+ - `timingTowerRenderer.js` owns timing tower rows, runtime gap-mode labels/toggle state, waved-flag labels, and timing penalty badges. A single simulator may mount more than one timing tower through standalone and embedded composable surfaces, so timing row rendering fans out through plural timing-list bindings while keeping row markup/caching in this focused renderer. DOM-capable timing lists use keyed row updates that reuse existing `li`/`button` nodes and update only changed text, attributes, classes, styles, and penalty badges. Rows move only when their position changes, preserving focused buttons during gap-only refreshes; non-DOM test stubs keep the string `innerHTML` fallback.
170
226
  - `telemetryRenderer.js` owns selected-car telemetry and lap/sector readouts.
171
227
  - `carOverviewRenderer.js` owns driver/vehicle overview field selection and rendering.
172
228
  - `raceStatusRenderer.js` owns race mode/lap/DRS/contact/start-light/finish readouts.
@@ -175,7 +231,12 @@ Responsibilities:
175
231
 
176
232
  `src/app/banners/` owns banner state and rendering helpers for race-data lower-thirds, project-radio scheduling/rendering, and steward warning/penalty messages. The app keeps the public state fields that tests and host-facing methods use, while the banner modules own the rendering and scheduling mechanics.
177
233
 
178
- `src/app/runtime/` owns frame pacing, DOM/timing update intervals, ticker visibility throttling, and fixed-step render-loop execution. `F1SimulatorApp` delegates ticker work to this runtime layer.
234
+ Radio scheduling transitions have one implementation in `raceDataBannerController.js`. The app supplies radio state, time, drivers, enabled-state lookup, and its seeded random callback; the feature preserves deadline catch-up and random-call order.
235
+
236
+ `src/app/runtime/` owns frame pacing, DOM/timing update intervals, ticker visibility throttling, and fixed-step render-loop execution. Pixi's private ticker owns the 60 FPS draw cap; the runtime does not apply a second frame gate. Each draw accumulates measured wall-clock elapsed time, capped at four target frame intervals for bounded stall recovery, and interpolates the remaining fraction of a deterministic fixed physics step. `src/rendering/renderSnapshot.js` renders destroyed/out-of-race entities directly at their frozen current pose instead of reusing stale previous endpoints. The same elapsed interval drives exponential camera damping, keeping follow/scale/rotation response consistent across irregular draw intervals. Visibility resume resets the wall clock rather than advancing through hidden time. `F1SimulatorApp` delegates ticker work to this runtime layer and forwards elapsed time to the camera feature.
237
+
238
+ `src/simulation/driver/trafficScan.js` owns synchronous pooled physical traffic scans, wrapped longitudinal gaps, chassis offsets in the main-track frame, following limits and alongside movement bounds. `racingLinePlan.js` chooses physical passing/defensive targets, owns the traffic penalty shared by arcade/advanced pacing, and exposes scalar following/corridor values from the same scan. Controllers compose their final path according to their physics mode: arcade applies traffic corner-load line discipline and the following cap after speed floors; advanced applies alongside bounds to its final target and gives inward edge correction priority over corner line hold. Advanced preview/target geometry uses separate reusable `pointAtInto()` scratch objects, so sampling does not allocate a point per preview distance and decisions do not rescan the full field for following.
239
+ `driver/advancedRacePace.js` reuses maximum-speed and zero-speed lateral-capacity estimates only within one synchronous braking-preview decision. Curvature arithmetic and intermediate-speed capacity queries remain unchanged; each new decision recomputes endpoints from current tires, wheel surfaces and aero/DRS state.
179
240
  Warmup ownership is split intentionally: `src/simulation/warmup/runtimeWarmup.js` owns warmup policy normalization, configuration fingerprint caching, and guarded execution; browser/environment/simulation call sites decide which disposable pre-step work to run for their surface.
180
241
 
181
242
  When `options.expert.enabled` is true, `F1SimulatorApp` creates a narrow browser expert adapter around its existing `this.sim` race simulation. The adapter uses the same shared environment runtime as the headless API, but it must not create a second race simulation for the visual mount. Expert browser mounts disable automatic ticker-driven simulation advancement; the canvas updates only after `simulator.expert.reset()` or `simulator.expert.step(actions)` renders the new snapshot. External renderer mode may temporarily redraw the shared track asset from authoritative external snapshots; detach restores the local simulation track before local expert stepping resumes. The shared runtime owns controlled-driver pit intent defaults: controlled cars start with `pitIntent` set to `0`, tire-threshold automatic pit calls are disabled for them, and expert actions may request `0`, `1`, or `2` plus optional `pitCompound` through the simulation API instead of steering into the pit lane directly. Expert mode is a mount-time boundary; runtime restart rejects `expert` changes instead of attempting to rewire ticker ownership in place.
@@ -190,6 +251,8 @@ Track material rendering must use the same surface-band thresholds as simulation
190
251
 
191
252
  Environment sensor target selection belongs to `src/environment/sensors/sensorTargets.js`. It adapts real cars and opt-in replay ghosts into a common sensor target shape while preserving the invariant that replay ghosts stay out of `snapshot.cars` and race systems. Radar enrichment keys targets by entity type plus id, so duplicate ids between real cars and replay ghosts do not cross-contaminate velocity-derived nearby-car facts.
192
253
 
254
+ Observation assembly lazily prepares nearby target projections and their radar lookup once per observation build, sharing them across controlled drivers. A context never survives that build or caches by mutable snapshot identity; returned nearby records stay fresh and driver-specific. The bounded nearest shortlist rejects losing candidates before allocating their payloads, retaining squared-distance and stable target-order tie rules. `site/src/showcase/policyRunner/senseReadout.js` owns sense presentation from the active observation object/vector, including missing and compact-vector observations; it does not query simulation state or recompute senses.
255
+
193
256
  `F1SimulatorApp.js` should stay a facade/orchestrator. New camera, rendering, readout, banner, or runtime behavior should usually land in the matching `src/app/*/` ownership module rather than expanding the app class.
194
257
 
195
258
  `src/app/domBindings.js` owns DOM selector lookup plus null-safe text and CSS custom-property writes for package-generated UI surfaces. It exposes both singular compatibility handles and plural collections for package-owned readouts that can appear more than once in the same simulator root, such as timing tower rows, lap counters, race-control status slots, and race-data panels. Shared helpers in this module resolve plural collections with singular fallbacks, skip duplicate node work, and avoid repeated identical text/style writes, so focused renderers do not each reimplement that control flow. Race-data panel bindings are grouped at lookup time so banner renderers and controls update embedded lower-thirds and standalone panels without repeated runtime DOM queries. Car/driver overview field label/value nodes are also grouped once at lookup time so overview renders update cached field bindings instead of rediscovering children on every changed overview render.
@@ -211,6 +274,8 @@ Responsibilities:
211
274
 
212
275
  The fixed-step loop is a phase pipeline. Normal active steps read the previously committed order/gap/DRS/rank state, update race control, choose controls, run local car motion, pit routing, runoff, collisions, local surface refresh, stalled-DNF review, and then perform one broad race-state commit. The broad commit is the only normal active-step phase that recalculates race distance, laps, sector timing, timing-line crossings, order, gaps, DRS latch state, finish state, and classification-facing rank fields. Same-lap leader gaps now accumulate from already-computed interval gaps inside that ordered broad-commit loop instead of re-estimating a fresh leader-vs-car timing gap for every same-lap follower, with the direct leader estimate kept only as a fallback when interval timing is not finite. Interval gap estimation itself now bounds timing-line lookups to the overlapping stored line window for the two cars instead of scanning the whole retained line-history span every time, returns before touching timing-line crossings at all when either car has not stored a valid timing-line window yet, and skips the timing-line helper call entirely on that empty-window path so the common broad-commit wrapper falls straight back to timing-history interpolation without extra timing-line setup work. Sector-performance classification is also dirty-gated inside the commit: the simulation still keeps `lapTelemetry.sectorPerformance` exact, but it only rebuilds the field-wide current/last/best sector classification buffers when a sector split or reset changed the underlying sector arrays or when the field-wide overall best sector values changed. This intentionally allows driver decisions to react to the previous committed frame rather than forcing a broad pre-step recalculation. Early-return phases such as grid hold and red flag still leave a committed state before returning, but they do not perform an additional active-step commit.
213
276
 
277
+ `src/simulation/rules/penaltyActions.js` owns ledger mutation, grid consequences, sequence IDs and event emission behind the simulation facade’s penalty methods. `src/simulation/timing/drsState.js` owns clearing only current DRS activation/eligibility fields; it deliberately preserves detection history and next-zone hints. External placement retains its separate full DRS reset. Shared policy extraction must preserve these distinct transitions, call order and RNG use.
278
+
214
279
  `src/simulation/race/raceControlState.js` owns initial race-control and safety-car state construction.
215
280
 
216
281
  `src/simulation/race/raceLifecycle.js` owns start-light updates, grid hold/release, and pit-lane open/closed transitions.
@@ -239,15 +304,17 @@ The fixed-step loop is a phase pipeline. Normal active steps read the previously
239
304
 
240
305
  `src/simulation/pit/pitState.js` owns pit team assignment, pit-crew normalization, and pit-stop initialization.
241
306
 
242
- `src/simulation/pit/pitIntent.js` owns manual and automatic pit-intent normalization and mutation.
307
+ `src/simulation/pit/pitIntent.js` owns manual and automatic pit-intent normalization and mutation. Environment action mapping uses the same numeric normalizer while retaining ownership of optional fields, compound input shape, and strict/report errors.
243
308
 
244
309
  `src/simulation/pit/pitFlow.js` owns pit-stop flow orchestration: scheduling, rearming, entry eligibility, stop start, queue/service/exit progression, and delegation to pit routing/service helpers.
245
310
 
246
- `src/simulation/pit/pitRouting.js` owns pit-route construction, pit-lane route sampling, limiter-segment lookup, nearest-route projection, and render-pose shifts used by route/contact movement. Internal pit-route movement samples into the pit stop's reusable route point and projects against route-owned scratch so entry, queue-release, and exit transitions stay allocation-light; the compatibility `sampleRoute()` helper remains object-returning for no-scratch callers.
311
+ `src/simulation/pit/pitRouting.js` owns pit-route construction, pit-lane route sampling, limiter-segment lookup, and nearest-route projection. Previous render-pose shifts live with vehicle kinematics, and pit-model cloning lives with track ownership; compatibility exports remain available. Internal pit-route movement samples into the pit stop's reusable route point and projects against route-owned scratch so entry, queue-release, and exit transitions stay allocation-light; the compatibility `sampleRoute()` helper remains object-returning for no-scratch callers.
312
+
313
+ `src/simulation/vehicle/wheelSurface.js` composes surface calculation/application. `wheelSurfaceCache.js` owns pose/track/pit cache validity, `wheelSurfaceSummary.js` owns pooled wheel aggregation and representative state, and `sampledWheelSurface.js` owns connector/full sampled paths. Analytic main-track and pit-wheel helpers retain their specialized scalar loops; scratch and scratchless result ownership remains explicit. Extracting owners does not replace pooled buffers or broaden numerical fallback paths.
247
314
 
248
- `src/simulation/pit/pitService.js` is the public pit-service export surface and owns only the service-entry handoff. Occupancy, queue behavior, service profile timing, penalty service, tire service, pit-exit release, and pit-route movement are split across focused `src/simulation/pit/pit*.js` modules.
315
+ `src/simulation/pit/pitService.js` preserves the pit-service export surface. `pitCarPlacement.js` owns held-car pose, zero-motion telemetry, surface refresh and race-distance commitment; `pitServiceProgress.js` owns service entry, countdowns and penalty/tire/exit transitions. Route traversal stays in `pitRouteMovement.js`. Occupancy, queue behavior, service profile timing, penalty service, tire service, pit-exit release, and pit-route movement are split across focused `src/simulation/pit/pit*.js` modules.
249
316
 
250
- `src/simulation/pit/pitSnapshots.js` owns pit-lane status snapshots and public/render/observation pit-stop serializers.
317
+ `src/simulation/pit/pitSnapshots.js` owns pit-lane status snapshots and public/render/observation pit-stop serializers. It imports pit-intent policy directly; race snapshot assembly passes the serializers without injecting pit defaults through adapters. Pit snapshots copy penalty ID arrays and shallow-copy service profiles; replacing top-level profile fields does not mutate live pit state, while nested pit-crew metadata retains its existing reference ownership.
251
318
 
252
319
  `src/simulation/timing/raceTiming.js` is the timing compatibility export surface. Lap telemetry, sector performance, timing history, timing-line crossings, and gap estimation live in `lapTelemetry.js`, `sectorPerformance.js`, `timingHistory.js`, `timingLines.js`, and `gapEstimation.js`.
253
320
 
@@ -259,7 +326,7 @@ The fixed-step loop is a phase pipeline. Normal active steps read the previously
259
326
 
260
327
  `src/simulation/vehicle/runoffResponse.js` owns non-penalty off-track vehicle correction. It checks the main-track barrier boundary and records the center track state it already computed, but normal running does not apply wheel-surface state there; post-motion local refresh owns the final wheel/contact-patch classification. Normal cars now reach that barrier check through a local segment-neighborhood projection derived from the previously committed track frame when pit geometry cannot apply, so runoff stays on a geometry-owned fast path instead of paying a full indexed nearest-track query or broader hinted-arc scan every step. That fast path now uses the tightest exact neighborhood only for small committed-step motion, then widens back to the broader exact local neighborhood before any teleport fallback, which keeps common step-sized runoff queries cheap without loosening the exact track-state contract. Its main-track state and returned result wrapper are reused per car across steps, so the hot runoff path stops allocating a fresh exact-query track-state container on every frame. Pit-lane, pit-bound, and teleport-like moves still fall back to the full indexed/pit-aware query path. Barrier reach against that main-track state is computed analytically from the body, wheels, and visual footprint instead of re-projecting every corner each step.
261
328
 
262
- `src/simulation/snapshots/raceSnapshots.js` owns public, render, and observation snapshot assembly.
329
+ `src/simulation/snapshots/raceSnapshots.js` owns public, render, and observation snapshot assembly. Its pooled render writer serializes ranked cars directly, then index-sorted excluded participants, without allocating rank wrappers. A WeakMap keyed by the caller's target car array owns reusable membership/order scratch, clears participant references after each frame and adds no public fields. Fresh full/observation/render snapshots retain their existing ownership and ordering.
263
330
 
264
331
  Weather effects, reliability failures, and fuel-load performance effects are not active simulator behavior. Their future ownership should stay separate from the race orchestrator: weather belongs in a future `src/simulation/weather/` module that returns track/session condition modifiers, reliability belongs in a future `src/simulation/reliability/` module that emits deterministic failure/degradation events, and fuel-load effects belong in a future vehicle-adjacent performance module that adjusts mass/pace through explicit inputs rather than hidden mutation.
265
332
 
@@ -267,9 +334,17 @@ Weather effects, reliability failures, and fuel-load performance effects are not
267
334
 
268
335
  `src/simulation/driverController.js` is a compatibility export for AI control decisions; canonical driver policy code lives under `src/simulation/driver/`. The dispatcher selects green-flag, rejoin, and safety-car controls, while separate modules own input building, personality generation, traffic scanning, edge recovery, edge-motion analysis, rejoin behavior, safety-car queueing, and racing-line attack/defense planning. It does not move cars directly; it returns steering/throttle/brake controls for `vehiclePhysics.js` to integrate.
269
336
 
337
+ `driver/racingControls.js` dispatches physics modes and retains the existing internal export names. `arcadeRacingControls.js` owns arcade pursuit steering, lane/edge composition, traffic penalties and pedal demand. `arcadeRacePace.js` owns its curvature preview, corner-line geometry and distance-aware braking envelope. `vehicle/vehiclePhysics.js` owns the analytic tarmac corner-capacity estimate alongside the unchanged arcade yaw law. `driverController.js` owns the backward-facing arcade recovery transition; recovery itself remains in `rejoinControls.js`.
338
+
339
+ `driver/racingDriverProfile.js` maps existing constructor skills and personality into ability, consistency, risk, policy margins and continuous distance-based judgement error, without RNG advancement or physical setup mutation. `vehicle/vehicleState.js` retains normalized consistency. `arcadeRacingEnvelope.js` predicts the actual arcade longitudinal model and converts a signed speed demand into exclusive pedals; `arcadeRacePace.js` owns racing-only smooth pursuit lookahead and the physical corner/braking preview. Recovery and safety use their existing separate controllers.
340
+
341
+ `driver/advancedRacingEnvelope.js` owns dynamically loaded balanced-axle lateral bounds and rear-drive/brake-bias longitudinal feasibility. Its scratch and endpoint reuse belong to synchronous decisions, with mutable vehicle inputs refreshed on each invocation. `advancedRacingSteering.js` owns velocity-oriented pursuit, inverse brush slip and current-pedal trail-braking projection; `advancedRacingTiming.js` shares pursuit geometry and yaw response with the pace preview. `advancedRacePace.js` examines contiguous current-to-future geometry, excludes reserved pursuit/response/anticipation distance from its backward braking integration, and converts target speed into force-based pedals. `advancedRacingControls.js` composes profile, lane/edge/traffic constraints, speed demand and steering using the current pedal request. `advancedPathControls.js` retains recovery/safety control. Additive policy accessors and brush inverse live beside their canonical vehicle constitutive functions; force and integration equations remain unchanged.
342
+
343
+ `scripts/benchmark-ai-envelope.mjs` and `scripts/aiEnvelopeMetrics.mjs` own offline observational characterization. A Node module-loader hook observes all actual advanced contact/substep results; bounded histograms distinguish delivered contact forces from requested grip demand. Named driver profiles pass through public data normalization with identical explicit vehicle coefficients. Phase denominators, complete laps, off-road intervals, hashes and source identity are recorded; benchmark instrumentation is not bundled into the browser runtime.
344
+
270
345
  `src/simulation/vehicle/vehicleGeometry.js` owns deterministic car footprint math. It exposes the rendered-scale body hull, four wheel/contact-patch oriented rectangles, current/previous pose generation, AABB helpers, and a per-car `geometryState` cache keyed by current and previous pose. Collision detection, wheel-surface sampling, snapshots, and debug previews consume this module instead of each recreating car dimensions. Its current-pose geometry writer now keeps body/wheel containers stable while also writing body and wheel rectangles directly from shared heading scalars, so post-motion local refresh no longer allocates temporary axis/center objects just to rebuild the same cached geometry tree. On that current-pose cache, wheel and body corners now stay lazy by default and are only materialized when a sampled wheel-surface path actually needs them; once materialized they are updated in place and reused across pose changes. The hot current-only cache also drops the old string signature bookkeeping, because runtime consumers match pose through the cached pose object itself rather than through rebuilt `x:y:heading` strings. Root `src/simulation/vehicleGeometry.js`, `vehiclePhysics.js`, and `wheelSurface.js` are compatibility re-exports.
271
346
 
272
- `src/simulation/collisionGeometry.js` owns shape-confirmed vehicle contact. It first prunes pairs by nearby track progress with a circular-track window, then uses swept body AABBs, SAT body/body overlap checks, and conservative continuous checks between previous and current poses. Hot internal broadphase calls reuse scratch-owned candidate arrays, pooled pair arrays, source-indexed distance entries, typed pair marks for candidate de-duplication, and reusable missing-distance flag/index arrays; public no-scratch candidate queries still return fresh pair arrays. Candidate generation carries each sorted distance entry's source car index directly into hidden pair metadata and the pair marker, so the hot path no longer builds an object `Map` for car order or a fallback `Set` for missing-distance membership. The swept narrowphase now interpolates body-only shapes into reusable scratch rectangles instead of rebuilding full temporary vehicle geometry on every sweep sample. Hot internal collision calls also keep shape-contact results, vehicle-contact metadata, and their contact-axis objects in reusable scratch containers; public no-scratch collision queries still return fresh result objects. The simulation collision resolver uses the hidden candidate-pair indexes to mark already-reported fresh contacts in a scratch-owned typed epoch buffer instead of building string keys in a `Set` during the collision phase. Wheel shapes are intentionally excluded from car-vs-car collision for cost and stability; wheel geometry is still used by the surface and track-limit pipeline. `raceSimulation.js` only orchestrates response/cooldown/steward review after this module returns contact metadata, and collision stewarding derives rear-contact fault from physical track order so lapped cars are handled consistently.
347
+ `src/simulation/collisionGeometry.js` owns shape-confirmed vehicle contact. It first prunes pairs by nearby track progress with a circular-track window, rejects pairs whose current body AABBs cannot overlap, then uses swept body AABBs, SAT body/body overlap checks, and conservative continuous checks between previous and current poses. Hot internal broadphase calls reuse scratch-owned candidate arrays, pooled pair arrays, source-indexed distance entries, typed pair marks for candidate de-duplication, and reusable missing-distance flag/index arrays; public no-scratch candidate queries still return fresh pair arrays. Candidate generation carries each sorted distance entry's source car index directly into hidden pair metadata and the pair marker, so the hot path no longer builds an object `Map` for car order or a fallback `Set` for missing-distance membership. The swept narrowphase now interpolates body-only shapes into reusable scratch rectangles instead of rebuilding full temporary vehicle geometry on every sweep sample. Hot internal collision calls also keep shape-contact results, vehicle-contact metadata, and their contact-axis objects in reusable scratch containers; public no-scratch collision queries still return fresh result objects. The simulation collision resolver uses the hidden candidate-pair indexes to mark already-reported fresh contacts in a scratch-owned typed epoch buffer instead of building string keys in a `Set` during the collision phase. Wheel shapes are intentionally excluded from car-vs-car collision for cost and stability; wheel geometry is still used by the surface and track-limit pipeline. `raceSimulation.js` only orchestrates response/cooldown/steward review after this module returns contact metadata, and collision stewarding derives rear-contact fault from physical track order so lapped cars are handled consistently.
273
348
 
274
349
  `src/simulation/track/spatialQueries.js`, `pitLaneState.js`, and the pit sections of `trackQueryPitIndex.js` own drivable-region state resolution. Main-track nearest projection stays indexed through the centerline query index, while pit-road and pit-box candidate lookup stays inside the pit query index. The pit query helpers can now reuse scratch candidate arrays/route buckets for hot internal callers such as `nearestTrackState()`, so pit entry/fast-lane/working-lane/exit and pit-box classification no longer rebuild temporary candidate containers on every call. Indexed pit-road classification also projects restricted entry/fast-lane/working-lane/exit candidate segments into one reusable polyline result while reading cumulative route distances precomputed on the pit query index, instead of rebuilding candidate sets, filtered segment arrays, and distance arrays per route. Connector-adjacent direct pit-entry/pit-exit override checks use that same query-index-owned polyline projection target and route metadata. Endpoint-progress shortcuts read precomputed route endpoint segment windows, while mid-connector points without an endpoint progress hint read bounded route candidates from the pit index without becoming full pit-lane queries, so connector shortcuts avoid full-route pit-entry/pit-exit projection scans and transient projection objects. Direct sensor-ray boundary tracing also asks the query index for reusable segment IDs and then reads typed centerline arrays directly, leaving temporary segment-object materialization to the public/debug `queryTrackSegmentsAlongRay()` helper instead of the hot ray-band tracer. `pitLaneState.js` also skips pit-box grid lookups entirely when a point is outside the dedicated box/service-area bounds, which removes wasted garage/service misses from connector-adjacent wheel and track classification. Transition resolution still chooses between indexed main-track and indexed pit-lane states, but the benchmark gate now explicitly exercises pit queries and requires direct connector projection scratch reuse, positive endpoint-window connector projection calls, zero direct connector full-route projection scans, pit-road grid hits, reused pit-road projection scratch, precomputed pit-road route distances, zero cumulative-distance rebuilds, zero road-grid misses, and `pitFallbacks: 0`. `trackStatePolicy.js` also owns the car-specific runoff fast path: when the previous committed track frame is valid and pit geometry is irrelevant, it resolves the new main-track frame through an exact local segment-neighborhood projection instead of the broader nearest-track search or hinted-arc path, using the tightest exact radius only for roughly meter-scale committed motion and widening back to the broader exact neighborhood automatically when motion is larger. That single-car runoff path now writes its exact local-neighborhood result into a reusable per-car track-state target and reuses a stable result wrapper, so the common post-physics runoff query avoids per-step track-state allocation without changing the exact geometric contract. Pit-aware runoff now only exits that fast path when the previous committed state is already beyond the main-road edge on the pit-lane side and the new pose lands inside connector-only runtime bounds, so normal on-track cars near the pit straight keep the exact local query path while true pit-transition edge cases still fall back to the full pit-aware nearest resolution.
275
350
 
@@ -277,7 +352,9 @@ Weather effects, reliability failures, and fuel-load performance effects are not
277
352
 
278
353
  Timing maintenance is also kept allocation-light now. Runtime timing writes in `timingHistory.js` use a fixed structured ring store for per-car `{ time, raceDistance }` samples, so broad commits no longer allocate a fresh object for every timing sample and long-running races stay bounded without repeated array churn. Structured timing-history interpolation also caches the last successful neighborhood and probes that bracket first on the next nearby lookup, which matches the normal adjacent-car broad-commit pattern and avoids repeatedly rescanning the same trailing timing-history window from the tail. `interpolateTimeAtDistance()` still accepts the older array-of-objects shape so characterization tests and legacy internal fixtures can keep using explicit sample arrays where needed. `timingLines.js` likewise caches the first and last retained mini-sector line numbers, so pruning old timing-line crossings only walks the numeric range when the pruning cutoff actually advances instead of rescanning object keys every step, and zero-travel updates with an unchanged cutoff now stop at the timestamp refresh instead of rewalking stored line keys.
279
354
 
280
- `src/simulation/vehicle/vehiclePhysics.js` owns vehicle integration using the already-resolved wheel surfaces and wheel-state resistance imbalance. It exposes the normalized `physicsMode` contract and routes between the compatibility arcade integrator and the opt-in advanced integrator. The advanced path owns 2D velocity integration, yaw response, traction-budget calculation, speed-sensitive steering, steering scrub, aero drag/downforce balance, per-wheel surface averaging, derived slip-angle telemetry, advanced surface coefficients, and telemetry fields; steering scrub opposes signed forward motion so reverse slides do not gain energy from steering loss. Its hot wheel-surface aggregation and left/right drag-yaw resistance paths now stay on direct scalar loops instead of allocation-heavy `filter()` / `reduce()` passes, so advanced integration uses the existing wheel-state contract without paying per-step array churn. Driver AI and environment policies still provide only steering/throttle/brake/pit intent.
355
+ `src/simulation/vehicle/vehiclePhysics.js` owns public mode selection and the unchanged arcade integrator. `advancedVehicleModel.js` owns SI calibration and the drive-force envelope. `advancedTireForces.js` owns per-wheel normal loads, nonlinear combined forces and the capacity estimate shared with advanced AI. `advancedVehiclePhysics.js` owns four-contact force/moment integration and existing physics telemetry; it receives only vehicle state, actuator commands, time and narrow physical limits. `advancedContactResponse.js` owns advanced rigid-body impulses, while `contactResolution.js` retains candidate detection, positional correction, fixed pit ownership and event/steward orchestration.
356
+
357
+ Advanced forces derive from current state without persistent axle/tire caches. Simulation world units convert to SI at the vehicle boundary. Steering is a physical road-wheel angle; environment actions remain normalized at their existing boundary. Advanced path/recovery/safety-car controllers use the same vehicle capacity helpers and issue only normal controls. `site/src/demo/runtime/expertKeyboard.js` owns human-only input conditioning, focus/visibility and held-key cleanup. It ramps normalized key travel before the speed-based wheel-lock limit, and relieves throttle using applied throttle and grip usage from the active observation, never higher-precision simulation state. `site/src/demo/runtime/expertFrameScheduler.js` owns the lab's 60 Hz wall-time cadence and cancellation; `expertLab.js` passes it into the existing controller-loop scheduler interface. Display refresh does not change the lab's simulation speed, and delayed frames do not accumulate catch-up work.
281
358
 
282
359
  `src/simulation/vehicle/vehicleState.js` owns external state mutation from simulator and environment reset APIs. It rejects non-finite numeric partial state and resynchronizes advanced-mode velocity from speed/heading when callers do not provide an explicit velocity vector, so scenario placement and `resetDrivers()` cannot leave stale physics velocity behind.
283
360
 
@@ -285,13 +362,13 @@ Local model training experiments belong outside the committed package surface. T
285
362
 
286
363
  `src/simulation/rules/pitLaneSpeedingSteward.js` owns pit-speed-limit review. `raceSimulation.js` calls it after authoritative race-state recalculation so it reads the same pit-lane part classification as snapshots and track-limit stewarding. The steward only checks speed-limited pit-lane parts (`fast-lane`, `working-lane`, service areas, and garage boxes), leaving pit-entry and pit-exit connector roads legal but not speed-limited.
287
364
 
288
- Browser mounts generate a fresh procedural seed when `trackSeed` is omitted; explicit procedural track seeds are cached by seed plus resolved generation options so repeated mounts with the same procedural contract do not regenerate or rebuild the same model. The canonical generator owns both the default `race` profile and the smaller training profiles. Profile resolution lives beside the track model, merges `race` defaults, selected profile defaults, and explicit overrides, then feeds the same centerline, validation, start-straight, spatial-query, surface, and physics pipeline. Procedural generation starts from seeded connected coarse region masks, traces the outer boundary, smooths and warps the boundary into centerline controls, validates shape, clearance, local turn accumulation, length, and per-sample heading jumps, and retries with another region-derived control set instead of immediately falling back to an oval control ring when attempts fail.
365
+ Browser mounts generate a fresh procedural seed when `trackSeed` is omitted; explicit procedural track seeds are cached by seed plus resolved generation options in a bounded eight-entry least-recently-used cache. Repeated mounts with the same procedural contract reuse the immutable generated definition and built model, while evicted definitions regenerate deterministically from the same seed/options. Validation associates its already-built model with the final frozen definition so the first runtime consumer does not immediately rebuild identical geometry. The canonical generator owns both the default `race` profile and the smaller training profiles. Profile resolution lives beside the track model, merges `race` defaults, selected profile defaults, and explicit overrides, then feeds the same centerline, validation, start-straight, spatial-query, surface, and physics pipeline. Procedural generation starts from seeded connected coarse region masks, traces the outer boundary, smooths and warps the boundary into centerline controls, validates shape, clearance, local turn accumulation, length, and per-sample heading jumps, and retries with another region-derived control set instead of immediately falling back to an oval control ring when attempts fail.
289
366
 
290
367
  `src/simulation/units.js` owns conversion between simulator units and public meter/km/h display values. Physics stays in simulator units; snapshots expose calibrated display fields such as `speedKph`, `distanceMeters`, and `gapMeters`.
291
368
 
292
369
  `src/rendering/renderSnapshot.js` owns interpolation for rendering. It accepts either the full public snapshot or the lean render snapshot; render-only callers should prefer the lean snapshot. Browser frame paths may fill reusable render-source and interpolation buffers, while public snapshot methods continue returning fresh caller-owned snapshot objects.
293
370
 
294
- The app runtime pauses its PixiJS ticker when the race canvas is outside the viewport or the document is hidden, then resets the frame clock before resuming. Runtime visibility uses `IntersectionObserver` plus a bounded canvas-geometry check on observer, scroll, resize, focus, and page-show events so a visible canvas cannot remain paused because of a stale observer result. Ticker synchronization also compares the intended runtime state with PixiJS' actual `ticker.started` state before deciding whether to call `start()` or `stop()`. This prevents host pages with several simulator embeds from running every race while only one is visible, and avoids a large simulation catch-up step when the canvas re-enters view. Camera safe-area layout measurements are cached and invalidated by resize observation so the 60 FPS render path does not force repeated DOM geometry reads. Camera framing derives a cached active-track bounds box from generated samples plus pit-lane extent; overview uses that frame directly, and every camera uses it as the zoom-out floor. The first camera frame is applied immediately, then later camera mode changes interpolate target position and scale so leader/selected/overview/show-all/pit switches do not jump the world view. Runtime readouts avoid redundant DOM writes for unchanged text, timing markup, finish classification, and static selected-car overview data, and `updateDom()` reuses its supplied full snapshot for camera-control availability instead of taking another public snapshot. Static pit-lane status graphics are redrawn only when their status/geometry key changes, and DRS trail histories are pruned in place. Startup marks the initial DOM refresh time so the first ticker frame does not immediately redo the same full readout update. At `5x` and `10x` browser playback, noncritical DOM/timing refreshes are throttled more aggressively while fixed-step simulation, event emission, and rendering continue from authoritative simulation state. Long project-radio delays are also treated as stale schedule state instead of replaying every missed lower-third transition.
371
+ The app runtime pauses its PixiJS ticker when the race canvas is outside the viewport or the document is hidden, then resets the frame clock before resuming. Runtime visibility uses `IntersectionObserver` plus a bounded canvas-geometry check on observer, scroll, resize, focus, and page-show events so a visible canvas cannot remain paused because of a stale observer result. Ticker synchronization also compares the intended runtime state with PixiJS' actual `ticker.started` state before deciding whether to call `start()` or `stop()`. This prevents host pages with several simulator embeds from running every race while only one is visible, and avoids a large simulation catch-up step when the canvas re-enters view. Layout resize synchronization extends one deadline on one RAF chain rather than starting overlapping chains for each resize signal. Camera safe-area layout measurements are cached and invalidated by resize observation so the 60 FPS render path does not force repeated DOM geometry reads. Camera framing derives a cached active-track bounds box from generated samples plus pit-lane extent; overview uses that frame directly, and every camera uses it as the zoom-out floor. The first camera frame is applied immediately, then later camera mode changes interpolate target position and scale so leader/selected/overview/show-all/pit switches do not jump the world view. Runtime readouts avoid redundant DOM writes for unchanged text, timing markup, finish classification, and static selected-car overview data, and `updateDom()` reuses its supplied full snapshot for camera-control availability instead of taking another public snapshot. Static pit-lane status graphics are redrawn only when their status/geometry key changes, and DRS trail histories are pruned in place. Startup marks the initial DOM refresh time so the first ticker frame does not immediately redo the same full readout update. At `5x` and `10x` browser playback, noncritical DOM/timing refreshes are throttled more aggressively while fixed-step simulation, event emission, and rendering continue from authoritative simulation state. Long project-radio delays are also treated as stale schedule state instead of replaying every missed lower-third transition.
295
372
 
296
373
  ## Data Layer
297
374
 
@@ -299,10 +376,12 @@ The app runtime pauses its PixiJS ticker when the race canvas is outside the vie
299
376
 
300
377
  `src/data/vehicleData.js` converts vehicle rating sheets into physical setup values.
301
378
 
302
- `src/data/championship.js` pairs drivers with entries and generates timing codes, numbers, team metadata, and converted constructor data. It rejects duplicate entry driver IDs, ignores omitted driver numbers during uniqueness checks, and falls back to stable grid-order numbers when host entries do not provide numbers.
379
+ `src/data/championship.js` pairs drivers with entries and generates timing codes, numbers, team metadata, and converted constructor data. It rejects duplicate entry driver IDs, ignores omitted driver numbers during uniqueness checks, and falls back to stable grid-order numbers when host entries do not provide numbers. `src/data/teamTypes.d.ts` is the single declaration owner for public team, pit-crew, and team-theme selector types; root, data, and environment declaration surfaces re-export it so those public contracts cannot drift independently.
303
380
 
304
381
  `src/data/normalizeDrivers.js` validates host driver data, rejects duplicate driver IDs before runtime maps are created, and invokes championship pairing.
305
382
 
383
+ The default championship grid is constructed on demand through `buildChampionshipDriverGrid()`. Tests own any eagerly constructed fixture; production imports do not build an unused normalized demo grid.
384
+
306
385
  `src/data/demoDrivers.js` is demo/portfolio-flavored sample data. It should not become the only supported data path.
307
386
 
308
387
  ## UI Shell
@@ -319,7 +398,7 @@ The app runtime pauses its PixiJS ticker when the race canvas is outside the vie
319
398
  - Race telemetry drawer template.
320
399
  - Car/driver overview panel.
321
400
 
322
- The package-owned component inventory is maintained in [Component Inventory](component_inventory.md). Use it before changing templates, DOM bindings, readout renderers, layout observers, or local-preview component usage so shared component behavior stays in focused helpers instead of drifting across all-in-one, composable, and derived mixed variants.
401
+ The package-owned component inventory is maintained in [Component Inventory](component_inventory.md). Use it before changing templates, DOM bindings, readout renderers, layout observers, or website component usage so shared component behavior stays in focused helpers instead of drifting across all-in-one, composable, and derived mixed variants.
323
402
 
324
403
  `src/ui/bannerTemplates.js` owns package banner markup:
325
404
 
@@ -334,11 +413,11 @@ The package-owned component inventory is maintained in [Component Inventory](com
334
413
 
335
414
  Penalty UI is display-only. `src/app/banners/stewardMessageController.js` may show track-limit warning events and `snapshot.penalties` in the top steward message, and `src/app/readouts/timingTowerRenderer.js` may show timing-row penalty badges when the host enables those UI options, but the UI must not infer, modify, or recalculate steward decisions.
336
415
 
337
- `src/config/defaultOptions.js` owns preset resolution, telemetry module normalization, browser warmup option normalization defaults, and deriving team theme selectors from normalized entry team metadata. `src/config/themeOptions.js` owns the public theme contract: semantic token resolution, generated opposite-mode value caching, known-token filtering, safe CSS color/size token validation, named derivative theme packages, component slot maps, component/team theme selectors, and legacy alias mapping for the old small color API. Presets are merged before host options; resolved theme fields are applied as package CSS variables by the all-in-one app and composable controller. Runtime selected-team theme context is cached so theme variables are not recomputed during normal render loops. This keeps sizing/color customization explicit without turning internal layout ratios into public API.
416
+ `src/config/defaultOptions.js` composes browser option resolution and derives team theme selectors from normalized entry metadata. `simulatorDefaults.js` owns defaults/presets and `uiOptions.js` owns UI/banner/telemetry normalization. `src/config/themeOptions.js` preserves the public theme exports. Focused owners under `src/config/theme/` separate token catalog (`themeTokens`), CSS/color validation/conversion (`cssTokens`), merging (`themeInputs`), normalization/cache (`themeResolver`), component/team selectors (`themeSelectors`), DOM application (`themeCss`) and dependency-free helpers (`themeUtils`). Together they own the public theme contract: semantic token resolution, generated opposite-mode value caching, known-token filtering, safe CSS color/size token validation, named derivative theme packages, component slot maps, component/team theme selectors, and legacy alias mapping for the old small color API. Presets are merged before host options; resolved theme fields are applied as package CSS variables by the all-in-one app and composable controller. Runtime selected-team theme context is cached so theme variables are not recomputed during normal render loops. This keeps sizing/color customization explicit without turning internal layout ratios into public API.
338
417
 
339
418
  `src/config/restartOptions.js` owns shared restart-option merging for both `mountF1Simulator().restart()` and `createPaddockSimulator().restart()`. It preserves omitted driver/entry data, merges nested UI banner and asset texture groups, and resets prior UI/theme state when a restart supplies a new preset so the two browser mount surfaces cannot drift.
340
419
 
341
- Race completion is owned by `src/simulation/raceSimulation.js`. The simulation records provisional `finishOrder` / `finishRank` as each car crosses the line and uses that order for snapshots until final classification exists, so app code never has to re-sort finished cars by live distance. Race retirement state is owned by `src/simulation/race/retirements.js`; it defines DNF detection and ordering while vehicle code only marks the physical destruction event. The app layer reads `raceControl.finished`, `winner`, `classification`, and per-car `raceStatus`/DNF fields from snapshots, renders the timing/winner states, and emits lifecycle callbacks. Penalty lifecycle, pit-stop penalty service, service conversion, time adjustment, position drops, grid drops, disqualification effects, and DNF classification placement are applied inside the simulation, not in the UI. UI code may display countdowns from `pitStop.phase`, `serviceRemainingSeconds`, and `penaltyServiceRemainingSeconds`, but it must not infer, serve, or recalculate steward outcomes from lap text, timing rows, or car position.
420
+ Race completion is owned by `src/simulation/raceSimulation.js`. The simulation records provisional `finishOrder` / `finishRank` as each car crosses the line and uses that order for snapshots until final classification exists, so app code never has to re-sort finished cars by live distance. Race retirement state is owned by `src/simulation/race/retirements.js`; it defines DNF detection and ordering while vehicle code only marks the physical destruction event. The app layer reads `raceControl.finished`, `winner`, `classification`, and per-car `raceStatus`/DNF fields from snapshots, renders the timing/winner states, and forwards lifecycle delivery to `HostLifecycleEmitter`. Final timing presentation derives its finished state from `raceControl.finished`, not the post-finish safety-car circulation mode. Penalty lifecycle, pit-stop penalty service, service conversion, time adjustment, position drops, grid drops, disqualification effects, and DNF classification placement are applied inside the simulation, not in the UI. UI code may display countdowns from `pitStop.phase`, `serviceRemainingSeconds`, and `penaltyServiceRemainingSeconds`, but it must not infer, serve, or recalculate steward outcomes from lap text, timing rows, or car position.
342
421
 
343
422
  Composable hosts may choose where each package-owned component root is placed, but they still receive package-generated markup through the public mount functions. The controller marks each mounted root as an `f1-sim-component` styling scope so standalone pieces receive the same package variables as the all-in-one shell.
344
423