@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.
- package/CHANGELOG.md +6 -0
- package/INSTALL_AND_UPDATE.md +163 -0
- package/README.md +80 -16
- package/docs/architecture.md +41 -6
- package/docs/data_contract.md +241 -7
- package/docs/data_model.md +1 -0
- package/docs/index.md +1 -0
- package/docs/system_specs.md +43 -12
- package/docs/training.md +104 -0
- package/examples/train-basic-policy.mjs +277 -0
- package/package.json +14 -7
- package/src/api/PaddockSimulatorController.js +7 -2
- package/src/app/BrowserExpertAdapter.js +32 -0
- package/src/app/F1SimulatorApp.js +152 -20
- package/src/app/domBindings.js +0 -1
- package/src/config/defaultOptions.js +8 -0
- package/src/data/championship.js +13 -0
- package/src/data/normalizeDrivers.js +7 -2
- package/src/environment/actions.js +45 -0
- package/src/environment/episode.js +17 -0
- package/src/environment/events.js +17 -0
- package/src/environment/index.d.ts +241 -0
- package/src/environment/index.js +2 -0
- package/src/environment/observations.js +112 -0
- package/src/environment/options.js +150 -0
- package/src/environment/rewards.js +69 -0
- package/src/environment/runtime.js +140 -0
- package/src/environment/sensors.js +257 -0
- package/src/environment/specs.js +79 -0
- package/src/index.d.ts +40 -4
- package/src/index.js +6 -2
- package/src/rendering/proceduralTrackAsset.js +7 -1
- package/src/simulation/raceSimulation.js +9 -1
- package/src/styles.css +71 -27
- package/src/ui/componentTemplates.js +31 -7
- package/AGENTS.md +0 -86
- package/src/standalone.js +0 -15
|
@@ -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
|
-
|
|
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="
|
|
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
|
-
...
|
|
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
|
-
}
|