@inventure71/paddockjs 3.0.0 → 4.1.0
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.
- package/CHANGELOG.md +152 -1
- package/INSTALL_AND_UPDATE.md +21 -4
- package/README.md +103 -551
- package/docs/architecture.md +28 -18
- package/docs/component_inventory.md +5 -4
- package/docs/composable-layouts.md +79 -0
- package/docs/custom_model_controller.md +4 -4
- package/docs/data-helpers.md +50 -0
- package/docs/data_contract.md +13 -7
- package/docs/getting-started.md +105 -0
- package/docs/headless-environment.md +68 -0
- package/docs/index.md +17 -4
- package/docs/rules.md +6 -2
- package/docs/system_specs.md +60 -7
- package/docs/theming.md +64 -0
- package/docs/training.md +20 -14
- package/docs/troubleshooting.md +76 -0
- package/docs/upgrading-hosts-to-3.md +42 -0
- package/examples/python/README.md +6 -0
- package/examples/python/base_policy_server.py +19 -0
- package/package.json +21 -5
- package/src/api/PaddockSimulatorController.js +78 -22
- package/src/api/runtimeTheme.js +59 -0
- package/src/app/F1SimulatorApp.js +24 -3
- package/src/app/readouts/timingTowerRenderer.js +203 -42
- package/src/app/runtime/frameLoop.js +5 -1
- package/src/config/defaultOptions.js +8 -1
- package/src/config/themeOptions.js +5 -0
- package/src/data/index.d.ts +212 -0
- package/src/data/index.js +14 -0
- package/src/environment/episode.js +11 -1
- package/src/environment/metrics.js +59 -16
- package/src/environment/observationVector.js +50 -33
- package/src/environment/observations.js +89 -20
- package/src/environment/runtime.js +41 -0
- package/src/environment/sensors/carRays.js +48 -37
- package/src/environment/sensors/index.js +501 -97
- package/src/environment/sensors/indexedRayBands.js +134 -46
- package/src/environment/sensors/rayBandTrace.js +1171 -0
- package/src/environment/sensors/rayChannels.js +108 -0
- package/src/environment/sensors/rayConfig.js +71 -25
- package/src/environment/sensors/rayGeometry.js +23 -17
- package/src/environment/sensors/sensorTargets.js +67 -1
- package/src/environment/sensors/surfaceRays.js +130 -89
- package/src/environment/sensors/trackRays.js +115 -1
- package/src/index.d.ts +43 -0
- package/src/index.js +27 -2
- package/src/placeholder/index.d.ts +11 -0
- package/src/placeholder/index.js +61 -0
- package/src/placeholder.css +72 -0
- package/src/simulation/collisionGeometry.js +260 -96
- package/src/simulation/pit/pitFlow.js +21 -9
- package/src/simulation/pit/pitQueue.js +5 -3
- package/src/simulation/pit/pitRouteMovement.js +9 -4
- package/src/simulation/pit/pitRouting.js +77 -42
- package/src/simulation/pit/pitState.js +1 -0
- package/src/simulation/pit/pitTireService.js +7 -3
- package/src/simulation/race/raceFinish.js +25 -8
- package/src/simulation/race/raceOrder.js +189 -32
- package/src/simulation/race/raceProgress.js +129 -37
- package/src/simulation/race/raceSetup.js +2 -0
- package/src/simulation/race/raceStep.js +25 -13
- package/src/simulation/raceSimulation.js +20 -17
- package/src/simulation/rules/collisionSteward.js +40 -23
- package/src/simulation/rules/rulesReview.js +77 -56
- package/src/simulation/snapshots/raceSnapshots.js +37 -0
- package/src/simulation/timing/drsTiming.js +143 -30
- package/src/simulation/timing/gapEstimation.js +25 -3
- package/src/simulation/timing/lapTelemetry.js +115 -36
- package/src/simulation/timing/raceTiming.js +5 -1
- package/src/simulation/timing/sectorPerformance.js +104 -26
- package/src/simulation/timing/timingHistory.js +229 -14
- package/src/simulation/timing/timingLines.js +65 -7
- package/src/simulation/track/pitLaneLayout.js +23 -0
- package/src/simulation/track/pitLaneState.js +218 -19
- package/src/simulation/track/spatialQueries.js +67 -11
- package/src/simulation/track/trackMath.js +91 -24
- package/src/simulation/track/trackModel.js +2 -0
- package/src/simulation/track/trackQueryIndex.js +1334 -100
- package/src/simulation/track/trackQueryPitIndex.js +138 -14
- package/src/simulation/track/trackQueryScratch.js +7 -0
- package/src/simulation/track/trackQueryStats.js +25 -0
- package/src/simulation/track/trackSnapshotJson.js +139 -0
- package/src/simulation/track/trackStatePolicy.js +249 -1
- package/src/simulation/vehicle/contactResolution.js +96 -59
- package/src/simulation/vehicle/mainTrackWheelSurface.js +73 -34
- package/src/simulation/vehicle/pitWheelSurface.js +81 -28
- package/src/simulation/vehicle/runoffResponse.js +54 -29
- package/src/simulation/vehicle/vehicleGeometry.js +245 -51
- package/src/simulation/vehicle/vehiclePhysics.js +42 -27
- package/src/simulation/vehicle/vehicleSnapshots.js +23 -0
- package/src/simulation/vehicle/vehicleState.js +6 -0
- package/src/simulation/vehicle/wheelSurface.js +807 -92
- package/src/styles.css +497 -497
- package/src/ui/raceControlsTemplate.js +1 -0
- package/src/ui/telemetryDrawerTemplate.js +1 -0
- package/src/ui/telemetryTemplates.js +1 -0
package/docs/architecture.md
CHANGED
|
@@ -108,10 +108,12 @@ Responsibilities:
|
|
|
108
108
|
- `options.js` validates `controlledDrivers`, scenario participants, reset placement config, frame skip, action policy, sensor config, and episode limits.
|
|
109
109
|
- `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
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. 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. 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
|
|
112
|
-
- `
|
|
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.
|
|
112
|
+
- `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.
|
|
113
114
|
- `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.
|
|
114
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.
|
|
115
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.
|
|
116
118
|
- `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.
|
|
117
119
|
|
|
@@ -119,7 +121,7 @@ The environment subpath must not import `src/index.js`, package CSS, PixiJS, `F1
|
|
|
119
121
|
|
|
120
122
|
`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.
|
|
121
123
|
|
|
122
|
-
`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, 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. 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. 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,
|
|
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.
|
|
123
125
|
|
|
124
126
|
`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.
|
|
125
127
|
|
|
@@ -164,7 +166,7 @@ Responsibilities:
|
|
|
164
166
|
|
|
165
167
|
`src/app/readouts/` owns generated-DOM readout rendering:
|
|
166
168
|
|
|
167
|
-
- `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.
|
|
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.
|
|
168
170
|
- `telemetryRenderer.js` owns selected-car telemetry and lap/sector readouts.
|
|
169
171
|
- `carOverviewRenderer.js` owns driver/vehicle overview field selection and rendering.
|
|
170
172
|
- `raceStatusRenderer.js` owns race mode/lap/DRS/contact/start-light/finish readouts.
|
|
@@ -184,6 +186,8 @@ Expert sensor visualization belongs to the browser app layer, not the headless e
|
|
|
184
186
|
|
|
185
187
|
Track material rendering must use the same surface-band thresholds as simulation. `src/rendering/track/trackMaterialRenderer.js` exposes `getTrackMaterialBands(track)` as the rendering-side band contract derived from `track.width`, `track.kerbWidth`, `track.gravelWidth`, `track.runoffWidth`, and `track.barrierWidth`; gravel, runoff, visible barrier walls, ray surface hits, wheel surfaces, and barrier destruction must not invent independent offsets or hardcoded widths. Barrier contact uses the visible vehicle footprint against the rendered wall's inner face so visual contact, destruction, and the environment's terminal state agree.
|
|
186
188
|
|
|
189
|
+
`src/simulation/vehicle/vehiclePhysics.js` owns the simulator/arcade integration paths and keeps the per-step physics math allocation-light. The advanced simulator path now resolves forward/right heading axes as direct scalars inside the hot loop instead of allocating a temporary `vehicleAxes()` object tree on every integration step, so velocity projection and acceleration reconstruction stay in the same scalar path as the rest of the physics integration.
|
|
190
|
+
|
|
187
191
|
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.
|
|
188
192
|
|
|
189
193
|
`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.
|
|
@@ -205,6 +209,8 @@ Responsibilities:
|
|
|
205
209
|
- Event buffer lifecycle.
|
|
206
210
|
- Delegation to feature-owned simulation modules.
|
|
207
211
|
|
|
212
|
+
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
|
+
|
|
208
214
|
`src/simulation/race/raceControlState.js` owns initial race-control and safety-car state construction.
|
|
209
215
|
|
|
210
216
|
`src/simulation/race/raceLifecycle.js` owns start-light updates, grid hold/release, and pit-lane open/closed transitions.
|
|
@@ -215,15 +221,15 @@ Responsibilities:
|
|
|
215
221
|
|
|
216
222
|
`src/simulation/race/raceDistance.js` owns pure race-distance utilities including total-lap normalization, progress wrapping, lap calculation, and finish-distance calculation.
|
|
217
223
|
|
|
218
|
-
`src/simulation/race/raceOrder.js` owns ordered-car context, driver race context, aggression calculation, and DRS reference lookup.
|
|
224
|
+
`src/simulation/race/raceOrder.js` owns ordered-car context, driver race context, aggression calculation, and DRS reference lookup. The normal running path now partitions participating cars into live and DNF sets once before sorting, keeps the common running path free of per-call `id -> car` maps unless a finished/classification/frozen-order branch actually needs that lookup, and sorts the live field in place before appending DNF cars instead of cloning and reassembling extra arrays on the hot running branch. Broad race-state commits should derive nearest-ahead DRS references from one physical-track-order pass over the current active field instead of re-scanning the whole field per car. When the active running field still fits inside one lap window, the DRS helper may safely reuse the already ordered race field as that physical order and skip the extra wrapped-progress sort altogether; once lapped traffic appears, it falls back to the explicit physical-order sort so DRS can still reference physically ahead backmarkers correctly.
|
|
219
225
|
|
|
220
226
|
`src/simulation/participants/participantInteractions.js` owns the resolved per-car interaction contract. It normalizes profiles and overrides, attaches `car.interaction` during race setup, and provides helpers for collision, sensor visibility, pit-lane blocking, and race-order membership. It does not own vehicle motion or policy behavior.
|
|
221
227
|
|
|
222
228
|
`src/simulation/replay/replayGhosts.js` owns trajectory-driven replay/reference entities. It normalizes ghost input, advances ghost state from race time, interpolates trajectory samples, and serializes `snapshot.replayGhosts`. Replay ghosts are not physics participants and must remain outside car collisions, pit systems, timing/classification, stewarding, and car hit areas.
|
|
223
229
|
|
|
224
|
-
`src/simulation/race/raceProgress.js` owns per-step race-state
|
|
230
|
+
`src/simulation/race/raceProgress.js` owns local surface refresh and broad race-state commit helpers. Local refresh updates current wheel/track state needed by post-motion rule phases without changing timing/order. When runoff has already computed a center track state for the same final pose, local refresh may reuse it; if collision correction changed the pose, it discards that pending state and queries from the final position. Connector-adjacent wheel refresh now keeps those reused center states on an exact local segment-neighborhood path for wheel-center classification, instead of falling back to broad nearest-track scans at each wheel. Normal active steps now treat that local refresh as the authoritative post-motion surface pass, so the following broad commit reuses the already-committed wheel/track state instead of recomputing the same surface classification a second time for every car. The broad commit also derives DRS references from a one-pass physical track ordering once per commit instead of calling back into per-car whole-field DRS scans, keeps those references in a reusable per-car indexed scratch array rather than rebuilding a transient `Map` just to feed the ordered commit loop, and now skips that extra physical-order sort entirely when the ordered live field is provably still inside one lap window. DRS latch updates also cache the currently active zone reference on the car, cache the next detection-zone index across steady forward motion, mutate zone detection records in place, and stay on a sim-bound scalar call path during broad commits so the hot loop avoids rebuilding the same options object for every car on every step. External car-state changes clear that next-zone cache so repositioned cars still detect later zones correctly. It passes one precomputed live field depth into aggression updates instead of re-filtering the full field once per car, skips the excluded-participant reset scan entirely when the ordered list already covers every car in the field, and threads that same ordered field into finish evaluation instead of recomputing race order again at the end of the commit. On the common non-finish path, that finish evaluation now stays on one direct ordered-field pass instead of allocating a temporary `newlyFinished` array and then rescanning the field again just to prove the race is still running. Timing sample recording and timing-line crossing updates are folded into that same per-car commit loop, so the normal step no longer pays a second whole-field timing pass immediately after recomputing progress and lap telemetry. Lap telemetry and sector-performance helpers now keep their fixed three-sector buffers hot in place during normal broad commits, and lap-telemetry position refresh rewrites the existing `lapTelemetry` fields in place instead of allocating a throwaway lap/sector position object on every per-car update. In the dominant no-boundary case, lap telemetry now stays on an even narrower in-progress sync path that updates only the live scalars and the active sector slots instead of re-synchronizing the whole three-sector position structure as if a split had happened. Timing-gap estimation still limits timing-line scans to the overlapping stored window shared by the compared cars instead of walking the whole retained timing-line range on every gap estimate, and it now returns immediately when either compared car has not stored any usable timing-line window yet. Inside `src/simulation/vehicle/wheelSurface.js`, the hot `applyWheelSurfaceState()` path now lets `calculateWheelSurfaceState()` write the resolved representative state straight into the stable `car.trackState` container, instead of building a temporary representative object and immediately copying it back into that same hot-loop target. The broad commit owns progress, lap telemetry, sector timing, timing-line crossings, gaps, rank fields, attack state, DRS latch coordination, and finish evaluation.
|
|
225
231
|
|
|
226
|
-
`src/simulation/race/raceFinish.js` owns finish detection and race-finish transitions while classification ranking remains in `classification.js`.
|
|
232
|
+
`src/simulation/race/raceFinish.js` owns finish detection and race-finish transitions while classification ranking remains in `classification.js`. The common no-finish path should stay allocation-light: it may mark newly finished cars during one ordered-field pass, but it should not build throwaway arrays or do a second full-field confirmation scan before returning to the running race.
|
|
227
233
|
|
|
228
234
|
`src/simulation/race/classification.js` owns final classification ranking and classification consequences.
|
|
229
235
|
|
|
@@ -237,7 +243,7 @@ Responsibilities:
|
|
|
237
243
|
|
|
238
244
|
`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.
|
|
239
245
|
|
|
240
|
-
`src/simulation/pit/pitRouting.js` owns pit-route construction, pit-lane route sampling, limiter-segment lookup, and render-pose shifts used by route/contact movement.
|
|
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.
|
|
241
247
|
|
|
242
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.
|
|
243
249
|
|
|
@@ -249,25 +255,29 @@ Responsibilities:
|
|
|
249
255
|
|
|
250
256
|
`src/simulation/rules/penaltyStats.js` owns penalty-stat aggregation and lookup helpers.
|
|
251
257
|
|
|
252
|
-
`src/simulation/vehicle/contactResolution.js` owns physical collision resolution and contact velocity response. Stewarding remains separate from physical response. In advanced mode, every collision branch that changes speed also keeps `velocityX/Y` synchronized, including contacts where one car is fixed by pit entry, queue, service, or exit routing.
|
|
258
|
+
`src/simulation/vehicle/contactResolution.js` owns physical collision resolution and contact velocity response. Stewarding remains separate from physical response. In advanced mode, every collision branch that changes speed also keeps `velocityX/Y` synchronized, including contacts where one car is fixed by pit entry, queue, service, or exit routing. The hot response path stays on scalar velocity components rather than rebuilding temporary vector objects for impulse and damping math. The resolver keeps phase-level collidable-car, reported-contact, and steward-context containers in `sim.collisionScratch`, so collision phases reuse their orchestration scratch before entering candidate generation, narrowphase checks, and fresh-contact penalty review. `src/simulation/rules/rulesReview.js` writes collision steward facts into that scratch-owned context when provided and emits resulting collision penalties directly into the simulation ledger, rather than building an intermediate penalty array for the internal synchronous review path. Direct/no-scratch review calls still receive an isolated context for the synchronous penalty calculation.
|
|
253
259
|
|
|
254
|
-
`src/simulation/vehicle/runoffResponse.js` owns non-penalty off-track vehicle correction.
|
|
260
|
+
`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.
|
|
255
261
|
|
|
256
262
|
`src/simulation/snapshots/raceSnapshots.js` owns public, render, and observation snapshot assembly.
|
|
257
263
|
|
|
258
|
-
Weather effects, reliability failures, and fuel-load performance effects are not active
|
|
264
|
+
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.
|
|
259
265
|
|
|
260
|
-
`snapshot()` is the full public data contract and must remain stable for host callbacks, environment state, and external callers. Internal browser/rendering paths use lean read models such as `snapshotRender()` so the 60 FPS loop does not serialize setup, wheels, penalties, and other non-render fields every frame. Environment and sensor code may use similarly narrow internal views, cache shared per-observation ray origin state, and broadphase car-ray checks before exact footprint intersection, but those must not change the public `RaceSnapshot` shape.
|
|
266
|
+
`snapshot()` is the full public data contract and must remain stable for host callbacks, environment state, and external callers. Returning a full snapshot object is separate from repeatedly JSON-serializing it: full JSON serialization is reserved for explicit host/export/debug paths, worker/full-state requests, tests, and benchmarks. Internal browser/rendering paths use lean read models such as `snapshotRender()` so the 60 FPS loop does not serialize setup, wheels, penalties, and other non-render fields every frame. Environment and sensor code may use similarly narrow internal views, cache shared per-observation ray origin state, reuse scratch-backed rich ray channel containers, and broadphase car-ray checks before exact scalar footprint intersection using the caller-owned ray vector, but those must not change the public `RaceSnapshot` shape or the fresh-object behavior of no-scratch public ray calls.
|
|
261
267
|
|
|
262
268
|
`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.
|
|
263
269
|
|
|
264
|
-
`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. Root `src/simulation/vehicleGeometry.js`, `vehiclePhysics.js`, and `wheelSurface.js` are compatibility re-exports.
|
|
270
|
+
`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
|
+
|
|
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.
|
|
273
|
+
|
|
274
|
+
`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.
|
|
265
275
|
|
|
266
|
-
`src/simulation/
|
|
276
|
+
`src/simulation/vehicle/wheelSurface.js` owns per-wheel track sampling and the effective car surface. Surface priority, analytic main-track classification, and pit-lane/connector classification are split into `surfacePriority.js`, `mainTrackWheelSurface.js`, and `pitWheelSurface.js`. On normal main-track running it uses the cached wheel geometry and the car center's track normal to analytically classify each wheel patch from signed offsets. That normal-running apply path now shares the projected wheel half-width across the four same-orientation patches and folds analytic wheel writes plus surface/track-limit summarization into one pass, so the hot local-refresh path stops recomputing the same projection term and rewalking the same four wheels just to summarize them. The dominant common case now short-circuits directly to that analytic branch whenever the resolved center state is on the main track and not near a pit connector, instead of first walking the pit/connector decision tree before landing on the same result. Near pit connectors it still resolves ambiguous wheel centers through exact local segment-neighborhood projections anchored on the already-known center track segment, but those wheel-center projections now use a tighter exact radius for the real connector cases, write into reusable projection containers, and can be queried as one shared batch instead of rebuilding the same neighborhood search separately for each wheel. Straight connector-adjacent main-track poses first prove the full wheel footprint against the center track frame and skip those wheel-center projections entirely when every patch remains inside the main-track limits, so centered pit-straight connector refreshes use the same analytic writes as normal main-track running. When an exact batch is needed and proves every wheel center still belongs to the main track, the post-query apply path now stays on a one-pass analytic summary instead of routing each wheel back through the slower mixed connector branch plus a second summary pass. Connector-adjacent main-track cars whose full wheel footprint stays on the opposite half of the track from the pit-lane side skip those connector track queries entirely and stay on the plain analytic main-track path, because pit overlap is geometrically impossible in that region. Indexed pit geometry or full patch samples still run only when that local geometric pass says the wheel is genuinely ambiguous against pit-road bounds. Center-in-pit-lane states still use pit analytics when the whole patch fits inside the road and fall back to full sampling otherwise. The internal `applyWheelSurfaceState()` path keeps per-car scratch storage for wheel arrays, wheel objects, sampled-state arrays, reusable connector wheel-center states, reusable connector wheel-center projections, reusable connector wheel-center point refs, a reusable wheel-summary accumulator, a stable mutable `car.trackState`, and a stable cache wrapper so the post-physics refresh path avoids replacing hot-loop containers when only the field values changed; it now also passes the reusable queried-state target array straight through the exact connector batch query instead of allocating a fresh state array and copying the results back out. On cache misses it threads the already-resolved current geometry straight through `calculateWheelSurfaceState()` instead of rereading the same current-geometry cache a second time. Its cache-hit validation now compares geometry pose and explicit center-state fields directly instead of rebuilding per-call cache signature strings, so repeated explicit-center refreshes stay on the cheap cache-hit path without avoidable string churn. It also reads current-pose vehicle geometry from a current-only cache so pre-physics wheel-surface checks do not rebuild wheel geometry just because swept-collision history (`previousX/Y/Heading`) was refreshed, and that current-only geometry cache now updates its body/wheel containers in place when the current pose changes while leaving wheel/body corners lazy until a sampled path requests them, so post-physics local refresh stops rebuilding unused current-geometry corner trees every step without weakening exact sampled wheel checks. The final wheel summary now stays in the classifier pass for scratch-backed main-track, connector, pit-analytic, and full-sample branches, and its reusable `trackLimits` object is the car's stable `trackLimitState`; runtime benchmarks require the summary container, track-limit object, wheel containers, sampled-state objects, and representative track-state object to stay reused. Direct calculation calls still return fresh public containers, but they now use the same scalar wheel writer batches and direct full-patch sampler functions instead of rebuilding callback-driven patch-sample arrays; the wheel-surface benchmark requires scratchless scalar writer batches and one scalar wheel write per wheel on the direct connector path. `vehiclePhysics.js` consumes that effective surface through the car track state, and separately compares left-side and right-side wheel resistance to add a small capped yaw bias toward the slower side.
|
|
267
277
|
|
|
268
|
-
`
|
|
278
|
+
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.
|
|
269
279
|
|
|
270
|
-
`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. Driver AI and environment policies still provide only steering/throttle/brake/pit intent.
|
|
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.
|
|
271
281
|
|
|
272
282
|
`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.
|
|
273
283
|
|
|
@@ -277,7 +287,7 @@ Browser mounts generate a fresh procedural seed when `trackSeed` is omitted; exp
|
|
|
277
287
|
|
|
278
288
|
`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`.
|
|
279
289
|
|
|
280
|
-
`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.
|
|
290
|
+
`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.
|
|
281
291
|
|
|
282
292
|
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.
|
|
283
293
|
|
|
@@ -330,7 +340,7 @@ Race completion is owned by `src/simulation/raceSimulation.js`. The simulation r
|
|
|
330
340
|
|
|
331
341
|
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.
|
|
332
342
|
|
|
333
|
-
`src/styles.css` styles the generated shell,
|
|
343
|
+
`src/styles.css` styles the generated shell, uses package-owned system font stacks, caps timing-tower width for readability, and owns the timing-list scroll behavior. Timing rows are compact content-sized grid rows stacked from the top, with `ui.timingEntryVerticalPadding` setting one shared pixel padding value above and below every entry, so rank positions do not stretch or redistribute when the number of entries changes. The timing tower owns its runtime interval-vs-leader-gap switch, while `F1SimulatorApp` reads both seconds and whole-lap gap values from the race snapshot and formats positive lap deficits as `+N` before seconds. The compact `Int`/`Gap` header toggle is a timing-tower style contract, not host CSS: the grid reserves a stable gap column, the header labels and toggle share one vertical center, the visible pill stays inside the header, and the clickable target remains `44px` without changing tower width. Fixed hidden timing-line creation, crossing timestamps, and seconds-gap calculation stay inside `src/simulation/timing/`. Sector-map surfaces read `lapTelemetry.sectorProgress` for bar fill and `lapTelemetry.liveSectors` for live active-sector time; the simulation owns those values so UI components do not infer active split state from DOM state. Future sectors are cleared by the simulation and ignored defensively by the renderer, so banner/sidebar sector nodes cannot keep stale timing text or yellow/purple/green performance classes after the active sector changes. Component templates include lightweight package-owned loading overlays that exist after package JavaScript mounts the shell but before PixiJS/runtime startup completes; `F1SimulatorApp` removes them only after the initial frame has rendered. `src/placeholder/index.js` and `src/placeholder.css` own the separate pre-root-import placeholder contract and must remain independent of `src/index.js`, PixiJS, simulator assets, `src/styles.css`, and browser app modules.
|
|
334
344
|
|
|
335
345
|
The host page should only provide:
|
|
336
346
|
|
|
@@ -18,7 +18,7 @@ This inventory maps package-owned UI surfaces to their shared bases, owner modul
|
|
|
18
18
|
| --- | --- | --- | --- | --- | --- |
|
|
19
19
|
| All-in-one shell | `mountF1Simulator()` | `src/ui/shellTemplate.js` | Composes race controls, optional external camera controls, timing tower, race canvas, telemetry stack, shell unsupported overlay | `F1SimulatorApp` | Owns `data-f1-simulator-shell`, workspace/grid classes, layout preset classes, and the package root styling scope. Not individually mounted by composable hosts. |
|
|
20
20
|
| Race controls | `mountRaceControls()` and all-in-one shell | `src/ui/raceControlsTemplate.js` | Component surface frame helper, `createSafetyCarControlMarkup({ compact: true })`, loading overlay, unsupported overlay | `controlStateRenderer.js` plus `F1SimulatorApp.bindControls()` | Includes title, kicker, safe backlink, safety-car button, and restart. The compact safety button has no standalone component wrapper. |
|
|
21
|
-
| Safety-car control | `mountSafetyCarControl()` and drawer/race-controls compact embed | `src/ui/raceControlsTemplate.js` | Component surface frame helper for standalone, same `data-safety-car` button and control-state renderer | `controlStateRenderer.js` plus `F1SimulatorApp.bindControls()` | Standalone variant gets `data-paddock-component="safety-car-control"
|
|
21
|
+
| Safety-car control | `mountSafetyCarControl()` and drawer/race-controls compact embed | `src/ui/raceControlsTemplate.js` | Component surface frame helper for standalone, loading overlay, unsupported overlay, same `data-safety-car` button and control-state renderer | `controlStateRenderer.js` plus `F1SimulatorApp.bindControls()` | Standalone variant gets `data-paddock-component="safety-car-control"`. Compact variants are plain subcontrols inside race controls or drawer toolbar. |
|
|
22
22
|
| Camera controls | `mountCameraControls()`, all-in-one external controls, embedded race-canvas controls, drawer toolbar | `src/ui/cameraControlsTemplate.js` | Component surface frame helper, shared `data-camera-mode`, zoom, banner mute, optional speed button, loading overlay, unsupported overlay | `controlStateRenderer.js`, `CameraController`, `F1SimulatorApp.bindControls()` | External variant adds `camera-controls--external`; embedded variant omits that class. Driver camera and simulation-speed buttons are option gated. |
|
|
23
23
|
| Timing tower | `mountTimingTower()`, all-in-one shell, embedded race canvas, drawer race canvas | `src/ui/timingTowerTemplate.js` | Component surface frame helper, race-control status banner, loading overlay, unsupported overlay, shared timing gap controls | `timingTowerRenderer.js`, `raceStatusRenderer.js`, `controlStateRenderer.js`, `F1SimulatorApp.observeLayoutResize()` | Embedded variants receive generated instance ids for `aria-controls`. `domBindings.js` exposes plural timing lists/header/status nodes and focused renderers fan out rows, lap counters, status banners, gap controls, and resize observation across every mounted tower owned by the same simulator. Header gap toggle may be hidden while controller APIs remain active. Width/proportions are package-owned and must not be tuned by host CSS. |
|
|
24
24
|
| Race-control status banner | Timing tower internal component | `src/ui/raceControlStatusBanner.js` | Timing tower status slot | `raceStatusRenderer.js` | Internal only. It has no public mount because it is a timing-tower status affordance, not a standalone component. |
|
|
@@ -27,14 +27,15 @@ This inventory maps package-owned UI surfaces to their shared bases, owner modul
|
|
|
27
27
|
| Steward message | Race canvas internal component | `src/ui/bannerTemplates.js` | Shared banner markup conventions | `stewardMessageController.js` | Internal top race-control message for warnings/penalties. No public mount because it depends on race-canvas layering. |
|
|
28
28
|
| Race-data panel | `mountRaceDataPanel()`, race canvas lower-third, drawer lower-third | `src/ui/bannerTemplates.js` | Component surface frame helper, selected driver readouts, optional race-data telemetry detail, loading overlay, unsupported overlay | `domBindings.js` grouped panel bindings, `raceDataBannerController.js`, `telemetryRenderer.js` for sector detail | Standalone variant adds `race-data-panel--standalone`. Embedded variants are clipped/layered by race canvas. Project and radio modes share the same panel; runtime swaps content/classes across every mounted panel owned by the simulator. |
|
|
29
29
|
| Telemetry sector banner | `mountTelemetrySectorBanner()` and optional race-canvas embed | `src/ui/bannerTemplates.js` | Component surface frame helper, sector-bar markup, loading overlay, unsupported overlay | `telemetryRenderer.js` | Independent broadcast lower-third style sector surface. It is separate from race-data telemetry detail and should only render when explicitly mounted or embedded. |
|
|
30
|
-
| Telemetry stack | `mountTelemetryPanel()` and drawer content | `src/ui/telemetryTemplates.js` | Component surface frame helper, telemetry core, sector progress, lap times, sector times, optional car/driver overview, unsupported overlay | `telemetryRenderer.js`, `carOverviewRenderer.js` when overview is included | The stack owns scrolling when constrained. It may include overview by default, but drawer content intentionally disables overview. |
|
|
30
|
+
| Telemetry stack | `mountTelemetryPanel()` and drawer content | `src/ui/telemetryTemplates.js` | Component surface frame helper, loading overlay, telemetry core, sector progress, lap times, sector times, optional car/driver overview, unsupported overlay | `telemetryRenderer.js`, `carOverviewRenderer.js` when overview is included | The stack owns scrolling when constrained. It may include overview by default, but drawer content intentionally disables overview. |
|
|
31
31
|
| Telemetry core | `mountTelemetryCore()` and telemetry stack | `src/ui/telemetryTemplates.js` | Telemetry module class helper, loading overlay, unsupported overlay | `telemetryRenderer.js` | Scalar selected-car telemetry only. It shares selected-code/name readouts with sector banner. |
|
|
32
32
|
| Telemetry sectors | `mountTelemetrySectors()` and telemetry stack | `src/ui/telemetryTemplates.js` | Telemetry module class helper, sector-bar markup, loading overlay, unsupported overlay | `telemetryRenderer.js` | Sector progress bars only. It shares sector bar selectors with race-data detail and sector banner. |
|
|
33
33
|
| Telemetry lap times | `mountTelemetryLapTimes()` and telemetry stack | `src/ui/telemetryTemplates.js` | Telemetry module class helper, loading overlay, unsupported overlay | `telemetryRenderer.js` | Current, last, and best lap table only. |
|
|
34
34
|
| Telemetry sector times | `mountTelemetrySectorTimes()` and telemetry stack | `src/ui/telemetryTemplates.js` | Telemetry module class helper, loading overlay, unsupported overlay | `telemetryRenderer.js` | Last/best sector table only. |
|
|
35
35
|
| Car/driver overview | `mountCarDriverOverview()` and optional telemetry stack embed | `src/ui/carOverviewTemplate.js` | Component surface frame helper, overview field slots, loading overlay, unsupported overlay | `domBindings.js` cached field bindings, `carOverviewRenderer.js` | Standalone and stack-embedded variants share identical markup. Runtime toggles vehicle vs driver fields without remounting and updates cached label/value bindings instead of querying each field during render. |
|
|
36
|
-
| Race telemetry drawer | `mountRaceTelemetryDrawer()` and complete preview template | `src/ui/telemetryDrawerTemplate.js` | Component surface frame helper, camera controls, compact safety-car control, race canvas with timing/data panel, telemetry stack, unsupported overlay | `F1SimulatorApp.setTelemetryDrawerOpen()`, `controlStateRenderer.js`, normal race/telemetry renderers | Higher-level mixed template. It intentionally owns toolbar placement, drawer ids, open/closed inert state, and responsive narrow drawer behavior. |
|
|
37
|
-
| Loading overlay | All startup-capable
|
|
36
|
+
| Race telemetry drawer | `mountRaceTelemetryDrawer()` and complete preview template | `src/ui/telemetryDrawerTemplate.js` | Component surface frame helper, loading overlay, camera controls, compact safety-car control, race canvas with timing/data panel, telemetry stack, unsupported overlay | `F1SimulatorApp.setTelemetryDrawerOpen()`, `controlStateRenderer.js`, normal race/telemetry renderers | Higher-level mixed template. It intentionally owns toolbar placement, drawer ids, open/closed inert state, and responsive narrow drawer behavior. |
|
|
37
|
+
| Loading overlay | All public startup-capable mounted surfaces, including all-in-one shell children and composable roots | `src/ui/templateUtils.js` | `createLoadingMarkup(label)` | `F1SimulatorApp.completeComponentLoading()` | Removed after startup after the initial frame. Component labels remain surface-specific for accessibility. Pre-JS placeholders are host-owned, not package-owned. |
|
|
38
|
+
| Pre-JS startup placeholder | `@inventure71/paddockjs/placeholder` and `@inventure71/paddockjs/placeholder.css` | `src/placeholder/index.js`, `src/placeholder.css` | Escaped inert HTML helper, optional tiny CSS start-light animation | Host page before root simulator import | Covers the network/import gap before the root package can render. It must stay independent of PixiJS, simulator assets, root runtime CSS, and app modules. |
|
|
38
39
|
| Unsupported-size overlay | Public mount roots, shell, drawer, race/data/telemetry surfaces | `src/ui/templateUtils.js` | `createUnsupportedSizeMarkup(label)` | `installLayoutSupport()` | Display is driven by measured support state. Minimum size thresholds remain centralized in `layoutSupport.js`. |
|
|
39
40
|
|
|
40
41
|
## Local Preview Variants
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Composable Layouts
|
|
2
|
+
|
|
3
|
+
Use composable layouts when a host page wants to place package-owned simulator pieces in separate DOM roots. The host owns placement and surrounding page layout; PaddockJS still owns generated markup, CSS variables, race state, timing state, and component behavior.
|
|
4
|
+
|
|
5
|
+
## Create One Controller
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import { createPaddockSimulator } from '@inventure71/paddockjs';
|
|
9
|
+
|
|
10
|
+
const simulator = createPaddockSimulator({
|
|
11
|
+
drivers,
|
|
12
|
+
entries,
|
|
13
|
+
onDriverOpen(driver) {
|
|
14
|
+
if (driver.link) window.location.href = driver.link;
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Keep one controller per race runtime. Mount the surfaces you need, then start it:
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
simulator.mountRaceControls(document.getElementById('sim-controls'));
|
|
23
|
+
simulator.mountCameraControls(document.getElementById('sim-camera-controls'));
|
|
24
|
+
simulator.mountSafetyCarControl(document.getElementById('sim-safety-car'));
|
|
25
|
+
simulator.mountRaceCanvas(document.getElementById('sim-race'), {
|
|
26
|
+
includeTimingTower: true,
|
|
27
|
+
includeRaceDataPanel: true,
|
|
28
|
+
timingTowerVerticalFit: 'scroll',
|
|
29
|
+
});
|
|
30
|
+
simulator.mountRaceDataPanel(document.getElementById('sim-race-data'));
|
|
31
|
+
|
|
32
|
+
await simulator.start();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Controller methods are the canonical composable style. The standalone mount helper exports exist for compatibility, but new host code should prefer `simulator.mountRaceCanvas(root)` and the matching controller methods.
|
|
36
|
+
|
|
37
|
+
Each `mount*()` call writes lightweight static package markup into that root immediately and includes a package-owned loading overlay for the mounted surface. `await simulator.start()` then initializes the shared PixiJS/simulation runtime and removes those overlays after assets, controls, initial DOM readouts, and the first frame are ready. Mount all required surfaces before `start()` so the runtime can bind one coherent set of package-owned controls and readouts.
|
|
38
|
+
|
|
39
|
+
## Available Surfaces
|
|
40
|
+
|
|
41
|
+
- `mountRaceControls(root)`
|
|
42
|
+
- `mountCameraControls(root)`
|
|
43
|
+
- `mountSafetyCarControl(root)`
|
|
44
|
+
- `mountTimingTower(root)`
|
|
45
|
+
- `mountRaceCanvas(root, options)`
|
|
46
|
+
- `mountTelemetryPanel(root)`
|
|
47
|
+
- `mountTelemetryCore(root)`
|
|
48
|
+
- `mountTelemetrySectors(root)`
|
|
49
|
+
- `mountTelemetrySectorBanner(root)`
|
|
50
|
+
- `mountTelemetryLapTimes(root)`
|
|
51
|
+
- `mountTelemetrySectorTimes(root)`
|
|
52
|
+
- `mountRaceTelemetryDrawer(root, options)`
|
|
53
|
+
- `mountCarDriverOverview(root)`
|
|
54
|
+
- `mountRaceDataPanel(root)`
|
|
55
|
+
|
|
56
|
+
`mountRaceCanvas()` can embed the timing tower and race-data lower-third when the host wants a single package-owned race window. `mountRaceTelemetryDrawer()` mounts a larger workbench with top controls, race canvas, timing tower, safety-car control, and telemetry drawer.
|
|
57
|
+
|
|
58
|
+
## Lifecycle
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
await simulator.start();
|
|
62
|
+
|
|
63
|
+
simulator.selectDriver('budget');
|
|
64
|
+
simulator.setTimingGapMode('leader');
|
|
65
|
+
simulator.setPitLaneOpen(false);
|
|
66
|
+
|
|
67
|
+
simulator.restart({ trackSeed: 5051 });
|
|
68
|
+
simulator.destroy();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Use `restart(nextOptions)` for race/data/seed changes. Use `setTheme()`, `setThemeMode()`, or `syncThemeFrom()` for presentation changes so the race does not reset.
|
|
72
|
+
|
|
73
|
+
Browser expert mode changes are not a casual restart setting. Expert mode changes ticker ownership and freezes the visual simulator until host code steps it, so only enable it intentionally for policy playback or debugging.
|
|
74
|
+
|
|
75
|
+
## Sizing
|
|
76
|
+
|
|
77
|
+
Each mounted root receives PaddockJS component scope classes and package CSS variables. Hosts may size the outer roots, but timing-board width, broadcast proportions, camera safe area, drawer behavior, lower-thirds, focus styles, and touch targets remain package-owned.
|
|
78
|
+
|
|
79
|
+
Use `includeTimingTower: true` when the race canvas should own the timing tower placement. Use `timingTowerVerticalFit: 'scroll'` to keep a fixed race height with a scrolling timing list, or `'expand-race-view'` when the race window may grow to fit the tower.
|
|
@@ -123,6 +123,8 @@ This gives each observation a `Float32Array` vector and avoids per-frame object/
|
|
|
123
123
|
|
|
124
124
|
For visual debugging, use `output: 'full'` or `output: 'object'` so you can display human-readable senses. Policy Runner uses the active observation values; it should not recompute sharper or different senses than the model receives.
|
|
125
125
|
|
|
126
|
+
For HTTP policy-server integrations, keep the transport compact. Policy Runner sends `protocolVersion: 2`; reset/init receives `actionSpec`, `observationSpec`, configuration, `previousActionFields`, and `metricFields`, while each `/policy/decide-batch` request sends `driverIds` plus compact `vectors`, `previousActions`, `metrics`, and `events` arrays aligned by driver index. Do not expect full observation objects, schemas, full snapshots, or track metadata in the per-decision payload. In this compact mode the Policy Runner senses panel shows the active vector shape instead of object/ray diagnostics.
|
|
127
|
+
|
|
126
128
|
## Headless Runtime
|
|
127
129
|
|
|
128
130
|
Use the browser-free environment when training or evaluating outside a mounted page:
|
|
@@ -169,10 +171,8 @@ while (!result.done) {
|
|
|
169
171
|
Use browser expert mode when you want to watch the model drive:
|
|
170
172
|
|
|
171
173
|
```js
|
|
172
|
-
import {
|
|
173
|
-
|
|
174
|
-
mountF1Simulator,
|
|
175
|
-
} from '@inventure71/paddockjs';
|
|
174
|
+
import { createPaddockDriverControllerLoop } from '@inventure71/paddockjs/environment';
|
|
175
|
+
import { mountF1Simulator } from '@inventure71/paddockjs';
|
|
176
176
|
|
|
177
177
|
const simulator = await mountF1Simulator(root, {
|
|
178
178
|
drivers,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Data Helpers
|
|
2
|
+
|
|
3
|
+
Use the data subpath for CSS-free host tooling, server-side data preparation, public TypeScript types, and procedural track helpers.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
import {
|
|
7
|
+
DriverData,
|
|
8
|
+
createProceduralTrack,
|
|
9
|
+
formatDriverNumber,
|
|
10
|
+
normalizeSimulatorDrivers,
|
|
11
|
+
} from '@inventure71/paddockjs/data';
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The data subpath does not import DOM, PixiJS, package CSS, or browser app code. It is the correct path for raw Node checks and build-time scripts.
|
|
15
|
+
|
|
16
|
+
## Driver And Entry Normalization
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
const drivers = [
|
|
20
|
+
{ id: 'budget', name: 'Budget Buddy', color: '#ff2d55' },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const entries = [
|
|
24
|
+
{
|
|
25
|
+
driverId: 'budget',
|
|
26
|
+
driverNumber: 71,
|
|
27
|
+
timingName: 'Budget',
|
|
28
|
+
driver: { pace: 52, racecraft: 74, aggression: 38, riskTolerance: 47, patience: 81, consistency: 86 },
|
|
29
|
+
vehicle: { id: 'bb-01', name: 'BB-01', power: 48, braking: 72, aero: 55, dragEfficiency: 66, mechanicalGrip: 63, weightControl: 58, tireCare: 82 },
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const normalized = normalizeSimulatorDrivers(drivers, { entries });
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Host websites pass the original `drivers` and `entries` to browser mounts. Tooling can use `normalizeSimulatorDrivers()` when it needs the resolved driver/car/team data before runtime.
|
|
37
|
+
|
|
38
|
+
## Procedural Track Helper
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
const track = createProceduralTrack(4101, {
|
|
42
|
+
profile: 'training-short',
|
|
43
|
+
length: { minMeters: 900, maxMeters: 1800 },
|
|
44
|
+
pitLane: { enabled: false },
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Treat generated track definitions as read-only package data. If you need to experiment with mutable definitions, copy the data first.
|
|
49
|
+
|
|
50
|
+
See [Data Contract](data_contract.md) for full driver, entry, team, asset, rule, and callback shapes.
|
package/docs/data_contract.md
CHANGED
|
@@ -301,7 +301,7 @@ Operationally this is one architecture with two modes:
|
|
|
301
301
|
- local expert mode: browser owns stepping
|
|
302
302
|
- external render mode: browser only renders authoritative external frames
|
|
303
303
|
|
|
304
|
-
Policy Runner maps this directly through supported controller modes: `Distilled policy` and `Policy server` for browser-owned stepping, and `Live preview stream` for external render-only frames.
|
|
304
|
+
Policy Runner maps this directly through supported controller modes: `Distilled policy` and `Policy server` for browser-owned stepping, and `Live preview stream` for external render-only frames. The browser-owned HTTP policy-server transport uses protocol version `2`: `/policy/reset` receives static metadata such as `actionSpec`, `observationSpec`, configuration, `previousActionFields`, and `metricFields` once per session, while `/policy/decide-batch` receives only `driverIds` plus parallel compact `vectors`, `previousActions`, `metrics`, and `events` arrays aligned by driver index. Per-decision payloads intentionally do not include full observation objects, schemas, snapshots, or track/static metadata. Server integrations must treat the reset payload as the place to cache specs/configuration and tuple field order; Policy Runner server mode does not expose a legacy rich-observation transport option.
|
|
305
305
|
|
|
306
306
|
`steering` is an absolute steering-wheel target: `-1` points at the maximum left steering limit, `0` points at center, `1` points at the maximum right steering limit, and intermediate values are percentages of that limit. The vehicle physics moves the steering angle toward that target through the configured steering-rate limit, so centering is physical rather than an instantaneous snap. `throttle` and `brake` are clamped from `0` to `1`. `steering`, `throttle`, and `brake` are required on every controlled-driver action; missing or non-finite values fail action validation instead of defaulting to zero.
|
|
307
307
|
|
|
@@ -312,7 +312,7 @@ Pit actions may also include `pitCompound` or `pitTargetCompound`, for example `
|
|
|
312
312
|
The recommended runtime convention is a user-owned driver controller with `decideBatch(context) -> { [driverId]: { steering, throttle, brake, pitIntent? } }`. Controllers may load any model format or call any inference backend. PaddockJS only provides the simulator runtime context, stable controlled-driver ordering, cached specs, compact observations, and normalized action validation.
|
|
313
313
|
|
|
314
314
|
```js
|
|
315
|
-
import { createPaddockDriverControllerLoop } from '@inventure71/paddockjs';
|
|
315
|
+
import { createPaddockDriverControllerLoop } from '@inventure71/paddockjs/environment';
|
|
316
316
|
|
|
317
317
|
const controller = {
|
|
318
318
|
async init(ctx) {
|
|
@@ -636,7 +636,7 @@ sensors: {
|
|
|
636
636
|
|
|
637
637
|
Ray precision defaults to `driver`. Driver precision is the active model-facing sensor contract and uses the normal sampled ray step without extra refinement. `precision: 'debug'` is available for clearly labeled diagnostics with additional edge refinement, but debug precision must not be displayed as model senses unless the policy is also running with that exact sensor config.
|
|
638
638
|
|
|
639
|
-
Surface channels are computed only when requested. `kerb` is legal racing surface. `illegalSurface` reports the first grass or gravel surface crossed by the ray. Requested surface-ray channels are part of the active object observation and compact vector schema for both default and physical-driver profiles. `precision: 'driver'` road-edge, kerb, and illegal-surface channels use the canonical indexed track projection path with driver-precision validation, preserving zero-distance origin surface hits and avoiding debug-only refinement in policy inputs. Requested surface channels preserve an origin hit at `distanceMeters: 0` even if another requested channel has no visible boundary.
|
|
639
|
+
Surface channels are computed only when requested. `kerb` is legal racing surface. `illegalSurface` reports the first grass or gravel surface crossed by the ray. Requested surface-ray channels are part of the active object observation and compact vector schema for both default and physical-driver profiles. `precision: 'driver'` road-edge, kerb, and illegal-surface channels use the canonical indexed track projection path with driver-precision validation, preserving zero-distance origin surface hits and avoiding debug-only refinement in policy inputs. Requested surface channels preserve an origin hit at `distanceMeters: 0` even if another requested channel has no visible boundary. Barrier-origin recovery rays stay on direct indexed band results whenever the tracer can already prove the misses and the first non-barrier illegal-surface entry; ambiguous pit-connector, pit-lane-origin, and remaining unusual geometry cases may still use sampled validation internally, but the returned ray object shape is unchanged. Barrier walls are not exposed as a model-facing ray target; active ray objects, vectors, schemas, and visualizations must not expose a `barrier` ray channel. Barrier contact is enforced as a shared destruction boundary in both physics modes at the rendered wall's inner face and reported through events, snapshots, metrics, and per-driver episode state.
|
|
640
640
|
|
|
641
641
|
Nearby-car observations are car-relative. When advanced-mode velocity fields are present, `closingRateMetersPerSecond` and `timeToContactSeconds` use the actual 2D velocity vector instead of assuming the car is moving in its heading direction, so slip and recovery states remain physically accurate:
|
|
642
642
|
|
|
@@ -1108,6 +1108,8 @@ Every built track is automatically divided into three equal sectors. `snapshot.t
|
|
|
1108
1108
|
|
|
1109
1109
|
Every built track also exposes hidden `snapshot.track.timingLines`. Timing lines are spaced from the track length at an F1-style mini-sector target of roughly `150m..200m`; they are simulation metadata for gap calculation and are not rendered by default.
|
|
1110
1110
|
|
|
1111
|
+
In-memory snapshots keep the live track object so package code and host-side geometry helpers can still read full `samples`, pit-lane bounds, and internal runtime metadata. JSON serialization is intentionally leaner: `JSON.stringify(snapshot.track)` reuses a cached public geometry view, rounds static numeric track data to bounded precision, decimates the serialized `samples` polyline to a compact export path, encodes those exported samples as `[x, y, distance, heading, normalX, normalY, curvature]` rows under `track.sampleSchema`, and omits engine-only pit query helpers such as `pitLane.bounds`, `pitLane.boxBounds`, `pitLane.connectorBounds`, and pit-crew metadata. JSON pit-box and service-area exports also drop duplicated team metadata because the canonical team table already lives under `pitLane.teams`. This affects JSON/export/transport size only; it does not change the live JavaScript snapshot shape.
|
|
1112
|
+
|
|
1111
1113
|
Tracks whose resolved generation options enable pit lanes expose `snapshot.track.pitLane`. The `race` profile includes this geometry; training profiles are pitless by default unless explicitly overridden. When present, the pit lane is deterministic for the track seed and contains:
|
|
1112
1114
|
|
|
1113
1115
|
- `entry`: track distance before the start line, the true track `edgePoint`, an overlapping lane-facing `trackConnectPoint` on the track surface, connector points from the racing surface to the pit lane, and a procedural `roadCenterline` that is tangent to the main track at entry and tangent to the straight pit lane at the pit-lane start.
|
|
@@ -1116,8 +1118,8 @@ Tracks whose resolved generation options enable pit lanes expose `snapshot.track
|
|
|
1116
1118
|
- `workingLane`: parallel pit box lane start/end points, offset, width, and centerline points.
|
|
1117
1119
|
- `exit`: connector points from the pit lane back to the racing surface after the start line, plus the true track `edgePoint`, an overlapping lane-facing `trackConnectPoint` on the track surface, and a procedural `roadCenterline` that is tangent to the straight pit lane at pit-lane end and tangent to the main track at merge.
|
|
1118
1120
|
- `teams`: team pit groups with id, name, color, index, the two assigned garage box ids, and one shared service area id.
|
|
1119
|
-
- `serviceAreas`: 10 team service areas, one per team, each with center, queue point, corners, queue corners, team index
|
|
1120
|
-
- `boxes`: 20 unused garage boxes as 10 team pairs, each with center, lane target, corners, team index, box index
|
|
1121
|
+
- `serviceAreas`: 10 team service areas, one per team, each with center, queue point, corners, queue corners, and team index. The live object also keeps optional `teamId`, `teamName`, and `teamColor`; JSON export drops those duplicates because `pitLane.teams` is the canonical source.
|
|
1122
|
+
- `boxes`: 20 unused garage boxes as 10 team pairs, each with center, lane target, corners, team index, and box index. The live object also keeps optional `teamId`, `teamName`, and `teamColor`; JSON export drops those duplicates because `pitLane.teams` is the canonical source.
|
|
1121
1123
|
|
|
1122
1124
|
Pit-lane road and box states are legal drivable surfaces. Track state may report `surface: 'pit-entry'`, `'pit-lane'`, `'pit-exit'`, or `'pit-box'` with `inPitLane: true`; `crossTrackError` remains the distance from the main race track for compatibility, while pit-specific offsets are exposed as pit-lane fields on the internal state.
|
|
1123
1125
|
|
|
@@ -1183,7 +1185,7 @@ Controller methods:
|
|
|
1183
1185
|
- `cancelPenalty(penaltyId)`: cancels a penalty so it no longer affects service, timing, grid, or classification.
|
|
1184
1186
|
- `getSnapshot()`: returns the latest simulation snapshot.
|
|
1185
1187
|
|
|
1186
|
-
Composable controllers additionally expose:
|
|
1188
|
+
Composable controllers additionally expose the canonical composable mounting API:
|
|
1187
1189
|
|
|
1188
1190
|
- `mountRaceControls(root)`: renders the top control/header component.
|
|
1189
1191
|
- `mountCameraControls(root)`: renders package-owned camera mode, zoom, and project/radio banner mute controls outside the race canvas.
|
|
@@ -1201,4 +1203,8 @@ Composable controllers additionally expose:
|
|
|
1201
1203
|
- `querySelector(selector)` / `querySelectorAll(selector)`: search across the mounted package-owned composable roots. These exist for integration tests and advanced host glue; ordinary hosts should prefer explicit controller methods and mounted component roots.
|
|
1202
1204
|
- `start()`: initializes PixiJS, binds mounted controls, and starts the simulation loop.
|
|
1203
1205
|
|
|
1204
|
-
Mount component roots before calling `start()`. If a component is not mounted, the runtime skips that UI surface instead of requiring hidden placeholder DOM.
|
|
1206
|
+
Mount component roots before calling `start()`. If a component is not mounted, the runtime skips that UI surface instead of requiring hidden placeholder DOM. Every public mounted surface renders lightweight static package markup and a surface-specific `data-paddock-loading` overlay immediately. `start()` removes those overlays only after PixiJS, assets, controls, initial DOM readouts, and the first frame have initialized. If `restart(nextOptions)` is called before `start()`, mounted component markup is refreshed from the new resolved options so copy, component visibility, driver data, and theme changes do not leave stale pre-start HTML behind.
|
|
1207
|
+
|
|
1208
|
+
This loading overlay is the package's client-side startup state. It begins after the PaddockJS JavaScript bundle has executed and a mount function has run. Hosts that need an instant placeholder before package JavaScript loads should render host-owned fallback HTML/CSS around or inside the mount root, then let `mountF1Simulator()` or the composable `mount*()` calls replace that root content when the package starts.
|
|
1209
|
+
|
|
1210
|
+
For a package-owned pre-JS placeholder, import `createPaddockLoadingPlaceholder()` from `@inventure71/paddockjs/placeholder` and the optional tiny stylesheet from `@inventure71/paddockjs/placeholder.css`. That subpath is intentionally separate from the root browser mount and does not import PixiJS, simulator assets, package runtime CSS, or `F1SimulatorApp`.
|