@inventure71/paddockjs 4.1.0 → 4.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 +76 -0
- package/INSTALL_AND_UPDATE.md +31 -5
- package/README.md +34 -3
- package/docs/advanced_ai_pace.md +131 -0
- package/docs/advanced_physics_rebuild.md +166 -0
- package/docs/architecture.md +111 -30
- package/docs/cleanup_coverage.md +284 -0
- package/docs/component_inventory.md +32 -16
- package/docs/composable-layouts.md +2 -0
- package/docs/concepts.md +12 -1
- package/docs/custom_model_controller.md +1 -1
- package/docs/data_contract.md +33 -10
- package/docs/data_model.md +4 -2
- package/docs/efficiency_cleanup_2026-10-13.md +80 -0
- package/docs/getting-started.md +3 -0
- package/docs/grid_walk.md +80 -0
- package/docs/index.md +5 -2
- package/docs/keyboard_driving_fix.md +118 -0
- package/docs/limit_driving_2026-10-14.md +88 -0
- package/docs/loading.md +141 -0
- package/docs/maintainability_audit_2026-09-08.md +146 -0
- package/docs/racing_ai_improvements.md +145 -0
- package/docs/rules.md +43 -13
- package/docs/sense_contract.md +4 -1
- package/docs/system_specs.md +40 -12
- package/docs/testing-reliability-guide.md +67 -0
- package/docs/v11_improvements.md +108 -0
- package/package.json +23 -15
- package/src/api/MountedSimulatorComponents.js +101 -0
- package/src/api/PaddockSimulatorController.js +56 -81
- package/src/app/BrowserExpertAdapter.js +0 -6
- package/src/app/BrowserExpertExternalRenderer.js +72 -50
- package/src/app/ExternalTrackSnapshot.js +50 -0
- package/src/app/F1SimulatorApp.js +242 -297
- package/src/app/HostLifecycleEmitter.js +93 -0
- package/src/app/banners/raceDataBannerController.js +9 -4
- package/src/app/camera/cameraConstants.js +1 -1
- package/src/app/camera/cameraController.js +10 -13
- package/src/app/camera/cameraGeometry.js +1 -1
- package/src/app/controls/SimulatorViewControls.js +45 -0
- package/src/app/controls/workbenchBindings.js +64 -0
- package/src/app/controls/workbenchDisclosure.js +41 -0
- package/src/app/domBindings.js +21 -13
- package/src/app/gridWalk/GridWalkController.js +180 -0
- package/src/app/gridWalk/gridWalkCamera.js +88 -0
- package/src/app/gridWalk/gridWalkControls.js +24 -0
- package/src/app/gridWalk/gridWalkCrew.js +246 -0
- package/src/app/gridWalk/gridWalkLabels.js +104 -0
- package/src/app/gridWalk/gridWalkLayout.js +46 -0
- package/src/app/gridWalk/gridWalkNavigation.js +99 -0
- package/src/app/gridWalk/gridWalkProjectBanner.js +43 -0
- package/src/app/gridWalk/gridWalkSession.js +50 -0
- package/src/app/layoutSupport.js +2 -0
- package/src/app/raceOverlayClearanceSupport.js +21 -7
- package/src/app/readouts/carOverviewRenderer.js +9 -4
- package/src/app/readouts/readoutFormatters.js +1 -8
- package/src/app/readouts/telemetryRenderer.js +1 -3
- package/src/app/readouts/timingTowerRenderer.js +48 -50
- package/src/app/rendering/appAssets.js +8 -10
- package/src/app/rendering/carRenderer.js +27 -79
- package/src/app/rendering/displayUtils.js +0 -6
- package/src/app/rendering/expertSensorRenderer.js +1 -1
- package/src/app/rendering/replayGhostRenderer.js +4 -1
- package/src/app/rendering/trackRenderer.js +2 -2
- package/src/app/runtime/frameLoop.js +18 -17
- package/src/app/runtime/pixiStartupLifecycle.js +94 -0
- package/src/app/runtime/runtimeTiming.js +4 -1
- package/src/app/runtime/runtimeVisibility.js +7 -2
- package/src/config/cssValues.js +1 -1
- package/src/config/defaultOptions.js +12 -173
- package/src/config/restartOptions.js +1 -0
- package/src/config/runtimeSeed.js +10 -0
- package/src/config/simulatorDefaults.js +106 -0
- package/src/config/telemetryModules.js +20 -0
- package/src/config/theme/cssTokens.js +85 -0
- package/src/config/theme/themeCss.js +72 -0
- package/src/config/theme/themeInputs.js +155 -0
- package/src/config/theme/themeResolver.js +172 -0
- package/src/config/theme/themeSelectors.js +69 -0
- package/src/config/theme/themeTokens.js +257 -0
- package/src/config/theme/themeUtils.js +15 -0
- package/src/config/themeOptions.js +14 -830
- package/src/config/timingGapMode.js +0 -2
- package/src/config/uiOptions.js +53 -0
- package/src/data/championship.js +16 -158
- package/src/data/championshipDefaults.js +87 -0
- package/src/data/driverData.js +10 -20
- package/src/data/entryIdentity.js +67 -0
- package/src/data/entryPresentation.js +23 -0
- package/src/data/entryTypes.d.ts +160 -0
- package/src/data/index.d.ts +12 -195
- package/src/data/ratingScale.js +14 -0
- package/src/data/teamTypes.d.ts +23 -0
- package/src/data/trackTypes.d.ts +101 -0
- package/src/data/vehicleData.js +12 -23
- package/src/environment/actions.js +3 -2
- package/src/environment/controllerLoop.js +57 -44
- package/src/environment/controllerScheduler.js +30 -0
- package/src/environment/driverObservations.js +260 -0
- package/src/environment/driverReset.js +51 -0
- package/src/environment/episode.js +0 -1
- package/src/environment/evaluation.js +6 -90
- package/src/environment/evaluationTracker.js +100 -0
- package/src/environment/events.js +23 -0
- package/src/environment/index.d.ts +35 -91
- package/src/environment/metrics.js +3 -17
- package/src/environment/observationSchema.js +104 -0
- package/src/environment/observationVector.js +16 -115
- package/src/environment/observations.js +16 -316
- package/src/environment/options.js +5 -24
- package/src/environment/renderFrames.js +21 -0
- package/src/environment/resetOptions.js +41 -0
- package/src/environment/results.js +125 -0
- package/src/environment/rewards.js +10 -16
- package/src/environment/runtime.js +17 -300
- package/src/environment/scenarioOptions.js +90 -0
- package/src/environment/scenarioPlacements.js +70 -0
- package/src/environment/scenarioPresets.js +78 -0
- package/src/environment/scenarios.js +5 -236
- package/src/environment/scratch.js +18 -0
- package/src/environment/sensorOptions.js +20 -0
- package/src/environment/sensors/index.js +2 -556
- package/src/environment/sensors/indexedRayBands.js +0 -18
- package/src/environment/sensors/nearbyCars.js +18 -15
- package/src/environment/sensors/opponentRadar.js +3 -2
- package/src/environment/sensors/pitConnectorProximity.js +20 -0
- package/src/environment/sensors/rayBandTrace.js +1 -37
- package/src/environment/sensors/rayBatch.js +81 -0
- package/src/environment/sensors/rayConfig.js +26 -0
- package/src/environment/sensors/rayGeometry.js +0 -8
- package/src/environment/sensors/rayGuards.js +0 -6
- package/src/environment/sensors/rayOutputs.js +75 -0
- package/src/environment/sensors/rayQuery.js +110 -0
- package/src/environment/sensors/rayScratch.js +146 -0
- package/src/environment/sensors/sensorTargets.js +12 -4
- package/src/environment/sensors/surfaceRays.js +6 -61
- package/src/environment/sensors/trackRays.js +7 -80
- package/src/environment/simulationSetup.js +30 -0
- package/src/environment/specs.js +15 -109
- package/src/index.d.ts +43 -202
- package/src/index.js +28 -6
- package/src/placeholder/index.js +1 -8
- package/src/rendering/displayLifecycle.js +6 -0
- package/src/rendering/proceduralTrackAsset.js +1 -1
- package/src/rendering/renderSnapshot.js +3 -0
- package/src/rendering/track/finishLineRenderer.js +1 -1
- package/src/rendering/track/gridRenderer.js +1 -1
- package/src/rendering/track/offsetStrokeSafety.js +2 -2
- package/src/rendering/track/pitLaneRenderer.js +1 -1
- package/src/rendering/track/trackMaterialRenderer.js +4 -4
- package/src/rendering/track/trackRenderConstants.js +0 -1
- package/src/rendering/track/trackRenderGeometry.js +1 -7
- package/src/simulation/collisionGeometry.js +12 -20
- package/src/simulation/driver/advancedPathControls.js +53 -0
- package/src/simulation/driver/advancedRacePace.js +112 -0
- package/src/simulation/driver/advancedRacingControls.js +61 -0
- package/src/simulation/driver/advancedRacingEnvelope.js +255 -0
- package/src/simulation/driver/advancedRacingSteering.js +76 -0
- package/src/simulation/driver/advancedRacingTiming.js +16 -0
- package/src/simulation/driver/arcadeRacePace.js +83 -0
- package/src/simulation/driver/arcadeRacingControls.js +111 -0
- package/src/simulation/driver/arcadeRacingEnvelope.js +63 -0
- package/src/simulation/driver/driverControlConstants.js +0 -2
- package/src/simulation/driver/driverController.js +16 -5
- package/src/simulation/driver/edgeRecovery.js +2 -3
- package/src/simulation/driver/racingControls.js +10 -396
- package/src/simulation/driver/racingDriverProfile.js +48 -0
- package/src/simulation/driver/racingLinePlan.js +54 -20
- package/src/simulation/driver/rejoinControls.js +59 -112
- package/src/simulation/driver/safetyCarControls.js +12 -0
- package/src/simulation/driver/trafficScan.js +136 -27
- package/src/simulation/participants/participantInteractions.js +24 -15
- package/src/simulation/pit/pitCarPlacement.js +29 -0
- package/src/simulation/pit/pitFlow.js +8 -30
- package/src/simulation/pit/pitIntent.js +4 -3
- package/src/simulation/pit/pitOccupancy.js +2 -2
- package/src/simulation/pit/pitQueue.js +2 -18
- package/src/simulation/pit/pitRouteMovement.js +1 -36
- package/src/simulation/pit/pitRouting.js +5 -81
- package/src/simulation/pit/pitService.js +3 -32
- package/src/simulation/pit/pitServiceConstants.js +1 -0
- package/src/simulation/pit/pitServiceProgress.js +34 -0
- package/src/simulation/pit/pitSnapshots.js +8 -4
- package/src/simulation/pit/pitState.js +1 -5
- package/src/simulation/race/classification.js +1 -8
- package/src/simulation/race/raceFinish.js +3 -8
- package/src/simulation/race/raceOrder.js +2 -13
- package/src/simulation/race/raceProgress.js +6 -20
- package/src/simulation/race/raceSetup.js +1 -1
- package/src/simulation/race/raceStep.js +20 -11
- package/src/simulation/race/redFlag.js +2 -4
- package/src/simulation/race/safetyCar.js +5 -9
- package/src/simulation/race/stalledDnf.js +2 -4
- package/src/simulation/raceSimulation.js +6 -38
- package/src/simulation/replay/replayGhosts.js +44 -32
- package/src/simulation/rules/collisionSteward.js +0 -14
- package/src/simulation/rules/penaltyActions.js +37 -0
- package/src/simulation/rules/penaltyLedger.js +8 -1
- package/src/simulation/rules/pitLaneSpeedingSteward.js +1 -1
- package/src/simulation/rules/rulesReview.js +20 -16
- package/src/simulation/rules/trackLimitsSteward.js +2 -2
- package/src/simulation/simMath.js +4 -0
- package/src/simulation/snapshots/raceSnapshots.js +30 -11
- package/src/simulation/timing/drsState.js +8 -0
- package/src/simulation/timing/drsTiming.js +2 -4
- package/src/simulation/timing/lapTelemetry.js +7 -4
- package/src/simulation/timing/sectorPerformance.js +14 -6
- package/src/simulation/timing/timingLines.js +12 -6
- package/src/simulation/track/pitLaneAccess.js +2 -2
- package/src/simulation/track/pitLaneLayout.js +4 -135
- package/src/simulation/track/pitLaneModel.js +70 -0
- package/src/simulation/track/pitLaneState.js +0 -1
- package/src/simulation/track/pitServiceLayout.js +121 -0
- package/src/simulation/track/proceduralTrack.js +77 -0
- package/src/simulation/track/spatialQueries.js +76 -31
- package/src/simulation/track/trackBuilder.js +54 -0
- package/src/simulation/track/trackConstants.js +0 -22
- package/src/simulation/track/trackMath.js +4 -29
- package/src/simulation/track/trackModel.js +12 -240
- package/src/simulation/track/trackModelCache.js +105 -0
- package/src/simulation/track/trackNearestQueries.js +1218 -0
- package/src/simulation/track/trackQueryConstants.js +3 -0
- package/src/simulation/track/trackQueryConstruction.js +368 -0
- package/src/simulation/track/trackQueryIndex.js +12 -1933
- package/src/simulation/track/trackQuerySegments.js +174 -0
- package/src/simulation/track/trackQueryStats.js +12 -0
- package/src/simulation/track/trackRayQueries.js +165 -0
- package/src/simulation/vehicle/advancedContactResponse.js +89 -0
- package/src/simulation/vehicle/advancedTireForces.js +92 -0
- package/src/simulation/vehicle/advancedVehicleModel.js +65 -0
- package/src/simulation/vehicle/advancedVehiclePhysics.js +127 -0
- package/src/simulation/vehicle/contactResolution.js +23 -9
- package/src/simulation/vehicle/mainTrackWheelSurface.js +2 -13
- package/src/simulation/vehicle/pitWheelSurface.js +0 -12
- package/src/simulation/vehicle/runoffResponse.js +2 -4
- package/src/simulation/vehicle/sampledWheelSurface.js +266 -0
- package/src/simulation/vehicle/vehicleGeometry.js +85 -103
- package/src/simulation/vehicle/vehicleKinematics.js +10 -0
- package/src/simulation/vehicle/vehiclePhysics.js +42 -196
- package/src/simulation/vehicle/vehicleSnapshots.js +18 -6
- package/src/simulation/vehicle/vehicleState.js +5 -3
- package/src/simulation/vehicle/wheelSurface.js +13 -432
- package/src/simulation/vehicle/wheelSurfaceCache.js +55 -0
- package/src/simulation/vehicle/wheelSurfaceSummary.js +101 -0
- package/src/styles.css +173 -19
- package/src/ui/bannerTemplates.js +4 -2
- package/src/ui/cameraControlsTemplate.js +4 -2
- package/src/ui/gridWalkTemplate.js +29 -0
- package/src/ui/htmlEscaping.js +8 -0
- package/src/ui/raceCanvasTemplate.js +3 -2
- package/src/ui/shellTemplate.js +3 -0
- package/src/ui/telemetryDrawerTemplate.js +4 -2
- package/src/ui/telemetryTemplates.js +3 -22
- package/src/ui/templateUtils.js +2 -8
- package/src/ui/viewControlsTemplate.js +21 -0
- package/examples/train-basic-policy.mjs +0 -276
- package/examples/trainingData.mjs +0 -133
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { getStartGridSlot } from '../../simulation/vehicle/vehicleState.js';
|
|
2
|
+
import { offsetTrackPoint, pointAt } from '../../simulation/track/trackModel.js';
|
|
3
|
+
|
|
4
|
+
// This state owns display poses only. It never steps, resets or writes the race.
|
|
5
|
+
export class GridWalkSession {
|
|
6
|
+
constructor(simulation, options) {
|
|
7
|
+
this.view = options.initialView;
|
|
8
|
+
this.hasRaced = this.view === 'race';
|
|
9
|
+
this.clearing = false;
|
|
10
|
+
this.clearElapsed = 0;
|
|
11
|
+
const snapshot = simulation.snapshot();
|
|
12
|
+
const participants = new Map(simulation.cars.map((car) => [car.id, car]));
|
|
13
|
+
this.snapshot = {
|
|
14
|
+
...snapshot,
|
|
15
|
+
replayGhosts: [],
|
|
16
|
+
safetyCar: { ...snapshot.safetyCar, deployed: false },
|
|
17
|
+
cars: snapshot.cars.map((car) => {
|
|
18
|
+
const participant = participants.get(car.id);
|
|
19
|
+
// Standing starts use the actual assigned slots, including grid penalties.
|
|
20
|
+
// Rolling starts get display-only staging slots without changing their rules.
|
|
21
|
+
const slot = participant.gridLocked ? participant : getStartGridSlot(participant.index, { standingStart: true });
|
|
22
|
+
const point = pointAt(simulation.track, slot.gridDistance);
|
|
23
|
+
const pose = offsetTrackPoint(point, slot.gridOffset);
|
|
24
|
+
return { ...car, ...pose, heading: point.heading, speed: 0, drsActive: false,
|
|
25
|
+
previousX: pose.x, previousY: pose.y, previousHeading: point.heading,
|
|
26
|
+
gridDistance: slot.gridDistance, gridOffset: slot.gridOffset };
|
|
27
|
+
}).sort((a, b) => b.gridDistance - a.gridDistance),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get active() { return this.view === 'grid-walk' || this.clearing; }
|
|
32
|
+
|
|
33
|
+
setView(view, motion) {
|
|
34
|
+
if (!['grid-walk', 'race'].includes(view) || view === this.view) return false;
|
|
35
|
+
this.view = view;
|
|
36
|
+
this.clearing = view === 'race' && !this.hasRaced && motion;
|
|
37
|
+
this.clearElapsed = 0;
|
|
38
|
+
if (view === 'race' && !this.clearing) this.hasRaced = true;
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
advance(elapsedMs, motion) {
|
|
43
|
+
if (!this.clearing) return;
|
|
44
|
+
this.clearElapsed += Math.max(0, Math.min(elapsedMs, 100));
|
|
45
|
+
if (!motion || this.clearElapsed >= 650) {
|
|
46
|
+
this.clearing = false;
|
|
47
|
+
this.hasRaced = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/app/layoutSupport.js
CHANGED
|
@@ -5,6 +5,7 @@ export const PADDOCK_MIN_SUPPORTED_INLINE_SIZE = 320;
|
|
|
5
5
|
const MIN_INLINE_SIZE_BY_COMPONENT = {
|
|
6
6
|
'race-controls': 260,
|
|
7
7
|
'camera-controls': 160,
|
|
8
|
+
'view-controls': 180,
|
|
8
9
|
'safety-car-control': 44,
|
|
9
10
|
'timing-tower': 255,
|
|
10
11
|
'race-canvas': 180,
|
|
@@ -23,6 +24,7 @@ const MIN_INLINE_SIZE_BY_COMPONENT = {
|
|
|
23
24
|
const MIN_BLOCK_SIZE_BY_COMPONENT = {
|
|
24
25
|
'race-controls': 88,
|
|
25
26
|
'camera-controls': 56,
|
|
27
|
+
'view-controls': 56,
|
|
26
28
|
'safety-car-control': 44,
|
|
27
29
|
'timing-tower': 220,
|
|
28
30
|
'race-canvas': 180,
|
|
@@ -69,19 +69,33 @@ function createPanelOverlayRecord(panel) {
|
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
function setPanelClearanceClass(panel, needed) {
|
|
73
|
+
if (panel.classList?.contains(CLEARANCE_CLASS) !== needed) {
|
|
74
|
+
panel.classList?.toggle(CLEARANCE_CLASS, needed);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function setPanelClearance(panel, needed, value = '') {
|
|
79
|
+
setPanelClearanceClass(panel, needed);
|
|
80
|
+
// This panel is observed for style changes. Repeating an identical write
|
|
81
|
+
// can notify MutationObserver and schedule another layout pass forever.
|
|
82
|
+
const currentValue = panel.style?.getPropertyValue(CLEARANCE_VAR) ?? '';
|
|
83
|
+
if (currentValue === value) return;
|
|
84
|
+
if (value) panel.style?.setProperty(CLEARANCE_VAR, value);
|
|
85
|
+
else panel.style?.removeProperty(CLEARANCE_VAR);
|
|
86
|
+
}
|
|
87
|
+
|
|
72
88
|
function syncPanelOverlayClearance(record) {
|
|
73
89
|
const { panel, tower, banner, toggle } = record;
|
|
74
90
|
if (!panel.classList?.contains('sim-canvas-panel--responsive-narrow')) {
|
|
75
|
-
panel
|
|
76
|
-
panel.style?.removeProperty(CLEARANCE_VAR);
|
|
91
|
+
setPanelClearance(panel, false);
|
|
77
92
|
return;
|
|
78
93
|
}
|
|
79
94
|
|
|
80
95
|
const toggleStyle = toggle ? getComputedStyle(toggle) : null;
|
|
81
96
|
const timingRevealMode = Boolean(toggle && toggleStyle?.display !== 'none' && toggleStyle?.visibility !== 'hidden');
|
|
82
97
|
if (!timingRevealMode || !tower || !isVisible(banner)) {
|
|
83
|
-
panel
|
|
84
|
-
panel.style?.removeProperty(CLEARANCE_VAR);
|
|
98
|
+
setPanelClearance(panel, false);
|
|
85
99
|
return;
|
|
86
100
|
}
|
|
87
101
|
|
|
@@ -95,16 +109,16 @@ function syncPanelOverlayClearance(record) {
|
|
|
95
109
|
const needsClearance = timingEntryBounds(panel, tower)
|
|
96
110
|
.some((entryBounds) => rectanglesOverlap(entryBounds, bannerWithoutClearance));
|
|
97
111
|
|
|
98
|
-
panel
|
|
112
|
+
setPanelClearanceClass(panel, needsClearance);
|
|
99
113
|
if (!needsClearance) {
|
|
100
|
-
panel
|
|
114
|
+
setPanelClearance(panel, false);
|
|
101
115
|
return;
|
|
102
116
|
}
|
|
103
117
|
|
|
104
118
|
const panelRect = panel.getBoundingClientRect();
|
|
105
119
|
const bannerRect = banner.getBoundingClientRect();
|
|
106
120
|
const clearance = Math.max(0, Math.ceil(panelRect.bottom - bannerRect.top + OVERLAY_GAP_PX));
|
|
107
|
-
panel
|
|
121
|
+
setPanelClearance(panel, true, `${clearance}px`);
|
|
108
122
|
}
|
|
109
123
|
|
|
110
124
|
function raceCanvasPanels(root) {
|
|
@@ -29,10 +29,8 @@ export function getOverviewFields(driver, mode) {
|
|
|
29
29
|
label,
|
|
30
30
|
value: ratings[key] ?? DRIVER_STAT_DEFINITIONS[key]?.neutral,
|
|
31
31
|
})),
|
|
32
|
-
...normalizeCustomFields(
|
|
33
|
-
|
|
34
|
-
...(driver?.customFields ?? []),
|
|
35
|
-
]),
|
|
32
|
+
...normalizeCustomFields(driver?.constructorArgs?.driver?.customFields),
|
|
33
|
+
...normalizeCustomFields(driver?.customFields),
|
|
36
34
|
];
|
|
37
35
|
}
|
|
38
36
|
|
|
@@ -56,6 +54,13 @@ export function renderCarDriverOverview({
|
|
|
56
54
|
lastOverviewRenderKey,
|
|
57
55
|
}) {
|
|
58
56
|
if (!car || !readouts.carOverview) return lastOverviewRenderKey;
|
|
57
|
+
if (readouts.carOverviewBindings?.length) {
|
|
58
|
+
for (const binding of readouts.carOverviewBindings) {
|
|
59
|
+
binding.lastOverviewRenderKey = renderCarDriverOverview({ car, driverById, assets, overviewMode, readouts: binding,
|
|
60
|
+
lastOverviewRenderKey: binding.lastOverviewRenderKey });
|
|
61
|
+
}
|
|
62
|
+
return readouts.carOverviewBindings[0].lastOverviewRenderKey;
|
|
63
|
+
}
|
|
59
64
|
const driver = driverById.get(car.id);
|
|
60
65
|
const icon = car.icon ?? driver?.icon ?? car.code;
|
|
61
66
|
const driverNumber = formatDriverNumber(car.driverNumber ?? driver?.driverNumber);
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
export { formatCssColor } from '../../config/cssValues.js';
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
return String(value ?? '')
|
|
5
|
-
.replaceAll('&', '&')
|
|
6
|
-
.replaceAll('<', '<')
|
|
7
|
-
.replaceAll('>', '>')
|
|
8
|
-
.replaceAll('"', '"')
|
|
9
|
-
.replaceAll("'", ''');
|
|
10
|
-
}
|
|
3
|
+
export { escapeHtml } from '../../ui/htmlEscaping.js';
|
|
11
4
|
|
|
12
5
|
export function getTireClass(tire) {
|
|
13
6
|
return String(tire ?? 'M').toLowerCase();
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { setStyleProperty, setTextAll, setText } from '../domBindings.js';
|
|
2
2
|
import { clamp } from '../../simulation/simMath.js';
|
|
3
|
-
import { formatDriverNumber } from '../../data/championship.js';
|
|
4
3
|
import {
|
|
5
4
|
formatCssColor,
|
|
6
5
|
formatTelemetryGap,
|
|
@@ -8,9 +7,8 @@ import {
|
|
|
8
7
|
setPerformanceClass,
|
|
9
8
|
} from './readoutFormatters.js';
|
|
10
9
|
|
|
11
|
-
export function renderTelemetryReadouts({ readouts, car
|
|
10
|
+
export function renderTelemetryReadouts({ readouts, car }) {
|
|
12
11
|
if (!car) return;
|
|
13
|
-
const driver = driverById.get(car.id);
|
|
14
12
|
const drsState = car.drsActive ? 'OPEN' : car.drsEligible ? 'READY' : 'OFF';
|
|
15
13
|
const surface = (car.surface ?? 'track').toUpperCase();
|
|
16
14
|
const driverColor = formatCssColor(car.color);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getNextTimingGapMode, getTimingGapModeLabel, normalizeTimingGapMode } from '../../config/timingGapMode.js';
|
|
2
|
-
import {
|
|
2
|
+
import { formatPenaltyHeadline } from '../banners/bannerFormatters.js';
|
|
3
|
+
import { setStyleProperty as setDomStyleProperty, setText, setTextAll } from '../domBindings.js';
|
|
3
4
|
import {
|
|
4
5
|
escapeHtml,
|
|
5
6
|
formatCssColor,
|
|
@@ -8,29 +9,6 @@ import {
|
|
|
8
9
|
getTireClass,
|
|
9
10
|
} from './readoutFormatters.js';
|
|
10
11
|
|
|
11
|
-
function formatPenaltyHeadline(penalty) {
|
|
12
|
-
const serviceType = penalty?.serviceType;
|
|
13
|
-
if (serviceType === 'driveThrough') {
|
|
14
|
-
return penalty?.unserved ? 'unserved drive-through penalty' : 'drive-through penalty';
|
|
15
|
-
}
|
|
16
|
-
if (serviceType === 'stopGo') {
|
|
17
|
-
return penalty?.unserved ? 'unserved stop-go penalty' : 'stop-go penalty';
|
|
18
|
-
}
|
|
19
|
-
const positionDrop = Number(penalty?.positionDrop);
|
|
20
|
-
if (Number.isFinite(positionDrop) && positionDrop > 0) {
|
|
21
|
-
return `${positionDrop}-place position drop`;
|
|
22
|
-
}
|
|
23
|
-
const gridDrop = Number(penalty?.gridDrop);
|
|
24
|
-
if (Number.isFinite(gridDrop) && gridDrop > 0) {
|
|
25
|
-
return `${gridDrop}-place grid drop`;
|
|
26
|
-
}
|
|
27
|
-
if (penalty?.disqualified) return 'disqualification';
|
|
28
|
-
const seconds = Number(penalty?.penaltySeconds);
|
|
29
|
-
if (!Number.isFinite(seconds) || seconds <= 0) return 'Penalty decision';
|
|
30
|
-
const unit = seconds === 1 ? 'second' : 'seconds';
|
|
31
|
-
return `+${seconds} ${unit} time penalty`;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
12
|
function formatPenaltyBadgeLabel(penalty) {
|
|
35
13
|
const seconds = Number(penalty?.penaltySeconds);
|
|
36
14
|
const timeLabel = Number.isFinite(seconds) && seconds > 0 ? `${seconds}s ` : '';
|
|
@@ -243,52 +221,61 @@ function createTimingRowView(document) {
|
|
|
243
221
|
};
|
|
244
222
|
}
|
|
245
223
|
|
|
246
|
-
function
|
|
224
|
+
function setTimingStyleProperty(element, name, value) {
|
|
247
225
|
if (typeof element.style?.setProperty === 'function') {
|
|
248
|
-
element
|
|
226
|
+
setDomStyleProperty(element, name, value);
|
|
249
227
|
} else {
|
|
250
|
-
element
|
|
228
|
+
setAttributeIfChanged(element, 'style', `${name}: ${value}`);
|
|
251
229
|
}
|
|
252
230
|
}
|
|
253
231
|
|
|
232
|
+
function setAttributeIfChanged(element, name, value) {
|
|
233
|
+
const nextValue = String(value);
|
|
234
|
+
if (element.getAttribute?.(name) !== nextValue) element.setAttribute?.(name, nextValue);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function setClassName(element, className) {
|
|
238
|
+
if (element.className !== className) element.className = className;
|
|
239
|
+
}
|
|
240
|
+
|
|
254
241
|
function updateTimingRowView(view, row) {
|
|
255
|
-
view.button
|
|
256
|
-
view.button
|
|
257
|
-
view.button
|
|
258
|
-
view.button
|
|
259
|
-
|
|
242
|
+
setClassName(view.button, `timing-row ${row.selected ? 'is-selected' : ''} ${row.dnf ? 'is-dnf' : ''}`);
|
|
243
|
+
setAttributeIfChanged(view.button, 'type', 'button');
|
|
244
|
+
setAttributeIfChanged(view.button, 'data-driver-id', row.id);
|
|
245
|
+
setAttributeIfChanged(view.button, 'aria-label', `Select ${row.name}`);
|
|
246
|
+
setTimingStyleProperty(view.button, '--driver-color', row.driverColor);
|
|
260
247
|
|
|
261
|
-
view.position
|
|
262
|
-
view.position
|
|
248
|
+
setClassName(view.position, 'timing-position');
|
|
249
|
+
setText(view.position, String(row.rank));
|
|
263
250
|
|
|
264
|
-
view.icon
|
|
265
|
-
view.icon
|
|
266
|
-
|
|
267
|
-
view.icon
|
|
251
|
+
setClassName(view.icon, 'timing-icon timing-team-icon');
|
|
252
|
+
setAttributeIfChanged(view.icon, 'aria-hidden', 'true');
|
|
253
|
+
setTimingStyleProperty(view.icon, '--team-color', row.iconColor);
|
|
254
|
+
setText(view.icon, String(row.icon ?? ''));
|
|
268
255
|
|
|
269
|
-
view.name
|
|
270
|
-
view.name
|
|
271
|
-
view.code
|
|
256
|
+
setClassName(view.name, 'timing-name');
|
|
257
|
+
setAttributeIfChanged(view.name, 'title', row.name);
|
|
258
|
+
setText(view.code, String(row.timingCode ?? ''));
|
|
272
259
|
|
|
273
260
|
if (row.penaltyLabel) {
|
|
274
261
|
if (!view.penaltyBadge) {
|
|
275
262
|
view.penaltyBadge = view.name.ownerDocument.createElement('span');
|
|
276
263
|
view.name.appendChild(view.penaltyBadge);
|
|
277
264
|
}
|
|
278
|
-
view.penaltyBadge
|
|
279
|
-
view.penaltyBadge
|
|
280
|
-
view.penaltyBadge
|
|
281
|
-
view.penaltyBadge
|
|
265
|
+
setClassName(view.penaltyBadge, 'timing-penalty-badge');
|
|
266
|
+
setAttributeIfChanged(view.penaltyBadge, 'aria-label', row.penaltyLabel);
|
|
267
|
+
setAttributeIfChanged(view.penaltyBadge, 'title', row.penaltyLabel);
|
|
268
|
+
setText(view.penaltyBadge, '!');
|
|
282
269
|
} else if (view.penaltyBadge) {
|
|
283
270
|
view.penaltyBadge.remove();
|
|
284
271
|
view.penaltyBadge = null;
|
|
285
272
|
}
|
|
286
273
|
|
|
287
|
-
view.gap
|
|
288
|
-
view.gap
|
|
274
|
+
setClassName(view.gap, 'timing-gap');
|
|
275
|
+
setText(view.gap, String(row.gap));
|
|
289
276
|
|
|
290
|
-
view.tire
|
|
291
|
-
view.tire
|
|
277
|
+
setClassName(view.tire, `timing-tire timing-tire--${row.tireClass}`);
|
|
278
|
+
setText(view.tire, String(row.tire ?? ''));
|
|
292
279
|
}
|
|
293
280
|
|
|
294
281
|
function patchTimingRows(timingList, rows) {
|
|
@@ -304,7 +291,6 @@ function patchTimingRows(timingList, rows) {
|
|
|
304
291
|
}
|
|
305
292
|
seen.add(row.id);
|
|
306
293
|
updateTimingRowView(view, row);
|
|
307
|
-
timingList.appendChild(view.item);
|
|
308
294
|
});
|
|
309
295
|
|
|
310
296
|
rowViews.forEach((view, id) => {
|
|
@@ -312,6 +298,18 @@ function patchTimingRows(timingList, rows) {
|
|
|
312
298
|
view.item.remove();
|
|
313
299
|
rowViews.delete(id);
|
|
314
300
|
});
|
|
301
|
+
|
|
302
|
+
if (typeof timingList.insertBefore === 'function') {
|
|
303
|
+
rows.forEach((row, index) => {
|
|
304
|
+
const item = rowViews.get(row.id).item;
|
|
305
|
+
const currentItem = timingList.children[index] ?? null;
|
|
306
|
+
if (currentItem !== item) timingList.insertBefore(item, currentItem);
|
|
307
|
+
});
|
|
308
|
+
} else if (rows.some((row, index) => timingList.children[index] !== rowViews.get(row.id).item)) {
|
|
309
|
+
// Minimal host/test DOM implementations may only support appendChild.
|
|
310
|
+
// Append in desired order only when reconciliation is actually needed.
|
|
311
|
+
rows.forEach((row) => timingList.appendChild(rowViews.get(row.id).item));
|
|
312
|
+
}
|
|
315
313
|
}
|
|
316
314
|
|
|
317
315
|
export function renderTimingTower({
|
|
@@ -6,33 +6,31 @@ async function loadTexture(url, { configure = null } = {}) {
|
|
|
6
6
|
configure?.(texture);
|
|
7
7
|
return texture;
|
|
8
8
|
} catch {
|
|
9
|
+
// Unavailable images and incompatible texture sources both use the visible fallback.
|
|
9
10
|
return Texture.WHITE;
|
|
10
11
|
}
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export async function loadAppTextures(assets) {
|
|
14
|
-
const
|
|
15
|
-
|
|
15
|
+
const [car, safetyCar, trackTextures] = await Promise.all([
|
|
16
|
+
loadTexture(assets.car, {
|
|
16
17
|
configure(texture) {
|
|
17
18
|
texture.source.scaleMode = 'linear';
|
|
18
19
|
texture.source.autoGenerateMipmaps = true;
|
|
19
20
|
},
|
|
20
21
|
}),
|
|
21
|
-
|
|
22
|
+
loadTexture(assets.safetyCar, {
|
|
22
23
|
configure(texture) {
|
|
23
24
|
texture.source.scaleMode = 'linear';
|
|
24
25
|
texture.source.autoGenerateMipmaps = true;
|
|
25
26
|
},
|
|
26
27
|
}),
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
await Promise.all(Object.entries(assets.trackTextures).map(async ([key, url]) => {
|
|
30
|
-
textures[key] = await loadTexture(url, {
|
|
28
|
+
Promise.all(Object.entries(assets.trackTextures).map(async ([key, url]) => [key, await loadTexture(url, {
|
|
31
29
|
configure(texture) {
|
|
32
30
|
texture.source.scaleMode = 'linear';
|
|
33
31
|
},
|
|
34
|
-
})
|
|
35
|
-
|
|
32
|
+
})])),
|
|
33
|
+
]);
|
|
36
34
|
|
|
37
|
-
return
|
|
35
|
+
return { car, safetyCar, ...Object.fromEntries(trackTextures) };
|
|
38
36
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { Container, Graphics, Sprite, Text, Texture } from 'pixi.js';
|
|
2
2
|
import { metersToSimUnits } from '../../simulation/units.js';
|
|
3
|
-
import { VEHICLE_GEOMETRY } from '../../simulation/vehicleGeometry.js';
|
|
3
|
+
import { VEHICLE_GEOMETRY } from '../../simulation/vehicle/vehicleGeometry.js';
|
|
4
4
|
import { CAR_WORLD_LENGTH } from '../camera/cameraConstants.js';
|
|
5
5
|
import { colorToTint, smoothAngle } from './displayUtils.js';
|
|
6
6
|
|
|
7
7
|
const CAR_WORLD_WIDTH = VEHICLE_GEOMETRY.visualWidth;
|
|
8
|
-
const TEMP_RENDER_RAW_CAR_GEOMETRY = false;
|
|
9
8
|
const NON_COLLIDING_MARKER_COLOR = 0x38bdf8;
|
|
10
9
|
const SAFETY_CAR_WORLD_LENGTH = metersToSimUnits(5.3);
|
|
11
10
|
const SAFETY_CAR_WORLD_WIDTH = metersToSimUnits(2.1);
|
|
@@ -54,26 +53,20 @@ export class CarRenderer {
|
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
drivers.forEach((driver) => {
|
|
57
|
-
const sprite =
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
sprite.anchor.set(0.5);
|
|
62
|
-
sprite.baseScale = baseScale;
|
|
63
|
-
sprite.scale.set(baseScale);
|
|
64
|
-
} else {
|
|
65
|
-
sprite.baseScale = 1;
|
|
66
|
-
}
|
|
56
|
+
const sprite = new Sprite(texture);
|
|
57
|
+
sprite.anchor.set(0.5);
|
|
58
|
+
sprite.baseScale = baseScale;
|
|
59
|
+
sprite.scale.set(baseScale);
|
|
67
60
|
|
|
68
61
|
const marker = this.createNonCollidingMarker();
|
|
69
62
|
this.nonCollidingMarkers.set(driver.id, marker);
|
|
70
63
|
carLayer.addChild(marker);
|
|
71
64
|
|
|
72
|
-
sprite.tint =
|
|
65
|
+
sprite.tint = colorToTint(driver.color);
|
|
73
66
|
sprite.eventMode = 'static';
|
|
74
67
|
sprite.cursor = 'pointer';
|
|
75
|
-
sprite.on('pointerdown', () => {
|
|
76
|
-
this.onSelectCar?.(driver.id);
|
|
68
|
+
sprite.on('pointerdown', (event) => {
|
|
69
|
+
this.onSelectCar?.(driver.id, event);
|
|
77
70
|
});
|
|
78
71
|
this.carSprites.set(driver.id, sprite);
|
|
79
72
|
carLayer.addChild(sprite);
|
|
@@ -82,8 +75,8 @@ export class CarRenderer {
|
|
|
82
75
|
hit.circle(0, 0, 24).fill({ color: 0xffffff, alpha: 0.001 });
|
|
83
76
|
hit.eventMode = 'static';
|
|
84
77
|
hit.cursor = 'pointer';
|
|
85
|
-
hit.on('pointerdown', () => {
|
|
86
|
-
this.onSelectCar?.(driver.id);
|
|
78
|
+
hit.on('pointerdown', (event) => {
|
|
79
|
+
this.onSelectCar?.(driver.id, event);
|
|
87
80
|
});
|
|
88
81
|
this.carHitAreas.set(driver.id, hit);
|
|
89
82
|
carLayer.addChild(hit);
|
|
@@ -95,64 +88,11 @@ export class CarRenderer {
|
|
|
95
88
|
});
|
|
96
89
|
}
|
|
97
90
|
|
|
98
|
-
createRawCarGeometryGraphic(driver) {
|
|
99
|
-
const graphic = new Graphics();
|
|
100
|
-
const tint = colorToTint(driver.color);
|
|
101
|
-
const wheelLong = VEHICLE_GEOMETRY.wheelLongitudinalOffset;
|
|
102
|
-
const wheelLat = VEHICLE_GEOMETRY.wheelLateralOffset;
|
|
103
|
-
const wheelLength = VEHICLE_GEOMETRY.wheelLength;
|
|
104
|
-
const wheelWidth = VEHICLE_GEOMETRY.wheelWidth;
|
|
105
|
-
|
|
106
|
-
graphic
|
|
107
|
-
.rect(
|
|
108
|
-
-VEHICLE_GEOMETRY.bodyLength / 2,
|
|
109
|
-
-VEHICLE_GEOMETRY.bodyWidth / 2,
|
|
110
|
-
VEHICLE_GEOMETRY.bodyLength,
|
|
111
|
-
VEHICLE_GEOMETRY.bodyWidth,
|
|
112
|
-
)
|
|
113
|
-
.fill({ color: tint, alpha: 0.42 })
|
|
114
|
-
.stroke({ width: 2.2, color: 0xf8fafc, alpha: 0.95 });
|
|
115
|
-
|
|
116
|
-
[
|
|
117
|
-
[wheelLong, -wheelLat],
|
|
118
|
-
[wheelLong, wheelLat],
|
|
119
|
-
[-wheelLong, -wheelLat],
|
|
120
|
-
[-wheelLong, wheelLat],
|
|
121
|
-
].forEach(([x, y]) => {
|
|
122
|
-
graphic
|
|
123
|
-
.rect(x - wheelLength / 2, y - wheelWidth / 2, wheelLength, wheelWidth)
|
|
124
|
-
.fill({ color: 0x111827, alpha: 0.95 })
|
|
125
|
-
.stroke({ width: 1.5, color: tint, alpha: 1 });
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const noseMarkerLength = metersToSimUnits(0.75);
|
|
129
|
-
graphic
|
|
130
|
-
.moveTo(VEHICLE_GEOMETRY.bodyLength / 2 - noseMarkerLength, 0)
|
|
131
|
-
.lineTo(VEHICLE_GEOMETRY.bodyLength / 2 + noseMarkerLength, 0)
|
|
132
|
-
.stroke({ width: 2, color: 0xf1c65b, alpha: 0.95 });
|
|
133
|
-
return graphic;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
91
|
createNonCollidingMarker() {
|
|
137
92
|
const marker = new Graphics();
|
|
138
93
|
marker.visible = false;
|
|
139
94
|
marker.eventMode = 'none';
|
|
140
95
|
marker.interactionVisualRole = 'non-colliding-marker';
|
|
141
|
-
return marker;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
renderNonCollidingMarker(car) {
|
|
145
|
-
const marker = this.nonCollidingMarkers.get(car.id);
|
|
146
|
-
if (!marker) return;
|
|
147
|
-
const visible = car.interaction?.collidable === false;
|
|
148
|
-
if (marker.visible !== visible) marker.visible = visible;
|
|
149
|
-
if (!visible) return;
|
|
150
|
-
|
|
151
|
-
if (marker.x !== car.x) marker.x = car.x;
|
|
152
|
-
if (marker.y !== car.y) marker.y = car.y;
|
|
153
|
-
if (marker.rotation !== car.heading) marker.rotation = car.heading;
|
|
154
|
-
|
|
155
|
-
marker.clear();
|
|
156
96
|
const length = CAR_WORLD_LENGTH * 0.82;
|
|
157
97
|
const width = CAR_WORLD_WIDTH * 1.32;
|
|
158
98
|
const halfLength = length / 2;
|
|
@@ -173,6 +113,19 @@ export class CarRenderer {
|
|
|
173
113
|
.lineTo(longitudinal * (halfLength - segment), lateral * halfWidth)
|
|
174
114
|
.stroke({ width: 4, color: NON_COLLIDING_MARKER_COLOR, alpha });
|
|
175
115
|
});
|
|
116
|
+
return marker;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
renderNonCollidingMarker(car) {
|
|
120
|
+
const marker = this.nonCollidingMarkers.get(car.id);
|
|
121
|
+
if (!marker) return;
|
|
122
|
+
const visible = car.interaction?.collidable === false;
|
|
123
|
+
if (marker.visible !== visible) marker.visible = visible;
|
|
124
|
+
if (!visible) return;
|
|
125
|
+
|
|
126
|
+
if (marker.x !== car.x) marker.x = car.x;
|
|
127
|
+
if (marker.y !== car.y) marker.y = car.y;
|
|
128
|
+
if (marker.rotation !== car.heading) marker.rotation = car.heading;
|
|
176
129
|
}
|
|
177
130
|
|
|
178
131
|
createServiceCountdownLabel() {
|
|
@@ -208,20 +161,15 @@ export class CarRenderer {
|
|
|
208
161
|
label.serviceTone = tone;
|
|
209
162
|
}
|
|
210
163
|
|
|
211
|
-
renderCars(snapshot, { textures, carLayer }) {
|
|
164
|
+
renderCars(snapshot, { textures, carLayer, snapToPose = false }) {
|
|
212
165
|
snapshot.cars.forEach((car) => {
|
|
213
166
|
const sprite = this.carSprites.get(car.id);
|
|
214
167
|
const hit = this.carHitAreas.get(car.id);
|
|
215
168
|
if (!sprite || !hit) return;
|
|
216
169
|
if (sprite.x !== car.x) sprite.x = car.x;
|
|
217
170
|
if (sprite.y !== car.y) sprite.y = car.y;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (sprite.rotation !== car.heading) sprite.rotation = car.heading;
|
|
221
|
-
} else {
|
|
222
|
-
sprite.currentRotation = smoothAngle(sprite.currentRotation, car.heading, 0.24);
|
|
223
|
-
if (sprite.rotation !== sprite.currentRotation) sprite.rotation = sprite.currentRotation;
|
|
224
|
-
}
|
|
171
|
+
sprite.currentRotation = snapToPose ? car.heading : smoothAngle(sprite.currentRotation, car.heading, 0.24);
|
|
172
|
+
if (sprite.rotation !== sprite.currentRotation) sprite.rotation = sprite.currentRotation;
|
|
225
173
|
const retired = Boolean(car.destroyed || car.dnf || car.outOfRace);
|
|
226
174
|
const alpha = retired ? 0.48 : snapshot.raceControl.mode === 'safety-car' ? 0.82 : 1;
|
|
227
175
|
if (sprite.alpha !== alpha) sprite.alpha = alpha;
|
|
@@ -229,7 +177,7 @@ export class CarRenderer {
|
|
|
229
177
|
sprite.scale.set(sprite.baseScale);
|
|
230
178
|
sprite.lastRenderedScale = sprite.baseScale;
|
|
231
179
|
}
|
|
232
|
-
const tint = retired ? 0x1f2937 :
|
|
180
|
+
const tint = retired ? 0x1f2937 : colorToTint(car.color);
|
|
233
181
|
if (sprite.tint !== tint) sprite.tint = tint;
|
|
234
182
|
if (hit.x !== car.x) hit.x = car.x;
|
|
235
183
|
if (hit.y !== car.y) hit.y = car.y;
|
|
@@ -14,9 +14,3 @@ export function smoothAngle(current, target, amount) {
|
|
|
14
14
|
if (diff < -Math.PI) diff += Math.PI * 2;
|
|
15
15
|
return current + diff * amount;
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
export function destroyDisplayChildren(container) {
|
|
19
|
-
container?.removeChildren?.().forEach((child) => {
|
|
20
|
-
child.destroy?.({ children: true, texture: false, textureSource: false });
|
|
21
|
-
});
|
|
22
|
-
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getCarRayOrigin, getCarRayVector } from '../../environment/sensors.js';
|
|
1
|
+
import { getCarRayOrigin, getCarRayVector } from '../../environment/sensors/index.js';
|
|
2
2
|
import { metersToSimUnits } from '../../simulation/units.js';
|
|
3
3
|
|
|
4
4
|
const SENSOR_RAY_TRACK_COLOR = 0xf1c65b;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Graphics, Sprite, Text, Texture } from 'pixi.js';
|
|
2
2
|
import { CAR_WORLD_LENGTH } from '../camera/cameraConstants.js';
|
|
3
|
-
import { VEHICLE_GEOMETRY } from '../../simulation/vehicleGeometry.js';
|
|
3
|
+
import { VEHICLE_GEOMETRY } from '../../simulation/vehicle/vehicleGeometry.js';
|
|
4
4
|
import { colorToTint, smoothAngle } from './displayUtils.js';
|
|
5
5
|
|
|
6
6
|
const CAR_WORLD_WIDTH = VEHICLE_GEOMETRY.visualWidth;
|
|
@@ -111,6 +111,9 @@ export class ReplayGhostRenderer {
|
|
|
111
111
|
drawHalo(halo, ghost) {
|
|
112
112
|
const tint = colorToTint(ghost.color);
|
|
113
113
|
const alpha = Math.max(0.18, Math.min(0.55, ghost.opacity + 0.16));
|
|
114
|
+
if (halo.lastRenderedTint === tint && halo.lastRenderedAlpha === alpha) return;
|
|
115
|
+
halo.lastRenderedTint = tint;
|
|
116
|
+
halo.lastRenderedAlpha = alpha;
|
|
114
117
|
halo.clear();
|
|
115
118
|
halo
|
|
116
119
|
.ellipse(0, 0, CAR_WORLD_LENGTH * 0.72, CAR_WORLD_WIDTH * 1.25)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Graphics } from 'pixi.js';
|
|
2
|
-
import { offsetTrackPoint, pointAt } from '../../simulation/trackModel.js';
|
|
3
|
-
import { destroyDisplayChildren } from '
|
|
2
|
+
import { offsetTrackPoint, pointAt } from '../../simulation/track/trackModel.js';
|
|
3
|
+
import { destroyDisplayChildren } from '../../rendering/displayLifecycle.js';
|
|
4
4
|
|
|
5
5
|
export function renderTrackSurface({ drsLayer, sensorLayer, pitLaneStatusLayer, trackAsset, snapshot }) {
|
|
6
6
|
destroyDisplayChildren(drsLayer);
|