@inventure71/paddockjs 0.2.0 → 0.3.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.
@@ -13,6 +13,8 @@ import { simSpeedToKph, simUnitsToMeters } from './units.js';
13
13
  import { getCarCorners, integrateVehiclePhysics, VEHICLE_LIMITS } from './vehiclePhysics.js';
14
14
 
15
15
  const DEFAULT_TOTAL_LAPS = 10;
16
+ export const FIXED_STEP = 1 / 60;
17
+ const MIN_TOTAL_LAPS = 1;
16
18
  const MAX_COLLISION_CORRECTION = 4.5;
17
19
  const GRID_SLOT_SPACING = 82;
18
20
  const GRID_FIRST_SLOT_DISTANCE = -42;
@@ -329,6 +331,12 @@ function createCar(driver, index, random, track, { standingStart = false } = {})
329
331
  };
330
332
  }
331
333
 
334
+ function normalizeTotalLaps(value) {
335
+ const numeric = Number(value);
336
+ if (!Number.isFinite(numeric)) return MIN_TOTAL_LAPS;
337
+ return Math.max(MIN_TOTAL_LAPS, Math.floor(numeric));
338
+ }
339
+
332
340
  function projectOntoAxis(points, axis) {
333
341
  let min = Infinity;
334
342
  let max = -Infinity;
@@ -572,7 +580,7 @@ export class F1RaceSimulation {
572
580
  this.trackSeed = this.track.seed ?? trackSeed;
573
581
  this.rules = { ...DEFAULT_RULES, ...rules };
574
582
  this.startLightsOutAt = this.rules.startLightCount * this.rules.startLightInterval + this.rules.startLightsOutHold;
575
- this.totalLaps = totalLaps;
583
+ this.totalLaps = normalizeTotalLaps(totalLaps);
576
584
  this.time = 0;
577
585
  this.events = [];
578
586
  this.raceControl = {
package/src/styles.css CHANGED
@@ -741,6 +741,7 @@
741
741
  position: relative;
742
742
  display: block;
743
743
  container-type: inline-size;
744
+ inline-size: 100%;
744
745
  min-height: var(--paddock-race-view-min-height, 620px);
745
746
  overflow: hidden;
746
747
  background: #000;
@@ -825,34 +826,8 @@
825
826
  }
826
827
 
827
828
  .telemetry-drawer__header {
828
- display: flex;
829
- align-items: center;
830
- justify-content: space-between;
831
- gap: 0.75rem;
832
- padding: 0.8rem 0.85rem;
829
+ min-height: 3.4rem;
833
830
  border-bottom: 1px solid var(--line);
834
- text-transform: uppercase;
835
- }
836
-
837
- .telemetry-drawer__header span {
838
- color: var(--green);
839
- font-family: var(--font-mono);
840
- font-size: 0.72rem;
841
- font-weight: 900;
842
- letter-spacing: 0.16em;
843
- }
844
-
845
- .telemetry-drawer__header button {
846
- border: 1px solid rgba(255, 255, 255, 0.18);
847
- color: var(--text);
848
- background: rgba(255, 255, 255, 0.06);
849
- font-family: var(--font-mono);
850
- font-size: 0.62rem;
851
- font-weight: 900;
852
- letter-spacing: 0.1em;
853
- text-transform: uppercase;
854
- padding: 0.45rem 0.54rem;
855
- cursor: pointer;
856
831
  }
857
832
 
858
833
  .telemetry-drawer__content {
@@ -1283,6 +1258,55 @@
1283
1258
  transform: translateX(-8px);
1284
1259
  }
1285
1260
 
1261
+ .race-data-panel--with-telemetry {
1262
+ grid-template-columns: clamp(6.5rem, 11vw, 8.7rem) minmax(220px, 1fr) minmax(190px, 0.48fr) auto;
1263
+ min-height: 112px;
1264
+ }
1265
+
1266
+ .race-data-telemetry {
1267
+ position: relative;
1268
+ z-index: 1;
1269
+ grid-column: 3;
1270
+ grid-row: 1;
1271
+ align-self: stretch;
1272
+ display: grid;
1273
+ grid-template-rows: auto minmax(0, 1fr);
1274
+ gap: 0.4rem;
1275
+ min-width: 0;
1276
+ padding: 0.85rem 0.65rem;
1277
+ border-left: 1px solid rgba(255, 255, 255, 0.13);
1278
+ }
1279
+
1280
+ .race-data-telemetry__label {
1281
+ color: var(--driver-color, var(--red));
1282
+ font-family: var(--font-mono);
1283
+ font-size: 0.58rem;
1284
+ font-weight: 900;
1285
+ letter-spacing: 0.14em;
1286
+ line-height: 1;
1287
+ text-transform: uppercase;
1288
+ }
1289
+
1290
+ .race-data-telemetry__bars {
1291
+ display: grid;
1292
+ grid-template-columns: repeat(3, minmax(0, 1fr));
1293
+ gap: 0.28rem;
1294
+ min-width: 0;
1295
+ }
1296
+
1297
+ .race-data-sector-bar {
1298
+ min-height: 3.85rem;
1299
+ padding: 0.52rem 0.42rem;
1300
+ }
1301
+
1302
+ .race-data-panel--with-telemetry .race-data-link {
1303
+ grid-column: 4;
1304
+ }
1305
+
1306
+ .race-data-panel.is-radio-mode .race-data-telemetry {
1307
+ display: none;
1308
+ }
1309
+
1286
1310
  .race-data-panel.is-radio-mode {
1287
1311
  grid-template-columns: minmax(280px, 1fr);
1288
1312
  }
@@ -2135,11 +2159,31 @@
2135
2159
  grid-template-columns: minmax(0, 1fr);
2136
2160
  }
2137
2161
 
2162
+ .race-data-panel--with-telemetry {
2163
+ grid-template-columns: minmax(4.8rem, auto) minmax(0, 1fr);
2164
+ }
2165
+
2138
2166
  .race-data-copy {
2139
2167
  grid-column: 2;
2140
2168
  padding: 0.78rem 0.95rem 0.85rem;
2141
2169
  }
2142
2170
 
2171
+ .race-data-panel--with-telemetry .race-data-copy {
2172
+ grid-column: 2;
2173
+ padding-bottom: 0.35rem;
2174
+ }
2175
+
2176
+ .race-data-telemetry {
2177
+ grid-column: 1 / -1;
2178
+ grid-row: 2;
2179
+ padding: 0 0.95rem 0.75rem;
2180
+ border-left: 0;
2181
+ }
2182
+
2183
+ .race-data-panel--with-telemetry .race-data-link {
2184
+ grid-column: 1 / -1;
2185
+ }
2186
+
2143
2187
  .race-data-panel.is-radio-mode .race-data-copy {
2144
2188
  grid-column: 1;
2145
2189
  padding-left: 0.95rem;
@@ -168,13 +168,17 @@ export function createRaceCanvasMarkup({
168
168
 
169
169
  export function createRaceDataPanelMarkup({ ui = {} } = {}) {
170
170
  const sizeMode = ui.raceDataBannerSize === 'auto' ? 'auto' : 'custom';
171
+ const telemetryDetail = Boolean(ui.raceDataTelemetryDetail);
172
+ const classNames = ['race-data-panel', `race-data-panel--${sizeMode}`];
173
+ if (telemetryDetail) classNames.push('race-data-panel--with-telemetry');
171
174
  return `
172
- <div class="race-data-panel race-data-panel--${sizeMode}" data-paddock-component="race-data-panel" data-race-data-panel aria-live="polite">
175
+ <div class="${classNames.join(' ')}" data-paddock-component="race-data-panel" data-race-data-panel aria-live="polite">
173
176
  <div class="race-data-copy">
174
177
  <span class="race-data-kicker" data-race-data-kicker>Project</span>
175
178
  <strong data-race-data-title>Select driver</strong>
176
179
  <span class="race-data-subtitle" data-race-data-subtitle>Race entry</span>
177
180
  </div>
181
+ ${telemetryDetail ? createRaceDataTelemetryMarkup() : ''}
178
182
  <strong class="race-data-number" data-race-data-number>--</strong>
179
183
  <button class="race-data-link" type="button" data-race-data-open>Open project</button>
180
184
  ${createLoadingMarkup('Race data')}
@@ -182,6 +186,22 @@ export function createRaceDataPanelMarkup({ ui = {} } = {}) {
182
186
  `;
183
187
  }
184
188
 
189
+ function createRaceDataTelemetryMarkup() {
190
+ return `
191
+ <div class="race-data-telemetry" data-race-data-telemetry aria-label="Project sector telemetry">
192
+ <span class="race-data-telemetry__label">Sectors</span>
193
+ <div class="race-data-telemetry__bars">
194
+ ${[1, 2, 3].map((sector) => `
195
+ <div class="telemetry-sector-bar race-data-sector-bar" data-telemetry-sector-bar="${sector}" style="--sector-fill: 0%">
196
+ <span>S${sector}</span>
197
+ <strong data-telemetry-sector-time="${sector}">--</strong>
198
+ </div>
199
+ `).join('')}
200
+ </div>
201
+ </div>
202
+ `;
203
+ }
204
+
185
205
  export function createCarDriverOverviewMarkup({ assets }) {
186
206
  const cells = Array.from({ length: 7 }, (_, index) => `
187
207
  <div class="car-overview-cell car-overview-cell--slot-${index + 1}" data-overview-field data-overview-slot="${index}">
@@ -368,17 +388,24 @@ export function createTelemetryPanelMarkup(options, { includeOverview = options.
368
388
  export function createRaceTelemetryDrawerMarkup(options, {
369
389
  timingTowerVerticalFit,
370
390
  drawerInitiallyOpen = false,
391
+ raceDataTelemetryDetail = options.ui?.raceDataTelemetryDetail,
371
392
  } = {}) {
372
393
  const openClass = drawerInitiallyOpen ? ' is-telemetry-open' : '';
373
394
  const drawerId = createTelemetryDrawerId();
395
+ const drawerOptions = {
396
+ ...options,
397
+ ui: {
398
+ ...(options.ui ?? {}),
399
+ raceDataTelemetryDetail: Boolean(raceDataTelemetryDetail),
400
+ },
401
+ };
374
402
  return `
375
403
  <section class="race-telemetry-drawer${openClass}" data-paddock-component="race-telemetry-drawer" data-race-telemetry-drawer aria-label="Race view with telemetry drawer">
376
404
  <div class="race-telemetry-drawer__race">
377
405
  ${createRaceCanvasMarkup({
378
- ...options,
406
+ ...drawerOptions,
379
407
  includeRaceDataPanel: true,
380
408
  includeTimingTower: true,
381
- includeTelemetrySectorBanner: true,
382
409
  timingTowerVerticalFit,
383
410
  })}
384
411
  </div>
@@ -389,10 +416,7 @@ export function createRaceTelemetryDrawerMarkup(options, {
389
416
  </button>
390
417
  </div>
391
418
  <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>
419
+ <div class="telemetry-drawer__header" aria-hidden="true"></div>
396
420
  <div class="telemetry-drawer__content">
397
421
  ${createTelemetryComponentMarkup(options)}
398
422
  </div>
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
- }