@inventure71/paddockjs 0.2.0 → 1.0.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 (63) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/INSTALL_AND_UPDATE.md +165 -0
  3. package/README.md +217 -20
  4. package/docs/architecture.md +84 -15
  5. package/docs/concepts.md +20 -3
  6. package/docs/data_contract.md +412 -20
  7. package/docs/data_model.md +2 -1
  8. package/docs/index.md +1 -0
  9. package/docs/learnings.md +1 -1
  10. package/docs/rules.md +140 -19
  11. package/docs/system_specs.md +74 -20
  12. package/docs/training.md +206 -0
  13. package/examples/train-basic-policy.mjs +276 -0
  14. package/examples/trainingData.mjs +133 -0
  15. package/package.json +18 -8
  16. package/src/api/PaddockSimulatorController.js +39 -4
  17. package/src/app/BrowserExpertAdapter.js +32 -0
  18. package/src/app/F1SimulatorApp.js +1251 -146
  19. package/src/app/domBindings.js +10 -2
  20. package/src/config/defaultOptions.js +11 -3
  21. package/src/data/championship.js +13 -0
  22. package/src/data/normalizeDrivers.js +7 -2
  23. package/src/environment/actions.js +68 -0
  24. package/src/environment/episode.js +17 -0
  25. package/src/environment/evaluation.js +184 -0
  26. package/src/environment/events.js +17 -0
  27. package/src/environment/index.d.ts +521 -0
  28. package/src/environment/index.js +15 -0
  29. package/src/environment/observationOptions.js +12 -0
  30. package/src/environment/observations.js +284 -0
  31. package/src/environment/options.js +157 -0
  32. package/src/environment/recorder.js +28 -0
  33. package/src/environment/rewards.js +69 -0
  34. package/src/environment/runtime.js +175 -0
  35. package/src/environment/scenarios.js +220 -0
  36. package/src/environment/sensors.js +367 -0
  37. package/src/environment/specs.js +163 -0
  38. package/src/environment/workerProtocol.js +52 -0
  39. package/src/index.d.ts +405 -6
  40. package/src/index.js +27 -2
  41. package/src/rendering/proceduralTrackAsset.js +343 -24
  42. package/src/simulation/collisionGeometry.js +214 -0
  43. package/src/simulation/driverController.js +431 -65
  44. package/src/simulation/raceSimulation.js +2140 -171
  45. package/src/simulation/rules/collisionSteward.js +69 -0
  46. package/src/simulation/rules/penaltyLedger.js +153 -0
  47. package/src/simulation/rules/pitLaneSpeedingSteward.js +71 -0
  48. package/src/simulation/rules/tireRequirementSteward.js +29 -0
  49. package/src/simulation/rules/trackLimitsSteward.js +85 -0
  50. package/src/simulation/rulesConfig.js +352 -0
  51. package/src/simulation/trackModel.js +1154 -76
  52. package/src/simulation/units.js +6 -4
  53. package/src/simulation/vehicleGeometry.js +247 -0
  54. package/src/simulation/vehiclePhysics.js +82 -50
  55. package/src/simulation/wheelSurface.js +338 -0
  56. package/src/styles.css +369 -67
  57. package/src/ui/bannerTemplates.js +69 -0
  58. package/src/ui/componentTemplates.js +45 -81
  59. package/src/ui/raceControlStatusBanner.js +39 -0
  60. package/src/ui/shellTemplate.js +1 -1
  61. package/src/ui/templateUtils.js +23 -0
  62. package/AGENTS.md +0 -86
  63. package/src/standalone.js +0 -15
@@ -0,0 +1,69 @@
1
+ import { createLoadingMarkup } from './templateUtils.js';
2
+
3
+ export function createStewardMessageMarkup() {
4
+ return `
5
+ <div class="steward-message is-hidden" data-paddock-component="steward-message" data-steward-message aria-live="polite">
6
+ <span class="steward-message__kicker" data-steward-message-kicker>Race control</span>
7
+ <strong data-steward-message-title>--</strong>
8
+ <span data-steward-message-detail>--</span>
9
+ </div>
10
+ `;
11
+ }
12
+
13
+ export function createRaceDataPanelMarkup({ ui = {} } = {}) {
14
+ const sizeMode = ui.raceDataBannerSize === 'auto' ? 'auto' : 'custom';
15
+ const telemetryDetail = Boolean(ui.raceDataTelemetryDetail);
16
+ const classNames = ['race-data-panel', `race-data-panel--${sizeMode}`];
17
+ if (telemetryDetail) classNames.push('race-data-panel--with-telemetry');
18
+ return `
19
+ <div class="${classNames.join(' ')}" data-paddock-component="race-data-panel" data-race-data-panel aria-live="polite">
20
+ <button class="race-data-dismiss" type="button" data-race-data-dismiss aria-label="Close race data pill">x</button>
21
+ <div class="race-data-copy">
22
+ <span class="race-data-kicker" data-race-data-kicker>Project</span>
23
+ <strong data-race-data-title>Select driver</strong>
24
+ <span class="race-data-subtitle" data-race-data-subtitle>Race entry</span>
25
+ </div>
26
+ ${telemetryDetail ? createRaceDataTelemetryMarkup() : ''}
27
+ <strong class="race-data-number" data-race-data-number>--</strong>
28
+ <button class="race-data-link" type="button" data-race-data-open>Open project</button>
29
+ ${createLoadingMarkup('Race data')}
30
+ </div>
31
+ `;
32
+ }
33
+
34
+ function createRaceDataTelemetryMarkup() {
35
+ return `
36
+ <div class="race-data-telemetry" data-race-data-telemetry aria-label="Project sector telemetry">
37
+ <span class="race-data-telemetry__label">Sectors</span>
38
+ <div class="race-data-telemetry__bars">
39
+ ${[1, 2, 3].map((sector) => `
40
+ <div class="telemetry-sector-bar race-data-sector-bar" data-telemetry-sector-bar="${sector}" style="--sector-fill: 0%">
41
+ <span>S${sector}</span>
42
+ <strong data-telemetry-sector-time="${sector}">--</strong>
43
+ </div>
44
+ `).join('')}
45
+ </div>
46
+ </div>
47
+ `;
48
+ }
49
+
50
+ export function createTelemetrySectorBannerMarkup() {
51
+ return `
52
+ <section class="telemetry-sector-banner" data-paddock-component="telemetry-sector-banner" data-telemetry-sector-banner aria-label="Broadcast sector telemetry">
53
+ <div class="telemetry-sector-banner__copy">
54
+ <span><b data-selected-code>--</b> sector telemetry</span>
55
+ <strong data-selected-name>Select driver</strong>
56
+ <em data-telemetry-current-sector>S1</em>
57
+ </div>
58
+ <div class="telemetry-sector-banner__bars">
59
+ ${[1, 2, 3].map((sector) => `
60
+ <div class="telemetry-sector-bar" data-telemetry-sector-bar="${sector}" style="--sector-fill: 0%">
61
+ <span>S${sector}</span>
62
+ <strong data-telemetry-sector-time="${sector}">--</strong>
63
+ </div>
64
+ `).join('')}
65
+ </div>
66
+ ${createLoadingMarkup('Sector banner')}
67
+ </section>
68
+ `;
69
+ }
@@ -1,3 +1,18 @@
1
+ import {
2
+ createRaceDataPanelMarkup,
3
+ createStewardMessageMarkup,
4
+ createTelemetrySectorBannerMarkup,
5
+ } from './bannerTemplates.js';
6
+ import { createRaceControlStatusBannerMarkup } from './raceControlStatusBanner.js';
7
+ import { createLoadingMarkup, escapeHtml } from './templateUtils.js';
8
+
9
+ export {
10
+ createRaceDataPanelMarkup,
11
+ createStewardMessageMarkup,
12
+ createTelemetrySectorBannerMarkup,
13
+ } from './bannerTemplates.js';
14
+ export { escapeHtml } from './templateUtils.js';
15
+
1
16
  function buttonHiddenAttribute(isVisible) {
2
17
  return isVisible ? '' : ' hidden';
3
18
  }
@@ -9,30 +24,6 @@ function createTelemetryDrawerId() {
9
24
  return `paddock-telemetry-drawer-${telemetryDrawerIdSequence}`;
10
25
  }
11
26
 
12
- function createLoadingMarkup(label = 'Loading') {
13
- return `
14
- <div class="paddock-loading" data-paddock-loading aria-label="${escapeHtml(label)} loading">
15
- <div class="paddock-loading__lights" aria-hidden="true">
16
- <span></span>
17
- <span></span>
18
- <span></span>
19
- <span></span>
20
- <span></span>
21
- </div>
22
- <span class="paddock-loading__label">${escapeHtml(label)}</span>
23
- </div>
24
- `;
25
- }
26
-
27
- export function escapeHtml(value) {
28
- return String(value ?? '')
29
- .replaceAll('&', '&amp;')
30
- .replaceAll('<', '&lt;')
31
- .replaceAll('>', '&gt;')
32
- .replaceAll('"', '&quot;')
33
- .replaceAll("'", '&#39;');
34
- }
35
-
36
27
  export function createRaceControlsMarkup({
37
28
  title,
38
29
  kicker,
@@ -81,10 +72,7 @@ export function createTimingTowerMarkup({ totalLaps, assets }) {
81
72
  <button type="button" data-timing-gap-mode="interval" aria-pressed="true">Int</button>
82
73
  <button type="button" data-timing-gap-mode="leader" aria-pressed="false">Gap</button>
83
74
  </div>
84
- <div class="broadcast-safety-banner" data-tower-safety-banner>
85
- <span>FIA</span>
86
- <strong>Safety Car</strong>
87
- </div>
75
+ ${createRaceControlStatusBannerMarkup()}
88
76
  <div class="broadcast-column-head" aria-hidden="true">
89
77
  <span>Pos</span>
90
78
  <span>Team</span>
@@ -99,16 +87,20 @@ export function createTimingTowerMarkup({ totalLaps, assets }) {
99
87
  `;
100
88
  }
101
89
 
102
- export function createCameraControlsMarkup({ embedded = false } = {}) {
90
+ export function createCameraControlsMarkup({ embedded = false, showSimulationSpeed = false, ui = {} } = {}) {
103
91
  const className = embedded ? 'camera-controls' : 'camera-controls camera-controls--external';
92
+ const showSpeedControl = showSimulationSpeed || ui.simulationSpeedControl === true;
104
93
  return `
105
94
  <div class="${className}" data-paddock-component="camera-controls" aria-label="Camera controls">
106
95
  <button type="button" data-camera-mode="overview" aria-pressed="false">Overview</button>
107
96
  <button type="button" data-camera-mode="leader" aria-pressed="true">Leader</button>
108
97
  <button type="button" data-camera-mode="selected" aria-pressed="false">Selected</button>
109
98
  <button type="button" data-camera-mode="show-all" aria-pressed="false">Show all</button>
99
+ <button type="button" data-camera-mode="pit" aria-pressed="false">Pits</button>
110
100
  <button type="button" data-zoom-out aria-label="Zoom out">-</button>
111
101
  <button type="button" data-zoom-in aria-label="Zoom in">+</button>
102
+ ${showSpeedControl ? '<button type="button" data-simulation-speed aria-label="Simulation speed">1x</button>' : ''}
103
+ <button type="button" data-race-data-banners-muted aria-pressed="false">Mute banners</button>
112
104
  ${createLoadingMarkup('Camera controls')}
113
105
  </div>
114
106
  `;
@@ -124,7 +116,7 @@ export function createRaceCanvasMarkup({
124
116
  ui = {},
125
117
  } = {}) {
126
118
  const showFps = ui.showFps !== false;
127
- const showEmbeddedCameraControls = ui.cameraControls !== 'external' && ui.cameraControls !== false;
119
+ const showEmbeddedCameraControls = ui.cameraControls === 'embedded';
128
120
  const timingFit = (timingTowerVerticalFit ?? ui.timingTowerVerticalFit) === 'scroll'
129
121
  ? 'scroll'
130
122
  : 'expand-race-view';
@@ -153,7 +145,8 @@ export function createRaceCanvasMarkup({
153
145
  <span></span>
154
146
  </div>
155
147
  </div>
156
- ${showEmbeddedCameraControls ? createCameraControlsMarkup({ embedded: true }) : ''}
148
+ ${createStewardMessageMarkup()}
149
+ ${showEmbeddedCameraControls ? createCameraControlsMarkup({ embedded: true, ui }) : ''}
157
150
  ${includeRaceDataPanel ? createRaceDataPanelMarkup({ assets, ui }) : ''}
158
151
  ${includeTelemetrySectorBanner ? createTelemetrySectorBannerMarkup({ ui }) : ''}
159
152
  <div class="race-finish-panel" data-race-finish-panel hidden aria-live="polite">
@@ -166,22 +159,6 @@ export function createRaceCanvasMarkup({
166
159
  `;
167
160
  }
168
161
 
169
- export function createRaceDataPanelMarkup({ ui = {} } = {}) {
170
- const sizeMode = ui.raceDataBannerSize === 'auto' ? 'auto' : 'custom';
171
- return `
172
- <div class="race-data-panel race-data-panel--${sizeMode}" data-paddock-component="race-data-panel" data-race-data-panel aria-live="polite">
173
- <div class="race-data-copy">
174
- <span class="race-data-kicker" data-race-data-kicker>Project</span>
175
- <strong data-race-data-title>Select driver</strong>
176
- <span class="race-data-subtitle" data-race-data-subtitle>Race entry</span>
177
- </div>
178
- <strong class="race-data-number" data-race-data-number>--</strong>
179
- <button class="race-data-link" type="button" data-race-data-open>Open project</button>
180
- ${createLoadingMarkup('Race data')}
181
- </div>
182
- `;
183
- }
184
-
185
162
  export function createCarDriverOverviewMarkup({ assets }) {
186
163
  const cells = Array.from({ length: 7 }, (_, index) => `
187
164
  <div class="car-overview-cell car-overview-cell--slot-${index + 1}" data-overview-field data-overview-slot="${index}">
@@ -280,27 +257,6 @@ export function createTelemetrySectorsMarkup() {
280
257
  `;
281
258
  }
282
259
 
283
- export function createTelemetrySectorBannerMarkup() {
284
- return `
285
- <section class="telemetry-sector-banner" data-paddock-component="telemetry-sector-banner" data-telemetry-sector-banner aria-label="Broadcast sector telemetry">
286
- <div class="telemetry-sector-banner__copy">
287
- <span><b data-selected-code>--</b> sector telemetry</span>
288
- <strong data-selected-name>Select driver</strong>
289
- <em data-telemetry-current-sector>S1</em>
290
- </div>
291
- <div class="telemetry-sector-banner__bars">
292
- ${[1, 2, 3].map((sector) => `
293
- <div class="telemetry-sector-bar" data-telemetry-sector-bar="${sector}" style="--sector-fill: 0%">
294
- <span>S${sector}</span>
295
- <strong data-telemetry-sector-time="${sector}">--</strong>
296
- </div>
297
- `).join('')}
298
- </div>
299
- ${createLoadingMarkup('Sector banner')}
300
- </section>
301
- `;
302
- }
303
-
304
260
  export function createTelemetryLapTimesMarkup() {
305
261
  return `
306
262
  <section class="${getTelemetryModuleClass('lap-times')} telemetry-lap-module" data-paddock-component="telemetry-lap-times" aria-label="Lap timing">
@@ -368,33 +324,41 @@ export function createTelemetryPanelMarkup(options, { includeOverview = options.
368
324
  export function createRaceTelemetryDrawerMarkup(options, {
369
325
  timingTowerVerticalFit,
370
326
  drawerInitiallyOpen = false,
327
+ raceDataTelemetryDetail = options.ui?.raceDataTelemetryDetail,
371
328
  } = {}) {
372
329
  const openClass = drawerInitiallyOpen ? ' is-telemetry-open' : '';
373
330
  const drawerId = createTelemetryDrawerId();
331
+ const drawerOptions = {
332
+ ...options,
333
+ ui: {
334
+ ...(options.ui ?? {}),
335
+ cameraControls: false,
336
+ raceDataTelemetryDetail: Boolean(raceDataTelemetryDetail),
337
+ },
338
+ };
339
+ const showCameraControls = options.ui?.cameraControls !== false;
374
340
  return `
375
341
  <section class="race-telemetry-drawer${openClass}" data-paddock-component="race-telemetry-drawer" data-race-telemetry-drawer aria-label="Race view with telemetry drawer">
342
+ <div class="race-telemetry-drawer__toolbar" aria-label="Race workbench controls">
343
+ ${showCameraControls ? createCameraControlsMarkup({ showSimulationSpeed: true, ui: options.ui }) : ''}
344
+ <div class="race-telemetry-drawer__controls">
345
+ ${createSafetyCarControlMarkup({ compact: true })}
346
+ <button class="telemetry-drawer-toggle" type="button" data-telemetry-drawer-toggle aria-expanded="${drawerInitiallyOpen ? 'true' : 'false'}" aria-controls="${drawerId}">
347
+ ${drawerInitiallyOpen ? 'Close telemetry' : 'Telemetry'}
348
+ </button>
349
+ </div>
350
+ </div>
376
351
  <div class="race-telemetry-drawer__race">
377
352
  ${createRaceCanvasMarkup({
378
- ...options,
353
+ ...drawerOptions,
379
354
  includeRaceDataPanel: true,
380
355
  includeTimingTower: true,
381
- includeTelemetrySectorBanner: true,
382
356
  timingTowerVerticalFit,
383
357
  })}
384
358
  </div>
385
- <div class="race-telemetry-drawer__controls" aria-label="Race workbench controls">
386
- ${createSafetyCarControlMarkup({ compact: true })}
387
- <button class="telemetry-drawer-toggle" type="button" data-telemetry-drawer-toggle aria-expanded="${drawerInitiallyOpen ? 'true' : 'false'}" aria-controls="${drawerId}">
388
- ${drawerInitiallyOpen ? 'Close telemetry' : 'Telemetry'}
389
- </button>
390
- </div>
391
359
  <aside id="${drawerId}" class="telemetry-drawer" data-telemetry-drawer aria-label="Telemetry drawer" aria-hidden="${drawerInitiallyOpen ? 'false' : 'true'}"${drawerInitiallyOpen ? '' : ' inert'}>
392
- <div class="telemetry-drawer__header">
393
- <span>Live telemetry</span>
394
- <button type="button" data-telemetry-drawer-close>Close</button>
395
- </div>
396
360
  <div class="telemetry-drawer__content">
397
- ${createTelemetryComponentMarkup(options)}
361
+ ${createTelemetryPanelMarkup(options, { includeOverview: false })}
398
362
  </div>
399
363
  </aside>
400
364
  </section>
@@ -0,0 +1,39 @@
1
+ import { escapeHtml } from './templateUtils.js';
2
+
3
+ export const RACE_CONTROL_STATUS_BANNERS = {
4
+ 'safety-car': {
5
+ status: 'safety-car',
6
+ className: 'is-safety-car',
7
+ kicker: 'FIA',
8
+ title: 'Safety Car',
9
+ },
10
+ 'red-flag': {
11
+ status: 'red-flag',
12
+ className: 'is-red-flag',
13
+ kicker: 'FIA',
14
+ title: 'Red Flag',
15
+ },
16
+ };
17
+
18
+ export function getRaceControlStatusBanner(mode) {
19
+ return RACE_CONTROL_STATUS_BANNERS[mode] ?? null;
20
+ }
21
+
22
+ export function createRaceControlStatusBannerMarkup({ status = null } = {}) {
23
+ const banner = getRaceControlStatusBanner(status);
24
+ const className = [
25
+ 'broadcast-race-control-banner',
26
+ banner?.className,
27
+ ].filter(Boolean).join(' ');
28
+ const hiddenAttribute = banner ? '' : ' hidden';
29
+ const statusAttribute = banner ? ` data-race-control-status="${escapeHtml(banner.status)}"` : '';
30
+ const kicker = banner?.kicker ?? 'FIA';
31
+ const title = banner?.title ?? 'Race Control';
32
+
33
+ return `
34
+ <div class="${className}" data-tower-race-control-banner${statusAttribute}${hiddenAttribute}>
35
+ <span data-tower-race-control-kicker>${escapeHtml(kicker)}</span>
36
+ <strong data-tower-race-control-title>${escapeHtml(title)}</strong>
37
+ </div>
38
+ `;
39
+ }
@@ -24,7 +24,7 @@ export function createF1SimulatorShell({
24
24
  shellClasses.push(`sim-shell--timing-${timingFit}`);
25
25
  shellClasses.push(`sim-shell--race-data-${raceDataBannerSize}`);
26
26
  const cameraControls = ui.cameraControls === 'external'
27
- ? `<div class="sim-external-camera-controls">${createCameraControlsMarkup()}</div>`
27
+ ? `<div class="sim-external-camera-controls">${createCameraControlsMarkup({ ui })}</div>`
28
28
  : '';
29
29
 
30
30
  return `
@@ -0,0 +1,23 @@
1
+ export function escapeHtml(value) {
2
+ return String(value ?? '')
3
+ .replaceAll('&', '&amp;')
4
+ .replaceAll('<', '&lt;')
5
+ .replaceAll('>', '&gt;')
6
+ .replaceAll('"', '&quot;')
7
+ .replaceAll("'", '&#39;');
8
+ }
9
+
10
+ export function createLoadingMarkup(label = 'Loading') {
11
+ return `
12
+ <div class="paddock-loading" data-paddock-loading aria-label="${escapeHtml(label)} loading">
13
+ <div class="paddock-loading__lights" aria-hidden="true">
14
+ <span></span>
15
+ <span></span>
16
+ <span></span>
17
+ <span></span>
18
+ <span></span>
19
+ </div>
20
+ <span class="paddock-loading__label">${escapeHtml(label)}</span>
21
+ </div>
22
+ `;
23
+ }
package/AGENTS.md DELETED
@@ -1,86 +0,0 @@
1
- # AGENTS.md
2
-
3
- ## Project
4
-
5
- PaddockJS is an installable browser component package for an F1-style project race simulator.
6
-
7
- The package lives independently from the portfolio website. Host websites install it and mount it with:
8
-
9
- ```js
10
- import { mountF1Simulator } from '@inventure71/paddockjs';
11
- ```
12
-
13
- It can also be mounted as rearrangeable package-owned pieces with:
14
-
15
- ```js
16
- import { createPaddockSimulator } from '@inventure71/paddockjs';
17
- ```
18
-
19
- ## What Is In This Repo
20
-
21
- - `src/index.js`: public package API.
22
- - `src/api/`: public composable mounting controller.
23
- - `src/app/`: browser runtime, DOM bindings, PixiJS app lifecycle, controls, readouts, and camera orchestration.
24
- - `src/config/`: default options and bundled default asset mapping.
25
- - `src/data/`: driver/project data normalization, driver/car pairing, rating conversion, and demo data.
26
- - `src/rendering/`: PixiJS track rendering helpers and render interpolation.
27
- - `src/simulation/`: race rules, driver AI, vehicle physics, track model, and math utilities.
28
- - `src/ui/`: generated markup for the all-in-one shell and individually mounted UI surfaces.
29
- - `src/__tests__/`: package tests.
30
- - `assets/`: simulator-owned default assets.
31
- - `docs/`: package specs and design documentation.
32
- - `INSTALL_AND_UPDATE.md`: install/update workflow.
33
-
34
- ## Required Documentation Discipline
35
-
36
- When changing simulator behavior, update docs in the same change.
37
-
38
- Use this mapping:
39
-
40
- - Public API, lifecycle, install assumptions: update `docs/system_specs.md` and `README.md` if needed.
41
- - Race behavior, DRS, safety car, starts, physics, collisions: update `docs/rules.md`.
42
- - Vocabulary or domain model: update `docs/concepts.md`.
43
- - Driver/entry/options/assets callback shapes: update `docs/data_contract.md`.
44
- - Module ownership or data/control flow: update `docs/architecture.md`.
45
- - Local install/update workflow: update `INSTALL_AND_UPDATE.md`.
46
-
47
- Do not leave docs describing old behavior after code changes.
48
-
49
- ## Linear Work Logging
50
-
51
- For any implementation that is more than a trivial/no-op change, log the work in the PaddockJS project on Linear.
52
-
53
- The Linear update must include:
54
-
55
- - what task was done
56
- - how it was solved
57
- - any important verification or remaining follow-up
58
-
59
- ## Engineering Rules
60
-
61
- - Preserve the package boundary: PaddockJS should stay reusable and host-agnostic.
62
- - Host-specific routing belongs in `onDriverOpen(driver)`, not package internals.
63
- - Host-specific project data should be passed as `drivers` and `entries`, not hardcoded into the runtime.
64
- - The package should own default simulator assets so hosts do not need to copy them.
65
- - Treat the timing board width and broadcast proportions as package-owned. Do not resize or retune timing-board width as a casual fix; preserve its intended width and solve layout bugs through internal column/content constraints unless the user explicitly asks to redesign the timing board sizing.
66
- - Prefer small, focused modules when extracting from `F1SimulatorApp.js`.
67
- - Keep simulation logic deterministic for the same seed, track seed, drivers, entries, and rules.
68
- - Do not claim completion without running verification.
69
-
70
- ## Verification
71
-
72
- Run before claiming package changes are complete:
73
-
74
- ```bash
75
- npm run check
76
- ```
77
-
78
- For portfolio integration changes, also run from the portfolio repo:
79
-
80
- ```bash
81
- cd /Users/inventure71/VSProjects/Inventure71.github.io
82
- npm install ../PaddockJS
83
- npm run check
84
- ```
85
-
86
- Browser behavior changes need a browser smoke test against the host page.
package/src/standalone.js DELETED
@@ -1,15 +0,0 @@
1
- import { DEMO_PROJECT_DRIVERS, mountF1Simulator } from './index.js';
2
-
3
- const root = document.getElementById('f1-simulator-root');
4
-
5
- if (root) {
6
- mountF1Simulator(root, {
7
- drivers: DEMO_PROJECT_DRIVERS,
8
- onDriverOpen(driver) {
9
- if (driver.link) window.location.href = driver.link;
10
- },
11
- }).catch((error) => {
12
- root.dataset.simulatorState = 'error';
13
- console.error('F1 simulator failed to initialize.', error);
14
- });
15
- }