@inventure71/paddockjs 4.0.0 → 4.2.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 (46) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/INSTALL_AND_UPDATE.md +14 -1
  3. package/README.md +33 -1
  4. package/docs/architecture.md +3 -1
  5. package/docs/component_inventory.md +5 -4
  6. package/docs/composable-layouts.md +2 -0
  7. package/docs/data_contract.md +5 -1
  8. package/docs/getting-started.md +33 -0
  9. package/docs/index.md +4 -2
  10. package/docs/loading.md +141 -0
  11. package/docs/system_specs.md +15 -2
  12. package/docs/testing-reliability-guide.md +62 -0
  13. package/package.json +16 -9
  14. package/src/data/index.d.ts +64 -1
  15. package/src/environment/index.d.ts +75 -12
  16. package/src/index.d.ts +72 -6
  17. package/src/placeholder/index.d.ts +11 -0
  18. package/src/placeholder/index.js +61 -0
  19. package/src/placeholder.css +72 -0
  20. package/src/simulation/collisionGeometry.js +7 -9
  21. package/src/simulation/driver/driverController.js +2 -2
  22. package/src/simulation/driver/edgeRecovery.js +2 -2
  23. package/src/simulation/driver/racingControls.js +35 -25
  24. package/src/simulation/driver/racingLinePlan.js +6 -5
  25. package/src/simulation/driver/rejoinControls.js +3 -3
  26. package/src/simulation/driver/trafficScan.js +48 -22
  27. package/src/simulation/participants/participantInteractions.js +24 -15
  28. package/src/simulation/race/raceStep.js +19 -10
  29. package/src/simulation/race/safetyCar.js +2 -1
  30. package/src/simulation/rules/pitLaneSpeedingSteward.js +1 -1
  31. package/src/simulation/rules/rulesReview.js +19 -9
  32. package/src/simulation/rules/trackLimitsSteward.js +2 -2
  33. package/src/simulation/timing/sectorPerformance.js +11 -3
  34. package/src/simulation/track/spatialQueries.js +80 -11
  35. package/src/simulation/track/trackModel.js +10 -1
  36. package/src/simulation/vehicle/contactResolution.js +4 -4
  37. package/src/simulation/vehicle/vehicleGeometry.js +87 -49
  38. package/src/simulation/vehicle/vehiclePhysics.js +7 -2
  39. package/src/simulation/vehicle/vehicleSnapshots.js +5 -2
  40. package/src/simulation/vehicle/vehicleState.js +3 -2
  41. package/src/ui/raceCanvasTemplate.js +3 -2
  42. package/src/ui/raceControlsTemplate.js +1 -0
  43. package/src/ui/telemetryDrawerTemplate.js +1 -0
  44. package/src/ui/telemetryTemplates.js +1 -0
  45. package/examples/train-basic-policy.mjs +0 -276
  46. package/examples/trainingData.mjs +0 -133
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inventure71/paddockjs",
3
- "version": "4.0.0",
3
+ "version": "4.2.0",
4
4
  "type": "module",
5
5
  "description": "A data-driven browser F1 simulator component for host websites.",
6
6
  "license": "Apache-2.0",
@@ -24,7 +24,8 @@
24
24
  "ui-component"
25
25
  ],
26
26
  "sideEffects": [
27
- "./src/styles.css"
27
+ "./src/styles.css",
28
+ "./src/placeholder.css"
28
29
  ],
29
30
  "publishConfig": {
30
31
  "access": "public"
@@ -50,7 +51,13 @@
50
51
  "import": "./src/data/index.js",
51
52
  "default": "./src/data/index.js"
52
53
  },
53
- "./styles.css": "./src/styles.css"
54
+ "./placeholder": {
55
+ "types": "./src/placeholder/index.d.ts",
56
+ "import": "./src/placeholder/index.js",
57
+ "default": "./src/placeholder/index.js"
58
+ },
59
+ "./styles.css": "./src/styles.css",
60
+ "./placeholder.css": "./src/placeholder.css"
54
61
  },
55
62
  "files": [
56
63
  "CHANGELOG.md",
@@ -61,19 +68,19 @@
61
68
  "examples/python/README.md",
62
69
  "examples/python/base_policy_server.py",
63
70
  "examples/python/requirements.txt",
64
- "examples/train-basic-policy.mjs",
65
- "examples/trainingData.mjs",
66
71
  "src/api",
67
72
  "src/app",
68
73
  "src/config",
69
74
  "src/data",
70
75
  "src/environment",
76
+ "src/placeholder",
71
77
  "src/rendering",
72
78
  "src/simulation",
73
79
  "src/ui",
74
80
  "src/index.d.ts",
75
81
  "src/index.js",
76
82
  "src/styles.css",
83
+ "src/placeholder.css",
77
84
  "README.md"
78
85
  ],
79
86
  "scripts": {
@@ -81,10 +88,11 @@
81
88
  "test:fast": "node scripts/run-vitest.mjs",
82
89
  "types:check": "tsc -p tsconfig.types.json --noEmit",
83
90
  "docs:check": "node scripts/docs-check.mjs",
84
- "check": "npm run docs:check && npm run test:fast && npm run types:check && npm run pack:dry && npm run consumer:smoke && npm run showcase:ci && npm run browser:smoke:quick -- --skip-build",
85
- "check:release": "npm run docs:check && npm test && npm run types:check && npm run pack:dry && npm run consumer:smoke && npm run showcase:ci && npm run browser:smoke:full -- --skip-build",
91
+ "check": "npm run docs:check && npm run test:fast && npm run types:check && npm run pack:dry && npm run consumer:smoke && npm run consumer:bundle-boundaries && npm run showcase:ci && npm run browser:smoke:quick -- --skip-build",
92
+ "check:release": "npm run docs:check && npm test && npm run types:check && npm run pack:dry && npm run consumer:smoke && npm run consumer:bundle-boundaries && npm run showcase:ci && npm run browser:smoke:full -- --skip-build",
86
93
  "pack:dry": "npm pack --dry-run",
87
94
  "consumer:smoke": "node scripts/consumer-package-smoke.mjs",
95
+ "consumer:bundle-boundaries": "node scripts/consumer-bundle-boundaries.mjs",
88
96
  "browser:smoke": "node smoke/browser-smoke.mjs",
89
97
  "browser:smoke:quick": "node smoke/browser-smoke.mjs --quick",
90
98
  "browser:smoke:full": "node smoke/browser-smoke.mjs --full",
@@ -95,8 +103,7 @@
95
103
  "showcase:ci": "npm --prefix local-preview ci && npm --prefix local-preview run build",
96
104
  "changeset": "changeset",
97
105
  "version-packages": "changeset version",
98
- "release": "npm publish --provenance",
99
- "example:train": "node examples/train-basic-policy.mjs"
106
+ "release": "npm publish --provenance"
100
107
  },
101
108
  "dependencies": {
102
109
  "pixi.js": "8.18.1"
@@ -38,6 +38,69 @@ export interface PaddockProceduralTrackOptions {
38
38
  };
39
39
  }
40
40
 
41
+ export interface PaddockTrackPoint {
42
+ x: number;
43
+ y: number;
44
+ }
45
+
46
+ export interface PaddockDrsZone {
47
+ id: string;
48
+ startRatio: number;
49
+ endRatio: number;
50
+ }
51
+
52
+ export interface PaddockResolvedProceduralTrackOptions {
53
+ profile: PaddockProceduralTrackProfile;
54
+ length: {
55
+ min: number;
56
+ max: number;
57
+ };
58
+ startStraight: {
59
+ grid: number;
60
+ exit: number;
61
+ blend: number;
62
+ lockExtra: number;
63
+ };
64
+ pitLane: {
65
+ enabled: boolean;
66
+ };
67
+ shape: {
68
+ scale: number;
69
+ cornerDensity: number;
70
+ variation: number;
71
+ };
72
+ validation: {
73
+ minClearanceMultiplier: number;
74
+ minShapeVariation: number;
75
+ minNonAdjacentArcDistance: number;
76
+ maxLocalTurnRadians: number;
77
+ maxSampleHeadingDeltaRadians: number;
78
+ };
79
+ attempts: {
80
+ primary: number;
81
+ fallback: number;
82
+ };
83
+ cacheKey: string;
84
+ [key: string]: unknown;
85
+ }
86
+
87
+ export interface PaddockProceduralTrack {
88
+ name: string;
89
+ width: number;
90
+ kerbWidth: number;
91
+ gravelWidth: number;
92
+ runoffWidth: number;
93
+ barrierWidth: number;
94
+ sampleCount: number;
95
+ seed?: number;
96
+ curveInterpolation?: string;
97
+ centerlineControls?: readonly PaddockTrackPoint[];
98
+ drsZones?: readonly PaddockDrsZone[] | null;
99
+ pitLane?: { enabled?: boolean } | null;
100
+ generationOptions?: PaddockResolvedProceduralTrackOptions;
101
+ [key: string]: unknown;
102
+ }
103
+
41
104
  export interface CustomField {
42
105
  label: string;
43
106
  value: string;
@@ -207,6 +270,6 @@ export function normalizeSimulatorDrivers(
207
270
  drivers: SimulatorDriver[],
208
271
  options?: { entries?: ChampionshipEntryBlueprint[]; caller?: string },
209
272
  ): NormalizedSimulatorDriver[];
210
- export function createProceduralTrack(seed?: number | string, options?: PaddockProceduralTrackOptions): unknown;
273
+ export function createProceduralTrack(seed?: number | string, options?: PaddockProceduralTrackOptions): PaddockProceduralTrack;
211
274
  export function kphToSimSpeed(kph: number): number;
212
275
  export function simSpeedToKph(simSpeed: number): number;
@@ -39,6 +39,69 @@ export interface PaddockProceduralTrackOptions {
39
39
  };
40
40
  }
41
41
 
42
+ export interface PaddockTrackPoint {
43
+ x: number;
44
+ y: number;
45
+ }
46
+
47
+ export interface PaddockDrsZone {
48
+ id: string;
49
+ startRatio: number;
50
+ endRatio: number;
51
+ }
52
+
53
+ export interface PaddockResolvedProceduralTrackOptions {
54
+ profile: PaddockProceduralTrackProfile;
55
+ length: {
56
+ min: number;
57
+ max: number;
58
+ };
59
+ startStraight: {
60
+ grid: number;
61
+ exit: number;
62
+ blend: number;
63
+ lockExtra: number;
64
+ };
65
+ pitLane: {
66
+ enabled: boolean;
67
+ };
68
+ shape: {
69
+ scale: number;
70
+ cornerDensity: number;
71
+ variation: number;
72
+ };
73
+ validation: {
74
+ minClearanceMultiplier: number;
75
+ minShapeVariation: number;
76
+ minNonAdjacentArcDistance: number;
77
+ maxLocalTurnRadians: number;
78
+ maxSampleHeadingDeltaRadians: number;
79
+ };
80
+ attempts: {
81
+ primary: number;
82
+ fallback: number;
83
+ };
84
+ cacheKey: string;
85
+ [key: string]: unknown;
86
+ }
87
+
88
+ export interface PaddockProceduralTrack {
89
+ name: string;
90
+ width: number;
91
+ kerbWidth: number;
92
+ gravelWidth: number;
93
+ runoffWidth: number;
94
+ barrierWidth: number;
95
+ sampleCount: number;
96
+ seed?: number;
97
+ curveInterpolation?: string;
98
+ centerlineControls?: readonly PaddockTrackPoint[];
99
+ drsZones?: readonly PaddockDrsZone[] | null;
100
+ pitLane?: { enabled?: boolean } | null;
101
+ generationOptions?: PaddockResolvedProceduralTrackOptions;
102
+ [key: string]: unknown;
103
+ }
104
+
42
105
  export type PaddockStabilityState = 'stable' | 'understeer' | 'oversteer' | 'spin-risk' | 'destroyed';
43
106
  export type PaddockPitIntent = 0 | 1 | 2;
44
107
  export type PaddockScenarioPreset = 'cornering' | 'off-track-recovery' | 'overtaking-pack' | 'pit-entry';
@@ -674,8 +737,8 @@ export interface PaddockDriverControllerContext {
674
737
  controlledDrivers: string[];
675
738
  actionSpec: PaddockActionSpec;
676
739
  observationSpec: PaddockObservationSpec;
677
- result: PaddockEnvironmentResult | unknown;
678
- previousResult: PaddockEnvironmentResult | unknown | null;
740
+ result: PaddockEnvironmentResult;
741
+ previousResult: PaddockEnvironmentResult | null;
679
742
  observation: Record<string, PaddockDriverObservation>;
680
743
  orderedObservations: Array<{
681
744
  driverId: string;
@@ -685,7 +748,7 @@ export interface PaddockDriverControllerContext {
685
748
  }>;
686
749
  metrics: Record<string, PaddockEnvironmentDriverMetrics>;
687
750
  events: RaceEvent[];
688
- info: PaddockEnvironmentResult['info'] | unknown | null;
751
+ info: PaddockEnvironmentResult['info'] | null;
689
752
  previousActions: PaddockActionMap;
690
753
  orderedPreviousActions: Array<PaddockAction | null>;
691
754
  actions: PaddockActionMap | null;
@@ -721,14 +784,14 @@ export interface PaddockDriverController {
721
784
  }
722
785
 
723
786
  export interface PaddockControllerRuntime {
724
- reset(options?: unknown): PaddockEnvironmentResult | unknown;
725
- step(actions: PaddockActionMap): PaddockEnvironmentResult | unknown;
787
+ reset(options?: unknown): PaddockEnvironmentResult;
788
+ step(actions: PaddockActionMap): PaddockEnvironmentResult;
726
789
  resetDrivers?(
727
790
  placements: Record<string, PaddockScenarioPlacement>,
728
791
  resultOptions?: PaddockEnvironmentResultOptions
729
- ): PaddockEnvironmentResult | unknown;
792
+ ): PaddockEnvironmentResult;
730
793
  getObservation(): Record<string, PaddockDriverObservation>;
731
- getState?(options?: { output?: 'full' | 'minimal' | 'none' }): PaddockEnvironmentResult['state'] | unknown;
794
+ getState?(options?: { output?: 'full' | 'minimal' | 'none' }): PaddockEnvironmentResult['state'];
732
795
  getActionSpec(): PaddockActionSpec;
733
796
  getObservationSpec(): PaddockObservationSpec;
734
797
  destroy?(): void;
@@ -743,7 +806,7 @@ export interface PaddockDriverControllerLoopOptions {
743
806
  }
744
807
 
745
808
  export interface PaddockDriverControllerLoop {
746
- readonly result: PaddockEnvironmentResult | unknown | null;
809
+ readonly result: PaddockEnvironmentResult | null;
747
810
  readonly actionSpec: PaddockActionSpec | null;
748
811
  readonly observationSpec: PaddockObservationSpec | null;
749
812
  readonly stats: {
@@ -756,13 +819,13 @@ export interface PaddockDriverControllerLoop {
756
819
  running: boolean;
757
820
  lastError: unknown | null;
758
821
  };
759
- reset(options?: unknown): Promise<PaddockEnvironmentResult | unknown>;
822
+ reset(options?: unknown): Promise<PaddockEnvironmentResult>;
760
823
  resetDrivers(
761
824
  placements: Record<string, PaddockScenarioPlacement>,
762
825
  resultOptions?: PaddockEnvironmentResultOptions
763
- ): Promise<PaddockEnvironmentResult | unknown>;
764
- step(): Promise<PaddockEnvironmentResult | unknown>;
765
- stepFrame(): Promise<PaddockEnvironmentResult | unknown>;
826
+ ): Promise<PaddockEnvironmentResult>;
827
+ step(): Promise<PaddockEnvironmentResult>;
828
+ stepFrame(): Promise<PaddockEnvironmentResult>;
766
829
  start(): void;
767
830
  stop(): void;
768
831
  }
package/src/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import type {
2
2
  PaddockActionSpec,
3
3
  PaddockDriverControllerLoop,
4
4
  PaddockDriverControllerLoopOptions,
5
+ PaddockEnvironmentResult,
5
6
  PaddockEnvironmentOptions,
6
7
  PaddockObservationSpec,
7
8
  PaddockParticipantInteraction as EnvPaddockParticipantInteraction,
@@ -20,6 +21,8 @@ export type {
20
21
  PaddockDriverControllerContext,
21
22
  PaddockDriverControllerLoop,
22
23
  PaddockDriverControllerLoopOptions,
24
+ PaddockEnvironmentResult,
25
+ PaddockEnvironmentSnapshotResult,
23
26
  PaddockObservationSpec,
24
27
  } from './environment/index.js';
25
28
 
@@ -71,6 +74,69 @@ export interface PaddockProceduralTrackOptions {
71
74
  };
72
75
  }
73
76
 
77
+ export interface PaddockTrackPoint {
78
+ x: number;
79
+ y: number;
80
+ }
81
+
82
+ export interface PaddockDrsZone {
83
+ id: string;
84
+ startRatio: number;
85
+ endRatio: number;
86
+ }
87
+
88
+ export interface PaddockResolvedProceduralTrackOptions {
89
+ profile: PaddockProceduralTrackProfile;
90
+ length: {
91
+ min: number;
92
+ max: number;
93
+ };
94
+ startStraight: {
95
+ grid: number;
96
+ exit: number;
97
+ blend: number;
98
+ lockExtra: number;
99
+ };
100
+ pitLane: {
101
+ enabled: boolean;
102
+ };
103
+ shape: {
104
+ scale: number;
105
+ cornerDensity: number;
106
+ variation: number;
107
+ };
108
+ validation: {
109
+ minClearanceMultiplier: number;
110
+ minShapeVariation: number;
111
+ minNonAdjacentArcDistance: number;
112
+ maxLocalTurnRadians: number;
113
+ maxSampleHeadingDeltaRadians: number;
114
+ };
115
+ attempts: {
116
+ primary: number;
117
+ fallback: number;
118
+ };
119
+ cacheKey: string;
120
+ [key: string]: unknown;
121
+ }
122
+
123
+ export interface PaddockProceduralTrack {
124
+ name: string;
125
+ width: number;
126
+ kerbWidth: number;
127
+ gravelWidth: number;
128
+ runoffWidth: number;
129
+ barrierWidth: number;
130
+ sampleCount: number;
131
+ seed?: number;
132
+ curveInterpolation?: string;
133
+ centerlineControls?: readonly PaddockTrackPoint[];
134
+ drsZones?: readonly PaddockDrsZone[] | null;
135
+ pitLane?: { enabled?: boolean } | null;
136
+ generationOptions?: PaddockResolvedProceduralTrackOptions;
137
+ [key: string]: unknown;
138
+ }
139
+
74
140
  export type PaddockStabilityState = 'stable' | 'understeer' | 'oversteer' | 'spin-risk' | 'destroyed';
75
141
  export type PaddockPitIntent = 0 | 1 | 2;
76
142
  export type PaddockPitIntentRequest = PaddockPitIntent | {
@@ -959,14 +1025,14 @@ export interface PaddockEnvironmentResultOptions {
959
1025
  }
960
1026
 
961
1027
  export interface F1SimulatorExpertApi {
962
- reset(options?: unknown): unknown;
963
- step(actions: Record<string, F1SimulatorExpertAction>): unknown;
1028
+ reset(options?: unknown): PaddockEnvironmentResult;
1029
+ step(actions: Record<string, F1SimulatorExpertAction>): PaddockEnvironmentResult;
964
1030
  resetDrivers?(
965
1031
  placements: Record<string, PaddockScenarioPlacement>,
966
1032
  resultOptions?: PaddockEnvironmentResultOptions
967
- ): unknown;
968
- getObservation(): unknown;
969
- getState(): unknown;
1033
+ ): PaddockEnvironmentResult;
1034
+ getObservation(): PaddockEnvironmentResult['observation'];
1035
+ getState(): PaddockEnvironmentResult['state'];
970
1036
  getActionSpec(): PaddockActionSpec;
971
1037
  getObservationSpec(): PaddockObservationSpec;
972
1038
  attachExternalRenderer(source: PaddockExternalRendererSource): void;
@@ -1133,7 +1199,7 @@ export function simSpeedToMetersPerSecond(simSpeed: number): number;
1133
1199
 
1134
1200
  export function createPaddockSimulator(options: F1SimulatorOptions): PaddockSimulatorController;
1135
1201
  export function mountF1Simulator(root: Element, options: F1SimulatorOptions): Promise<F1MountedSimulator>;
1136
- export function createProceduralTrack(seed?: number | string, options?: PaddockProceduralTrackOptions): unknown;
1202
+ export function createProceduralTrack(seed?: number | string, options?: PaddockProceduralTrackOptions): PaddockProceduralTrack;
1137
1203
  export function createPaddockDriverControllerLoop(options: PaddockDriverControllerLoopOptions): PaddockDriverControllerLoop;
1138
1204
 
1139
1205
  export function mountRaceControls<T extends Element>(root: T, simulator: PaddockSimulatorController): T;
@@ -0,0 +1,11 @@
1
+ export type PaddockLoadingPlaceholderVariant = 'lights' | 'custom';
2
+
3
+ export interface PaddockLoadingPlaceholderOptions {
4
+ label?: string;
5
+ detail?: string;
6
+ className?: string;
7
+ variant?: PaddockLoadingPlaceholderVariant;
8
+ attributes?: Record<string, string | number | boolean | null | undefined>;
9
+ }
10
+
11
+ export function createPaddockLoadingPlaceholder(options?: PaddockLoadingPlaceholderOptions): string;
@@ -0,0 +1,61 @@
1
+ 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
+ function normalizeVariant(value) {
11
+ return value === 'custom' ? 'custom' : 'lights';
12
+ }
13
+
14
+ function renderAttributes(attributes = {}) {
15
+ if (!attributes || typeof attributes !== 'object') return '';
16
+ return Object.entries(attributes)
17
+ .filter(([name, value]) => (
18
+ value != null &&
19
+ value !== false &&
20
+ /^[a-zA-Z_][\w:.-]*$/.test(name) &&
21
+ !/^on/i.test(name)
22
+ ))
23
+ .map(([name, value]) => (value === true
24
+ ? ` ${escapeHtml(name)}`
25
+ : ` ${escapeHtml(name)}="${escapeHtml(value)}"`))
26
+ .join('');
27
+ }
28
+
29
+ export function createPaddockLoadingPlaceholder({
30
+ label = 'Loading simulator',
31
+ detail = '',
32
+ className = '',
33
+ variant = 'lights',
34
+ attributes,
35
+ } = {}) {
36
+ const normalizedVariant = normalizeVariant(variant);
37
+ const extraClassName = className ? ` ${escapeHtml(className)}` : '';
38
+ const detailMarkup = detail
39
+ ? `<span class="paddock-placeholder__detail">${escapeHtml(detail)}</span>`
40
+ : '';
41
+ const lightsMarkup = normalizedVariant === 'lights'
42
+ ? `
43
+ <div class="paddock-placeholder__lights" aria-hidden="true">
44
+ <span></span>
45
+ <span></span>
46
+ <span></span>
47
+ <span></span>
48
+ <span></span>
49
+ </div>`
50
+ : '';
51
+
52
+ return `
53
+ <div class="paddock-placeholder${extraClassName}" data-paddock-placeholder data-paddock-placeholder-variant="${normalizedVariant}" role="status" aria-live="polite" aria-busy="true"${renderAttributes(attributes)}>
54
+ <div class="paddock-placeholder__content">
55
+ ${lightsMarkup}
56
+ <span class="paddock-placeholder__label">${escapeHtml(label)}</span>
57
+ ${detailMarkup}
58
+ </div>
59
+ </div>
60
+ `;
61
+ }
@@ -0,0 +1,72 @@
1
+ .paddock-placeholder {
2
+ --paddock-placeholder-background: #000;
3
+ --paddock-placeholder-color: rgba(244, 241, 234, 0.78);
4
+ --paddock-placeholder-light: #ff1f18;
5
+ --paddock-placeholder-light-dim: #8f0804;
6
+ --paddock-placeholder-min-height: 420px;
7
+ position: relative;
8
+ display: grid;
9
+ min-height: var(--paddock-placeholder-min-height);
10
+ place-items: center;
11
+ overflow: hidden;
12
+ color: var(--paddock-placeholder-color);
13
+ background: var(--paddock-placeholder-background);
14
+ }
15
+
16
+ .paddock-placeholder__content {
17
+ display: grid;
18
+ justify-items: center;
19
+ gap: 0.8rem;
20
+ padding: 1.25rem;
21
+ text-align: center;
22
+ }
23
+
24
+ .paddock-placeholder__label {
25
+ font: 800 0.72rem/1.2 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
26
+ letter-spacing: 0.14em;
27
+ text-transform: uppercase;
28
+ }
29
+
30
+ .paddock-placeholder__detail {
31
+ max-width: 32rem;
32
+ font: 500 0.82rem/1.4 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
33
+ opacity: 0.72;
34
+ }
35
+
36
+ .paddock-placeholder__lights {
37
+ display: grid;
38
+ grid-template-columns: repeat(5, 0.78rem);
39
+ gap: 0.42rem;
40
+ padding: 0.32rem 0.46rem;
41
+ border-radius: 999px;
42
+ background: rgba(255, 255, 255, 0.08);
43
+ }
44
+
45
+ .paddock-placeholder__lights span {
46
+ width: 0.78rem;
47
+ height: 0.78rem;
48
+ border-radius: 50%;
49
+ background: var(--paddock-placeholder-light-dim);
50
+ box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
51
+ animation: paddock-placeholder-lights 1200ms cubic-bezier(0.45, 0, 0.2, 1) infinite;
52
+ }
53
+
54
+ .paddock-placeholder__lights span:nth-child(2) { animation-delay: 120ms; }
55
+ .paddock-placeholder__lights span:nth-child(3) { animation-delay: 240ms; }
56
+ .paddock-placeholder__lights span:nth-child(4) { animation-delay: 360ms; }
57
+ .paddock-placeholder__lights span:nth-child(5) { animation-delay: 480ms; }
58
+
59
+ .paddock-placeholder[data-paddock-placeholder-variant="custom"] .paddock-placeholder__content {
60
+ min-height: inherit;
61
+ }
62
+
63
+ @keyframes paddock-placeholder-lights {
64
+ 0%, 100% {
65
+ background: var(--paddock-placeholder-light-dim);
66
+ box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
67
+ }
68
+ 45%, 70% {
69
+ background: var(--paddock-placeholder-light);
70
+ box-shadow: 0 0 14px rgba(225, 6, 0, 0.56);
71
+ }
72
+ }
@@ -1,7 +1,4 @@
1
- import {
2
- getVehicleGeometryState,
3
- vehicleAxes,
4
- } from './vehicle/vehicleGeometry.js';
1
+ import { getVehicleGeometryState } from './vehicle/vehicleGeometry.js';
5
2
  import { normalizeAngle } from './simMath.js';
6
3
 
7
4
  const DEFAULT_SWEEP_STEPS = 16;
@@ -340,17 +337,18 @@ function writeInterpolatedBodyShape(target, geometryState, amount) {
340
337
  const x = pose.previousX + (pose.x - pose.previousX) * amount;
341
338
  const y = pose.previousY + (pose.y - pose.previousY) * amount;
342
339
  const heading = normalizeAngle(pose.previousHeading + headingDelta * amount);
343
- const axes = vehicleAxes(heading);
340
+ const cos = Math.cos(heading);
341
+ const sin = Math.sin(heading);
344
342
  const halfLength = target.halfLength;
345
343
  const halfWidth = target.halfWidth;
346
344
 
347
345
  target.center.x = x;
348
346
  target.center.y = y;
349
347
  target.heading = heading;
350
- target.forward.x = axes.forward.x;
351
- target.forward.y = axes.forward.y;
352
- target.right.x = axes.right.x;
353
- target.right.y = axes.right.y;
348
+ target.forward.x = cos;
349
+ target.forward.y = sin;
350
+ target.right.x = -sin;
351
+ target.right.y = cos;
354
352
 
355
353
  writeOffsetCorner(target.corners[0], target.center, target.forward, target.right, halfLength, halfWidth);
356
354
  writeOffsetCorner(target.corners[1], target.center, target.forward, target.right, halfLength, -halfWidth);
@@ -4,7 +4,7 @@ import { decideRacingControls } from './racingControls.js';
4
4
  import { decideRejoinControls } from './rejoinControls.js';
5
5
  import { shouldContinueRejoinRecovery } from './edgeRecovery.js';
6
6
  import { decideSafetyCarControls } from './safetyCarControls.js';
7
- import { VEHICLE_LIMITS } from '../vehicle/vehiclePhysics.js';
7
+ import { VEHICLE_LIMITS, isSimulatorPhysicsMode } from '../vehicle/vehiclePhysics.js';
8
8
 
9
9
  export function decideDriverControls({ car, orderIndex, race }) {
10
10
  if (car.manualControls) return car.manualControls;
@@ -37,7 +37,7 @@ export function decideDriverControls({ car, orderIndex, race }) {
37
37
  }
38
38
 
39
39
  function shouldStabilizeSimulatorCar(car, race) {
40
- if (race.physicsMode !== 'advanced') return false;
40
+ if (!isSimulatorPhysicsMode(race.physicsMode)) return false;
41
41
  const gripUsage = car.gripUsage ?? 0;
42
42
  const slipAngle = Math.abs(car.slipAngleRadians ?? 0);
43
43
  const edgeDistance = race.track.width / 2 - VEHICLE_LIMITS.carWidth * 2.25;
@@ -1,11 +1,11 @@
1
1
  import { clamp, normalizeAngle } from '../simMath.js';
2
2
  import { metersToSimUnits } from '../units.js';
3
3
  import { pointAt } from '../track/trackModel.js';
4
- import { VEHICLE_LIMITS } from '../vehicle/vehiclePhysics.js';
4
+ import { VEHICLE_LIMITS, isSimulatorPhysicsMode } from '../vehicle/vehiclePhysics.js';
5
5
  import { REJOIN_LOOKAHEAD_BASE, REJOIN_LOOKAHEAD_MAX } from './driverControlConstants.js';
6
6
 
7
7
  export function calculateTrackEdgeGuard(car, race) {
8
- const simulatorMode = race.physicsMode === 'advanced';
8
+ const simulatorMode = isSimulatorPhysicsMode(race.physicsMode);
9
9
  const trackLimit = race.track.width / 2;
10
10
  const halfCarWidth = VEHICLE_LIMITS.carWidth / 2;
11
11
  const wholeCarOutsideLimit = trackLimit + halfCarWidth;