@inventure71/paddockjs 0.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.
Files changed (44) hide show
  1. package/AGENTS.md +86 -0
  2. package/CHANGELOG.md +5 -0
  3. package/LICENSE +176 -0
  4. package/README.md +165 -0
  5. package/assets/f1-broadcast-panel-surface.png +0 -0
  6. package/assets/f1-car-sprite-game.png +0 -0
  7. package/assets/f1-driver-helmet.png +0 -0
  8. package/assets/f1-safety-car-sprite.png +0 -0
  9. package/assets/f1-texture-asphalt.png +0 -0
  10. package/assets/f1Logo.png +0 -0
  11. package/docs/architecture.md +209 -0
  12. package/docs/concepts.md +178 -0
  13. package/docs/data_contract.md +360 -0
  14. package/docs/data_model.md +167 -0
  15. package/docs/index.md +15 -0
  16. package/docs/learnings.md +47 -0
  17. package/docs/rules.md +181 -0
  18. package/docs/system_specs.md +211 -0
  19. package/package.json +85 -0
  20. package/src/api/PaddockSimulatorController.js +233 -0
  21. package/src/app/F1SimulatorApp.js +1293 -0
  22. package/src/app/domBindings.js +64 -0
  23. package/src/config/defaultAssets.js +29 -0
  24. package/src/config/defaultOptions.js +184 -0
  25. package/src/data/championship.js +225 -0
  26. package/src/data/customFields.js +14 -0
  27. package/src/data/demoDrivers.js +134 -0
  28. package/src/data/driverData.js +71 -0
  29. package/src/data/normalizeDrivers.js +21 -0
  30. package/src/data/vehicleData.js +81 -0
  31. package/src/index.d.ts +399 -0
  32. package/src/index.js +116 -0
  33. package/src/rendering/proceduralTrackAsset.js +517 -0
  34. package/src/rendering/renderSnapshot.js +23 -0
  35. package/src/simulation/driverController.js +253 -0
  36. package/src/simulation/raceSimulation.js +933 -0
  37. package/src/simulation/simMath.js +34 -0
  38. package/src/simulation/trackModel.js +607 -0
  39. package/src/simulation/units.js +30 -0
  40. package/src/simulation/vehiclePhysics.js +104 -0
  41. package/src/standalone.js +15 -0
  42. package/src/styles.css +1818 -0
  43. package/src/ui/componentTemplates.js +229 -0
  44. package/src/ui/shellTemplate.js +43 -0
@@ -0,0 +1,178 @@
1
+ # Concepts
2
+
3
+ ## Host
4
+
5
+ The host is the website or app that installs PaddockJS. The host provides data and chooses what happens when a driver/project is opened.
6
+
7
+ Example host responsibilities:
8
+
9
+ - Install `@inventure71/paddockjs`.
10
+ - Add a root element.
11
+ - Pass `drivers`.
12
+ - Optionally pass `entries`.
13
+ - Implement `onDriverOpen(driver)`.
14
+
15
+ ## Mounted Simulator
16
+
17
+ The mounted simulator is the runtime returned by `mountF1Simulator()`.
18
+
19
+ It owns:
20
+
21
+ - Generated DOM shell.
22
+ - PixiJS application.
23
+ - Race simulation instance.
24
+ - Event listeners.
25
+ - Camera state.
26
+ - Timing and telemetry UI.
27
+
28
+ It exposes lifecycle methods such as `destroy()` and `restart()`.
29
+
30
+ ## Composable Simulator
31
+
32
+ The composable simulator is the runtime created by `createPaddockSimulator()`.
33
+
34
+ It lets a host mount package-owned UI surfaces into separate roots:
35
+
36
+ - Race controls.
37
+ - Timing tower.
38
+ - Race canvas.
39
+ - Telemetry panel.
40
+ - Race-data panel.
41
+
42
+ The host controls placement and layout. PaddockJS still owns the markup, CSS classes, event bindings, assets, and runtime behavior.
43
+
44
+ ## Simulator Controller
45
+
46
+ The simulator controller is the object returned by both public APIs.
47
+
48
+ The all-in-one controller starts immediately after `mountF1Simulator()`.
49
+
50
+ The composable controller has a setup phase:
51
+
52
+ - Mount the desired components.
53
+ - Mount the race canvas.
54
+ - Call `start()`.
55
+
56
+ After startup, both controller styles expose runtime methods such as `restart()`, `selectDriver()`, `setSafetyCarDeployed()`, `getSnapshot()`, and `destroy()`.
57
+
58
+ ## Driver
59
+
60
+ A driver is the host-facing entity shown as a race entry. In the portfolio use case, each driver maps to a project.
61
+
62
+ Core fields:
63
+
64
+ - `id`
65
+ - `name`
66
+ - `color`
67
+ - `link`
68
+ - `raceData`
69
+
70
+ Drivers can also define display fields such as `icon`, `code`, `tire`, `driverNumber`, and `customFields`.
71
+
72
+ ## Entry
73
+
74
+ An entry is the pairing between a driver, car setup, and optional team metadata. Entries live in the `entries` option and are matched by `driverId`.
75
+
76
+ Entry responsibilities:
77
+
78
+ - Driver number.
79
+ - Timing name.
80
+ - Driver ratings.
81
+ - Vehicle ratings.
82
+ - Optional team object with `id`, `name`, `color`, and `icon`.
83
+ - Driver and vehicle rating components for the car/driver overview panel.
84
+ - Optional driver and vehicle `customFields` for extra overview metadata.
85
+
86
+ ## Team
87
+
88
+ A team is entry-level metadata used for race identity and future pit-lane behavior. The timing tower uses the team icon in its team column. Team color defaults to the car color when omitted.
89
+
90
+ ## Driver Ratings
91
+
92
+ Driver ratings are `0-100` values converted into behavior inputs:
93
+
94
+ - `pace`
95
+ - `racecraft`
96
+ - `aggression`
97
+ - `riskTolerance`
98
+ - `patience`
99
+ - `consistency`
100
+
101
+ `50` is neutral.
102
+
103
+ ## Vehicle Ratings
104
+
105
+ Vehicle ratings are `0-100` values converted into physical setup values:
106
+
107
+ - `power`
108
+ - `braking`
109
+ - `aero`
110
+ - `dragEfficiency`
111
+ - `mechanicalGrip`
112
+ - `weightControl`
113
+ - `tireCare`
114
+
115
+ `50` is neutral.
116
+
117
+ ## Track
118
+
119
+ The track is a closed sampled centerline with width, kerbs, runoff, DRS zones, and surface classification.
120
+
121
+ The package supports:
122
+
123
+ - A default named track for low-level simulation callers that provide neither a track nor a track seed.
124
+ - Procedural browser tracks from generated or explicit `trackSeed` values.
125
+
126
+ Browser mounts that omit `trackSeed` create a fresh procedural track for that mount. Passing `trackSeed` makes the generated circuit deterministic; repeated procedural seeds are cached within the page runtime.
127
+
128
+ ## Progress
129
+
130
+ Progress is the wrapped distance around the current lap.
131
+
132
+ Race distance is cumulative and can increase beyond one lap. Ranking uses race distance, not wrapped progress.
133
+
134
+ ## Snapshot
135
+
136
+ A snapshot is the read-only state returned by the race simulation and exposed through `getSnapshot()`.
137
+
138
+ Snapshots include:
139
+
140
+ - Time.
141
+ - World dimensions.
142
+ - Track model.
143
+ - Race control mode.
144
+ - Safety car state.
145
+ - Current rules.
146
+ - Events from the last step.
147
+ - Ordered cars with telemetry and setup data.
148
+
149
+ Per-car timing exposes both interval to the car ahead and cumulative gap to the leader. Per-car speed and distance display fields are calibrated through the simulator unit conversion helpers instead of treating rendered world units as meters.
150
+
151
+ ## Render Snapshot
152
+
153
+ The render snapshot interpolates moving entities between physics ticks for smoother rendering.
154
+
155
+ `src/rendering/renderSnapshot.js` handles interpolation for cars and the safety car.
156
+
157
+ ## Race Data Panel
158
+
159
+ The race data panel is the lower-third UI shown over the track.
160
+
161
+ It has two modes:
162
+
163
+ - Selected project mode.
164
+ - Project radio quote mode.
165
+
166
+ Project opening is callback-based through `onDriverOpen(driver)`.
167
+
168
+ ## Assets
169
+
170
+ PaddockJS bundles default simulator assets:
171
+
172
+ - F1 logo.
173
+ - Car sprite.
174
+ - Safety car sprite.
175
+ - Broadcast panel surface.
176
+ - Asphalt texture.
177
+
178
+ Hosts can override assets, but they should not need to provide them.
@@ -0,0 +1,360 @@
1
+ # Data Contract
2
+
3
+ This file documents the data that host websites pass into PaddockJS.
4
+
5
+ ## Mount Options
6
+
7
+ All-in-one API:
8
+
9
+ ```js
10
+ mountF1Simulator(root, {
11
+ preset,
12
+ drivers,
13
+ entries,
14
+ onDriverOpen,
15
+ seed,
16
+ trackSeed,
17
+ totalLaps,
18
+ initialCameraMode,
19
+ theme,
20
+ title,
21
+ kicker,
22
+ backLinkHref,
23
+ backLinkLabel,
24
+ showBackLink,
25
+ ui,
26
+ assets,
27
+ onLoadingChange,
28
+ onReady,
29
+ onError,
30
+ onDriverSelect,
31
+ onRaceEvent,
32
+ onLapChange,
33
+ onRaceFinish,
34
+ });
35
+ ```
36
+
37
+ Composable API:
38
+
39
+ ```js
40
+ const simulator = createPaddockSimulator({
41
+ drivers,
42
+ entries,
43
+ onDriverOpen,
44
+ seed,
45
+ trackSeed,
46
+ totalLaps,
47
+ initialCameraMode,
48
+ preset,
49
+ theme,
50
+ ui,
51
+ assets,
52
+ });
53
+
54
+ simulator.mountRaceCanvas(canvasRoot, {
55
+ includeRaceDataPanel: true,
56
+ includeTimingTower: true,
57
+ timingTowerVerticalFit: 'scroll',
58
+ });
59
+ await simulator.start();
60
+ ```
61
+
62
+ Additional optional components:
63
+
64
+ ```js
65
+ simulator.mountRaceControls(controlsRoot);
66
+ simulator.mountCameraControls(cameraControlsRoot);
67
+ simulator.mountSafetyCarControl(safetyCarRoot);
68
+ simulator.mountTimingTower(timingRoot);
69
+ simulator.mountTelemetryPanel(telemetryRoot);
70
+ simulator.mountCarDriverOverview(overviewRoot);
71
+ simulator.mountRaceDataPanel(raceDataRoot);
72
+ ```
73
+
74
+ ## Required Options
75
+
76
+ `drivers` is required and must be a non-empty array.
77
+
78
+ `trackSeed` is optional. If omitted, each mounted browser simulator creates a fresh procedural circuit. Passing a `trackSeed` makes the track deterministic; repeated procedural seeds are cached so multiple mounts can reuse the same generated track definition.
79
+
80
+ Each driver must have:
81
+
82
+ ```js
83
+ {
84
+ id: 'budget',
85
+ name: 'Budget Buddy',
86
+ color: '#ff2d55',
87
+ }
88
+ ```
89
+
90
+ ## Recommended Driver Shape
91
+
92
+ ```js
93
+ {
94
+ id: 'budget',
95
+ name: 'Budget Buddy',
96
+ color: '#ff2d55',
97
+ link: '/project_details/project-budget-buddy.html',
98
+ icon: 'BB',
99
+ code: 'BUD',
100
+ tire: 'M',
101
+ raceData: ['AI finance coach', 'Python + LLM', 'Budget guardrails'],
102
+ customFields: {
103
+ Specialty: 'Late braking',
104
+ },
105
+ }
106
+ ```
107
+
108
+ Fields:
109
+
110
+ - `id`: stable unique ID used for matching entries and selection.
111
+ - `name`: display name.
112
+ - `color`: car/team color.
113
+ - `link`: optional host-owned navigation target.
114
+ - `icon`: short display mark in timing/telemetry.
115
+ - `code`: fallback timing code.
116
+ - `tire`: `S`, `M`, or `H`.
117
+ - `raceData`: short project/radio lines shown in the UI.
118
+ - `customFields`: optional driver overview fields. Use an object or an array of `{ label, value }`.
119
+
120
+ ## Entry Shape
121
+
122
+ Entries are optional. If omitted, defaults are used.
123
+
124
+ ```js
125
+ {
126
+ driverId: 'budget',
127
+ driverNumber: 71,
128
+ timingName: 'Budget',
129
+ driver: {
130
+ pace: 52,
131
+ racecraft: 74,
132
+ aggression: 38,
133
+ riskTolerance: 47,
134
+ patience: 81,
135
+ consistency: 86,
136
+ customFields: {
137
+ Style: 'Patient race manager',
138
+ },
139
+ },
140
+ vehicle: {
141
+ id: 'budget-bb01',
142
+ name: 'BB-01 Ledger',
143
+ power: 48,
144
+ braking: 72,
145
+ aero: 55,
146
+ dragEfficiency: 66,
147
+ mechanicalGrip: 63,
148
+ weightControl: 58,
149
+ tireCare: 82,
150
+ customFields: [
151
+ { label: 'Aero kit', value: 'Low drag' },
152
+ { label: 'Battery map', value: 'Conservative' },
153
+ ],
154
+ },
155
+ team: {
156
+ id: 'ledger-racing',
157
+ name: 'Ledger Racing',
158
+ color: '#00ff84',
159
+ icon: 'LR',
160
+ },
161
+ }
162
+ ```
163
+
164
+ Entries match drivers by `driverId`.
165
+ The car/driver overview primarily renders the existing driver and vehicle rating components from `driver` and `vehicle`. `team` is optional team-level metadata for race identity and future pit behavior; `color` defaults to the driver/car color, and `icon` defaults from the team name or timing code. The timing tower uses the team icon in the car/team column. `driver.customFields`, `vehicle.customFields`, and top-level driver `customFields` are accepted as extra metadata after those defined components.
166
+
167
+ ## Rating Rules
168
+
169
+ Driver and vehicle ratings use `0-100`.
170
+
171
+ - `0`: minimum.
172
+ - `50`: neutral.
173
+ - `100`: maximum.
174
+
175
+ Rating conversion lives in:
176
+
177
+ - `src/data/driverData.js`
178
+ - `src/data/vehicleData.js`
179
+
180
+ ## Callback Contract
181
+
182
+ PaddockJS does not directly own host navigation. The host should provide:
183
+
184
+ ```js
185
+ onDriverOpen(driver) {
186
+ window.location.href = driver.link;
187
+ }
188
+ ```
189
+
190
+ The callback receives the normalized driver object. If the host wants modals, routing, analytics, or external tabs, it should implement that inside this callback.
191
+
192
+ Optional lifecycle callbacks:
193
+
194
+ ```js
195
+ {
196
+ onLoadingChange({ loading, phase }) {},
197
+ onReady({ snapshot }) {},
198
+ onError(error, context) {},
199
+ onDriverSelect(driver, snapshot) {},
200
+ onRaceEvent(event, snapshot) {},
201
+ onLapChange({ previousLeaderLap, leaderLap, leader, snapshot }) {},
202
+ onRaceFinish({ winner, classification, snapshot }) {},
203
+ }
204
+ ```
205
+
206
+ `onRaceEvent` receives simulation events such as `contact`, `safety-car`, `green-flag`, `start-lights-out`, and `race-finish`. Host callback errors are caught; if `onError` exists, it receives `{ callback: name }` context for callback failures.
207
+
208
+ ## Asset Overrides
209
+
210
+ Assets are optional because PaddockJS bundles defaults.
211
+
212
+ Override shape:
213
+
214
+ ```js
215
+ assets: {
216
+ car: '/custom/car.png',
217
+ carOverview: '/custom/car-overview.png',
218
+ driverHelmet: '/custom/driver-helmet.png',
219
+ safetyCar: '/custom/safety-car.png',
220
+ broadcastPanel: '/custom/broadcast-panel.png',
221
+ f1Logo: '/custom/logo.png',
222
+ trackTextures: {
223
+ asphalt: '/custom/asphalt.png',
224
+ },
225
+ }
226
+ ```
227
+
228
+ Do not require hosts to copy PaddockJS default assets into their own project. If defaults are missing, fix the package.
229
+
230
+ ## UI Options
231
+
232
+ Preset-first options:
233
+
234
+ ```js
235
+ preset: 'timing-overlay',
236
+ ```
237
+
238
+ Available presets are:
239
+
240
+ - `dashboard`: the default all-in-one shell behavior.
241
+ - `timing-overlay`: left timing-tower overlay, external camera controls, auto lower-third sizing.
242
+ - `compact-race`: a smaller race-canvas-focused setup with fewer surfaces.
243
+ - `full-dashboard`: full telemetry/timing shell with external camera controls.
244
+
245
+ Explicit host options are merged after the preset, so `ui` and `theme` fields can override preset defaults.
246
+
247
+ Current UI options:
248
+
249
+ ```js
250
+ ui: {
251
+ layoutPreset: 'standard',
252
+ cameraControls: 'embedded',
253
+ showFps: true,
254
+ showTimingTower: true,
255
+ showTelemetry: true,
256
+ showRaceDataPanel: true,
257
+ raceDataBanners: {
258
+ initial: 'project',
259
+ enabled: ['project', 'radio'],
260
+ },
261
+ raceDataBannerSize: 'custom',
262
+ timingTowerVerticalFit: 'expand-race-view',
263
+ }
264
+ ```
265
+
266
+ - `layoutPreset`: `'standard'` or `'left-tower-overlay'`. The overlay preset creates a left broadcast gutter inside the race canvas, places the timing tower in that gutter at the same width as the default timing-board column, and keeps camera controls and camera framing in the remaining race-view area. In the combined shell, the project/radio lower-third stays inside the race window and can render over the timing sidebar.
267
+ - `cameraControls`: `'embedded'`, `'external'`, or `false`. Embedded controls render inside the race canvas. External controls are mounted with `mountCameraControls(root)`. `false` leaves camera controls unrendered, though callers can still drive selection through controller methods.
268
+ - `showFps`: controls whether the race canvas renders the FPS readout.
269
+ - `showRaceDataPanel`: controls whether the precombined shell includes the project/radio lower-third inside the race window.
270
+ - `showTimingTower`, `showTelemetry`: reserved component visibility flags for host layout decisions.
271
+ - `raceDataBanners.initial`: `'project'`, `'radio'`, or `'hidden'`. This controls which lower-third appears first in the precombined shell.
272
+ - `raceDataBanners.enabled`: array containing `'project'` and/or `'radio'`. Disabled banner types never appear, including after driver selection.
273
+ - `raceDataBannerSize`: `'custom'` preserves the default lower-third geometry and exposes package CSS variables for host tuning. `'auto'` uses the race space to the right of the timing board when there is enough room and falls back to full lower-third overlap when there is not.
274
+ - `timingTowerVerticalFit`: `'expand-race-view'` lets the combined race window grow to contain the timing tower. `'scroll'` keeps the race window height and scrolls the timing list inside the cropped tower. The same values can be passed to `mountRaceCanvas(root, { includeTimingTower: true, timingTowerVerticalFit })` for an embedded composable timing tower.
275
+
276
+ No UI option exists for raw timing-tower width, max width, or horizontal ratio. The timing tower is capped by the package CSS variable `--timing-board-max-width` because very wide timing boards read poorly. Host pages can scale the whole simulator by changing the mount container, but package-owned layout presets keep their internal proportions inside PaddockJS. For standalone timing towers, give the mount root a fixed height when a fixed vertical footprint is needed; the package keeps the frame inside that height and scrolls only the timing entries.
277
+
278
+ ## Theme And Sizing Contract
279
+
280
+ ```js
281
+ theme: {
282
+ accentColor: '#e10600',
283
+ greenColor: '#14c784',
284
+ yellowColor: '#ffd166',
285
+ timingTowerMaxWidth: '390px',
286
+ raceViewMinHeight: '620px',
287
+ }
288
+ ```
289
+
290
+ These values are applied as package CSS variables:
291
+
292
+ - `accentColor` -> `--paddock-accent-color`
293
+ - `greenColor` -> `--paddock-green-color`
294
+ - `yellowColor` -> `--paddock-yellow-color`
295
+ - `timingTowerMaxWidth` -> `--paddock-timing-tower-max-width`
296
+ - `raceViewMinHeight` -> `--paddock-race-view-min-height`
297
+
298
+ Prefer these fields over host CSS overrides. They are the stable styling surface for reusable embeds.
299
+
300
+ ## Race Completion Snapshot
301
+
302
+ After every car completes `totalLaps`, `getSnapshot()` returns:
303
+
304
+ ```js
305
+ {
306
+ raceControl: {
307
+ mode: 'safety-car',
308
+ finished: true,
309
+ finishedAt: 123.4,
310
+ winner: { id, code, name, rank, finished },
311
+ classification: [
312
+ { id, code, timingCode, name, rank, lap, lapsCompleted, distanceMeters, gapMeters, gapSeconds, intervalSeconds, finished, finishTime },
313
+ ],
314
+ },
315
+ }
316
+ ```
317
+
318
+ Cars also include `team`, `speedKph`, `distanceMeters`, `gapAheadMeters`, `gapAheadSeconds`, `intervalAheadSeconds`, `leaderGapSeconds`, `finished`, `finishTime`, and `classifiedRank`. `gapAheadSeconds` and `intervalAheadSeconds` are the interval to the car directly ahead. `leaderGapSeconds` is the cumulative gap to P1. The first car to finish sets `raceControl.winner` and receives a `car-finish` event, but the race keeps running until all cars finish. After final classification, the field continues under safety-car behavior.
319
+
320
+ ## Unit Conversion
321
+
322
+ The simulation keeps its internal physics in simulator units. Public speed and distance display fields use `src/simulation/units.js`:
323
+
324
+ - `simUnitsToMeters(simUnits)`
325
+ - `metersToSimUnits(meters)`
326
+ - `simSpeedToKph(simUnitsPerSecond)`
327
+ - `kphToSimSpeed(kph)`
328
+
329
+ The current calibrated speed scale maps `VEHICLE_LIMITS.maxSpeed` to an F1-like `330 km/h`. Rendered car sprite size remains a visual scale and is intentionally larger than physical car length for readability.
330
+
331
+ ## Returned Controller
332
+
333
+ ```js
334
+ const simulator = await mountF1Simulator(root, options);
335
+ ```
336
+
337
+ Controller methods:
338
+
339
+ - `destroy()`: removes listeners, destroys PixiJS runtime, clears the host root.
340
+ - `restart(nextOptions)`: restarts the simulation with merged options.
341
+ - `selectDriver(driverId)`: selects and focuses a driver.
342
+ - `setSafetyCarDeployed(deployed)`: toggles safety car state.
343
+ - `callSafetyCar()`: deploys the safety car.
344
+ - `clearSafetyCar()`: releases the safety car.
345
+ - `toggleSafetyCar()`: switches safety car deployment based on the current snapshot.
346
+ - `getSnapshot()`: returns the latest simulation snapshot.
347
+
348
+ Composable controllers additionally expose:
349
+
350
+ - `mountRaceControls(root)`: renders the top control/header component.
351
+ - `mountCameraControls(root)`: renders package-owned camera mode and zoom controls outside the race canvas.
352
+ - `mountSafetyCarControl(root)`: renders a package-owned safety-car button that binds to the same race-control state as other safety buttons.
353
+ - `mountTimingTower(root)`: renders the timing tower component.
354
+ - `mountRaceCanvas(root, { includeRaceDataPanel, includeTimingTower, timingTowerVerticalFit })`: renders the PixiJS canvas host, optional FPS, start lights, and optionally embedded camera controls. Pass `includeRaceDataPanel: true` to place the project/radio lower-third inside the race window so it shares race-canvas clipping and layering. Pass `includeTimingTower: true` to place the timing tower inside the race canvas; `timingTowerVerticalFit: 'expand-race-view'` grows the canvas to the tower height, while `'scroll'` keeps the canvas height and scrolls timing rows inside the tower frame. This is required before `start()`.
355
+ - `mountTelemetryPanel(root, { includeOverview })`: renders selected-car text telemetry. It includes the car/driver overview by default unless `includeOverview: false` is passed or `ui.telemetryIncludesOverview` is `false`.
356
+ - `mountCarDriverOverview(root)`: renders the package-owned car/driver overview as a separate component with a Car/Driver toggle, center visual, and linked stat cells from the existing driver/vehicle rating components.
357
+ - `mountRaceDataPanel(root)`: renders the project/race-data lower-third as a separate component for hosts that intentionally want it outside the race canvas.
358
+ - `start()`: initializes PixiJS, binds mounted controls, and starts the simulation loop.
359
+
360
+ Mount component roots before calling `start()`. If a component is not mounted, the runtime skips that UI surface instead of requiring hidden placeholder DOM. Mounted surfaces render a package-owned loading overlay immediately; `start()` removes those overlays after PixiJS, assets, controls, and initial readouts have initialized.
@@ -0,0 +1,167 @@
1
+ # F1 Simulator Data Model
2
+
3
+ This simulator separates three responsibilities:
4
+
5
+ - `DriverData` defines the driver ratings sheet.
6
+ - `VehicleData` defines the vehicle ratings sheet.
7
+ - `championship.js` pairs one driver sheet with one vehicle sheet for each championship entry.
8
+
9
+ The race loop does not read raw rating numbers directly. It receives constructor arguments produced by the data classes.
10
+
11
+ ## Rating Scale
12
+
13
+ Driver and vehicle stats use a `0-100` rating scale.
14
+
15
+ - `50` is neutral.
16
+ - Values above `50` add to the class default.
17
+ - Values below `50` subtract from the class default.
18
+ - Each stat has its own `base` and `variance`, so different stats scale by appropriate physical or behavioral amounts.
19
+
20
+ The conversion is:
21
+
22
+ ```js
23
+ normalized = (rating - 50) / 50;
24
+ actualValue = base + normalized * variance;
25
+ ```
26
+
27
+ For vehicle stats where lower is better, such as drag and mass, the stat definition uses `direction: -1`.
28
+
29
+ ## Driver Data
30
+
31
+ `driverData.js` owns the driver data class.
32
+
33
+ ```js
34
+ new DriverData({
35
+ pace,
36
+ racecraft,
37
+ aggression,
38
+ riskTolerance,
39
+ patience,
40
+ consistency,
41
+ })
42
+ ```
43
+
44
+ Driver stats:
45
+
46
+ - `pace`: Converts to the driver's base speed potential.
47
+ - `racecraft`: Converts to the driver's ability to handle traffic, steering, and race situations.
48
+ - `aggression`: Converts to the driver's willingness to attack and choose passing lines.
49
+ - `riskTolerance`: Converts to how much nearby traffic risk the driver accepts.
50
+ - `patience`: Converts to how much the driver waits before escalating aggression.
51
+ - `consistency`: Stored as constructor data for future behavior tuning.
52
+
53
+ `DriverData.toConstructorArgs()` returns:
54
+
55
+ ```js
56
+ {
57
+ ratings,
58
+ pace,
59
+ racecraft,
60
+ consistency,
61
+ personality: {
62
+ aggression,
63
+ riskTolerance,
64
+ patience,
65
+ },
66
+ }
67
+ ```
68
+
69
+ Those values are consumed by `raceSimulation.js` and `driverController.js`.
70
+
71
+ ## Vehicle Data
72
+
73
+ `vehicleData.js` owns the vehicle data class.
74
+
75
+ ```js
76
+ new VehicleData({
77
+ id,
78
+ name,
79
+ power,
80
+ braking,
81
+ aero,
82
+ dragEfficiency,
83
+ mechanicalGrip,
84
+ weightControl,
85
+ tireCare,
86
+ })
87
+ ```
88
+
89
+ Vehicle stats:
90
+
91
+ - `power`: Converts to `powerNewtons`.
92
+ - `braking`: Converts to `brakeNewtons`.
93
+ - `aero`: Converts to `downforceCoefficient`.
94
+ - `dragEfficiency`: Converts inversely to `dragCoefficient`; higher rating means less drag.
95
+ - `mechanicalGrip`: Converts to `tireGrip`.
96
+ - `weightControl`: Converts inversely to `mass`; higher rating means lower mass.
97
+ - `tireCare`: Converts to `tireCare`, reserved for tire behavior tuning.
98
+
99
+ `VehicleData.toConstructorArgs()` returns:
100
+
101
+ ```js
102
+ {
103
+ id,
104
+ name,
105
+ ratings,
106
+ powerNewtons,
107
+ brakeNewtons,
108
+ downforceCoefficient,
109
+ dragCoefficient,
110
+ tireGrip,
111
+ mass,
112
+ tireCare,
113
+ }
114
+ ```
115
+
116
+ Those values are consumed by `raceSimulation.js` when creating the physical car.
117
+
118
+ ## Championship Pairings
119
+
120
+ `championship.js` defines one pairing per project:
121
+
122
+ ```js
123
+ {
124
+ driverId: 'budget',
125
+ driverNumber: 71,
126
+ timingName: 'Budget',
127
+ driver: new DriverData({ pace: 52, racecraft: 74, ... }),
128
+ vehicle: new VehicleData({ id: 'budget-bb01', power: 48, braking: 72, ... }),
129
+ team: { id: 'ledger-racing', name: 'Ledger Racing', color: '#00ff84', icon: 'LR' },
130
+ }
131
+ ```
132
+
133
+ `buildChampionshipDriverGrid()` merges host-provided driver metadata with the championship pairing data. The demo page supplies `demoDrivers.js`, but the reusable component can receive any compatible driver list from `mountF1Simulator()`.
134
+
135
+ ```js
136
+ constructorArgs: {
137
+ driver: driver.toConstructorArgs(),
138
+ vehicle: vehicle.toConstructorArgs(),
139
+ }
140
+ ```
141
+
142
+ It also copies the converted driver fields and vehicle fields onto the object used by `createRaceSimulation()`.
143
+
144
+ Team metadata is copied onto the same normalized driver object. `team.color` defaults to the driver/car color when omitted, and `team.icon` defaults from the team name or timing code. Teams are metadata-only for now, but they are intentionally modeled separately from driver and vehicle data so future pit boxes, pit colors, team strategy, and team-owned fields do not have to be forced into car or driver objects.
145
+
146
+ ## Unit Conversion
147
+
148
+ `src/simulation/units.js` owns the simulator-unit conversion layer:
149
+
150
+ ```js
151
+ simUnitsToMeters(simUnits);
152
+ metersToSimUnits(meters);
153
+ simSpeedToKph(simUnitsPerSecond);
154
+ kphToSimSpeed(kph);
155
+ ```
156
+
157
+ The physics engine still works in simulator units. Public display fields such as `speedKph`, `distanceMeters`, and `gapMeters` use the conversion helpers. The current scale maps the simulator's maximum speed to `330 km/h`; rendered car sprite dimensions are deliberately larger than physical F1 dimensions for readability and are tracked separately from the physics distance scale.
158
+
159
+ ## Runtime Flow
160
+
161
+ 1. `mountF1Simulator()` receives host-provided drivers and optional championship entries.
162
+ 2. `normalizeSimulatorDrivers()` turns those entries into the constructor data expected by the race engine.
163
+ 3. `raceSimulation.js` creates each physical car from the driver entry.
164
+ 4. Driver values affect behavior through `driverController.js`.
165
+ 5. Vehicle values affect physics through `vehiclePhysics.js`.
166
+
167
+ In short: the championship file chooses the pairings, the data classes convert ratings into constructor arguments, and the simulation uses those converted arguments.