@inventure71/paddockjs 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/INSTALL_AND_UPDATE.md +4 -3
- package/README.md +194 -12
- package/docs/architecture.md +119 -44
- package/docs/concepts.md +4 -2
- package/docs/custom_model_controller.md +329 -0
- package/docs/data_contract.md +299 -17
- package/docs/index.md +1 -0
- package/docs/learnings.md +22 -0
- package/docs/rules.md +37 -10
- package/docs/system_specs.md +23 -10
- package/docs/training.md +272 -30
- package/examples/python/README.md +62 -0
- package/examples/python/base_policy_server.py +191 -0
- package/examples/python/requirements.txt +2 -0
- package/package.json +12 -4
- package/src/api/PaddockSimulatorController.js +4 -0
- package/src/app/BrowserExpertAdapter.js +367 -9
- package/src/app/F1SimulatorApp.js +305 -1603
- package/src/app/banners/bannerFormatters.js +55 -0
- package/src/app/banners/raceDataBannerController.js +141 -0
- package/src/app/banners/stewardMessageController.js +99 -0
- package/src/app/camera/cameraConstants.js +21 -0
- package/src/app/camera/cameraController.js +265 -0
- package/src/app/camera/cameraGeometry.js +176 -0
- package/src/app/domBindings.js +4 -0
- package/src/app/readouts/carOverviewRenderer.js +109 -0
- package/src/app/readouts/controlStateRenderer.js +37 -0
- package/src/app/readouts/raceStatusRenderer.js +133 -0
- package/src/app/readouts/readoutFormatters.js +58 -0
- package/src/app/readouts/telemetryRenderer.js +113 -0
- package/src/app/readouts/timingTowerRenderer.js +177 -0
- package/src/app/rendering/appAssets.js +38 -0
- package/src/app/rendering/carRenderer.js +286 -0
- package/src/app/rendering/displayUtils.js +22 -0
- package/src/app/rendering/drsTrailRenderer.js +90 -0
- package/src/app/rendering/expertSensorRenderer.js +126 -0
- package/src/app/rendering/pitLaneStatusRenderer.js +50 -0
- package/src/app/rendering/replayGhostRenderer.js +124 -0
- package/src/app/rendering/trackRenderer.js +25 -0
- package/src/app/runtime/frameLoop.js +66 -0
- package/src/app/runtime/runtimeTiming.js +34 -0
- package/src/app/runtime/runtimeVisibility.js +45 -0
- package/src/config/defaultOptions.js +20 -0
- package/src/environment/actions.js +13 -5
- package/src/environment/controllerLoop.js +246 -0
- package/src/environment/episode.js +86 -2
- package/src/environment/evaluation.js +54 -32
- package/src/environment/index.d.ts +361 -9
- package/src/environment/index.js +1 -0
- package/src/environment/metrics.js +159 -0
- package/src/environment/observationOptions.js +22 -1
- package/src/environment/observations.js +325 -40
- package/src/environment/options.js +37 -5
- package/src/environment/runtime.js +222 -21
- package/src/environment/scenarios.js +37 -8
- package/src/environment/sensors/bodySenses.js +23 -0
- package/src/environment/sensors/boundarySenses.js +17 -0
- package/src/environment/sensors/carRays.js +75 -0
- package/src/environment/sensors/contactSenses.js +32 -0
- package/src/environment/sensors/index.js +157 -0
- package/src/environment/sensors/indexedRayBands.js +103 -0
- package/src/environment/sensors/nearbyCars.js +52 -0
- package/src/environment/sensors/opponentRadar.js +65 -0
- package/src/environment/sensors/rayConfig.js +110 -0
- package/src/environment/sensors/rayDefaults.js +6 -0
- package/src/environment/sensors/rayGeometry.js +60 -0
- package/src/environment/sensors/rayGuards.js +19 -0
- package/src/environment/sensors/sensorTargets.js +60 -0
- package/src/environment/sensors/surfaceRays.js +296 -0
- package/src/environment/sensors/trackRays.js +261 -0
- package/src/environment/sensors.js +1 -367
- package/src/environment/specs.js +100 -5
- package/src/environment/workerProtocol.js +13 -1
- package/src/index.d.ts +169 -6
- package/src/index.js +5 -0
- package/src/rendering/proceduralTrackAsset.js +20 -823
- package/src/rendering/renderSnapshot.js +38 -12
- package/src/rendering/track/finishLineRenderer.js +34 -0
- package/src/rendering/track/gridRenderer.js +47 -0
- package/src/rendering/track/offsetStrokeSafety.js +136 -0
- package/src/rendering/track/pitLaneRenderer.js +248 -0
- package/src/rendering/track/trackMaterialRenderer.js +326 -0
- package/src/rendering/track/trackRenderConstants.js +38 -0
- package/src/rendering/track/trackRenderGeometry.js +94 -0
- package/src/simulation/collisionGeometry.js +1 -1
- package/src/simulation/driver/driverControlConstants.js +28 -0
- package/src/simulation/driver/driverController.js +47 -0
- package/src/simulation/driver/driverInput.js +28 -0
- package/src/simulation/driver/driverMath.js +6 -0
- package/src/simulation/driver/driverPersonality.js +25 -0
- package/src/simulation/driver/edgeRecovery.js +48 -0
- package/src/simulation/driver/racingControls.js +399 -0
- package/src/simulation/driver/racingLinePlan.js +166 -0
- package/src/simulation/driver/recoveryDynamics.js +38 -0
- package/src/simulation/driver/rejoinControls.js +213 -0
- package/src/simulation/driver/safetyCarControls.js +35 -0
- package/src/simulation/driver/trafficScan.js +51 -0
- package/src/simulation/driverController.js +4 -619
- package/src/simulation/participants/participantInteractions.js +120 -0
- package/src/simulation/pit/pitFlow.js +234 -0
- package/src/simulation/pit/pitIntent.js +92 -0
- package/src/simulation/pit/pitOccupancy.js +47 -0
- package/src/simulation/pit/pitPenaltyService.js +71 -0
- package/src/simulation/pit/pitQueue.js +55 -0
- package/src/simulation/pit/pitRouteMovement.js +160 -0
- package/src/simulation/pit/pitRouting.js +319 -0
- package/src/simulation/pit/pitService.js +41 -0
- package/src/simulation/pit/pitServiceConstants.js +21 -0
- package/src/simulation/pit/pitServiceProfile.js +45 -0
- package/src/simulation/pit/pitSnapshots.js +61 -0
- package/src/simulation/pit/pitState.js +113 -0
- package/src/simulation/pit/pitTireService.js +92 -0
- package/src/simulation/race/classification.js +104 -0
- package/src/simulation/race/gridPenalties.js +37 -0
- package/src/simulation/race/raceControlState.js +38 -0
- package/src/simulation/race/raceDistance.js +32 -0
- package/src/simulation/race/raceFinish.js +65 -0
- package/src/simulation/race/raceLifecycle.js +71 -0
- package/src/simulation/race/raceOrder.js +113 -0
- package/src/simulation/race/raceProgress.js +124 -0
- package/src/simulation/race/raceSetup.js +93 -0
- package/src/simulation/race/raceStep.js +80 -0
- package/src/simulation/race/redFlag.js +15 -0
- package/src/simulation/race/retirements.js +44 -0
- package/src/simulation/race/safetyCar.js +86 -0
- package/src/simulation/race/stalledDnf.js +86 -0
- package/src/simulation/raceSimulation.js +230 -2817
- package/src/simulation/replay/replayGhosts.js +128 -0
- package/src/simulation/rules/moduleConfig.js +72 -0
- package/src/simulation/rules/penaltyConfig.js +99 -0
- package/src/simulation/rules/penaltyEvents.js +18 -0
- package/src/simulation/rules/penaltyStats.js +26 -0
- package/src/simulation/rules/ruleConfigMerge.js +41 -0
- package/src/simulation/rules/ruleDefaults.js +85 -0
- package/src/simulation/rules/rulesReview.js +141 -0
- package/src/simulation/rules/rulesetPresets.js +47 -0
- package/src/simulation/rules/trackLimitsSteward.js +1 -1
- package/src/simulation/rulesConfig.js +5 -323
- package/src/simulation/snapshots/raceSnapshots.js +192 -0
- package/src/simulation/timing/drsTiming.js +87 -0
- package/src/simulation/timing/gapEstimation.js +38 -0
- package/src/simulation/timing/lapTelemetry.js +182 -0
- package/src/simulation/timing/raceTiming.js +15 -0
- package/src/simulation/timing/sectorPerformance.js +60 -0
- package/src/simulation/timing/timingConstants.js +5 -0
- package/src/simulation/timing/timingHistory.js +46 -0
- package/src/simulation/timing/timingLines.js +79 -0
- package/src/simulation/track/centerlineSpline.js +59 -0
- package/src/simulation/track/drsZones.js +67 -0
- package/src/simulation/track/pitLaneAccess.js +103 -0
- package/src/simulation/track/pitLaneLayout.js +395 -0
- package/src/simulation/track/pitLaneState.js +160 -0
- package/src/simulation/track/proceduralCenterline.js +237 -0
- package/src/simulation/track/sampleGeometry.js +49 -0
- package/src/simulation/track/spatialQueries.js +154 -0
- package/src/simulation/track/startStraight.js +141 -0
- package/src/simulation/track/trackConstants.js +109 -0
- package/src/simulation/track/trackGenerationOptions.js +283 -0
- package/src/simulation/track/trackMath.js +241 -0
- package/src/simulation/track/trackModel.js +240 -0
- package/src/simulation/track/trackQueryIndex.js +945 -0
- package/src/simulation/track/trackSectors.js +19 -0
- package/src/simulation/track/trackStatePolicy.js +24 -0
- package/src/simulation/track/trackValidation.js +98 -0
- package/src/simulation/trackModel.js +1 -1706
- package/src/simulation/vehicle/contactResolution.js +116 -0
- package/src/simulation/vehicle/mainTrackWheelSurface.js +82 -0
- package/src/simulation/vehicle/pitWheelSurface.js +69 -0
- package/src/simulation/vehicle/runoffResponse.js +88 -0
- package/src/simulation/vehicle/surfacePriority.js +28 -0
- package/src/simulation/vehicle/vehicleGeometry.js +247 -0
- package/src/simulation/vehicle/vehiclePhysics.js +363 -0
- package/src/simulation/vehicle/vehicleSnapshots.js +316 -0
- package/src/simulation/vehicle/vehicleState.js +186 -0
- package/src/simulation/vehicle/wheelSurface.js +150 -0
- package/src/simulation/vehicleGeometry.js +1 -247
- package/src/simulation/vehiclePhysics.js +1 -136
- package/src/simulation/warmup/runtimeWarmup.js +224 -0
- package/src/simulation/wheelSurface.js +1 -338
- package/src/styles.css +42 -0
- package/src/ui/cameraControlsTemplate.js +20 -0
- package/src/ui/carOverviewTemplate.js +33 -0
- package/src/ui/componentTemplates.js +14 -360
- package/src/ui/raceCanvasTemplate.js +69 -0
- package/src/ui/raceControlsTemplate.js +36 -0
- package/src/ui/shellTemplate.js +5 -4
- package/src/ui/telemetryDrawerTemplate.js +55 -0
- package/src/ui/telemetryTemplates.js +136 -0
- package/src/ui/timingTowerTemplate.js +34 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { Graphics, TilingSprite } from 'pixi.js';
|
|
2
|
+
import { offsetTrackPoint } from '../../simulation/trackModel.js';
|
|
3
|
+
import { ASPHALT_COLOR, BARRIER_WALL_WIDTH, EDGE_REVEAL_OFFSET, EDGE_REVEAL_WIDTH, GRASS_COLOR, GRAVEL_COLOR, KERB_CURVATURE_THRESHOLD, KERB_OFFSET, KERB_STEP, KERB_WIDTH, MATERIAL_TILE_SCALE, OUTER_BOUNDARY_OFFSET, OUTER_BOUNDARY_WIDTH, RUNOFF_GRASS_COLOR, SEGMENTED_STROKE_STEP, WORLD_BACKGROUND_PADDING_MULTIPLIER } from './trackRenderConstants.js';
|
|
4
|
+
import { makeTrackPath, textureOrWhite } from './trackRenderGeometry.js';
|
|
5
|
+
import { getOffsetGapBridges, getOffsetStrokeSegments, offsetSegmentIsSafe } from './offsetStrokeSafety.js';
|
|
6
|
+
|
|
7
|
+
export function drawOffsetSegment(graphics, start, end, {
|
|
8
|
+
width,
|
|
9
|
+
color,
|
|
10
|
+
alpha,
|
|
11
|
+
cap,
|
|
12
|
+
}) {
|
|
13
|
+
graphics.moveTo(start.x, start.y);
|
|
14
|
+
graphics.lineTo(end.x, end.y);
|
|
15
|
+
graphics.stroke({
|
|
16
|
+
width,
|
|
17
|
+
color,
|
|
18
|
+
alpha,
|
|
19
|
+
join: 'round',
|
|
20
|
+
cap,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function drawContinuousOffsetStroke(graphics, track, {
|
|
25
|
+
side,
|
|
26
|
+
offset,
|
|
27
|
+
width,
|
|
28
|
+
color,
|
|
29
|
+
alpha = 1,
|
|
30
|
+
step = SEGMENTED_STROKE_STEP,
|
|
31
|
+
}) {
|
|
32
|
+
const samples = track.samples.slice(0, -1).filter((_, index) => index % step === 0);
|
|
33
|
+
if (samples.length < 2) return;
|
|
34
|
+
|
|
35
|
+
const first = offsetTrackPoint(samples[0], side * offset);
|
|
36
|
+
graphics.moveTo(first.x, first.y);
|
|
37
|
+
samples.slice(1).forEach((sample) => {
|
|
38
|
+
const point = offsetTrackPoint(sample, side * offset);
|
|
39
|
+
graphics.lineTo(point.x, point.y);
|
|
40
|
+
});
|
|
41
|
+
graphics.closePath();
|
|
42
|
+
graphics.stroke({
|
|
43
|
+
width,
|
|
44
|
+
color,
|
|
45
|
+
alpha,
|
|
46
|
+
join: 'round',
|
|
47
|
+
cap: 'round',
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function drawSegmentedOffsetStroke(graphics, track, {
|
|
52
|
+
side,
|
|
53
|
+
offset,
|
|
54
|
+
width,
|
|
55
|
+
color,
|
|
56
|
+
alpha = 1,
|
|
57
|
+
step = SEGMENTED_STROKE_STEP,
|
|
58
|
+
cap = 'round',
|
|
59
|
+
}) {
|
|
60
|
+
const segments = getOffsetStrokeSegments(track, { side, offset, step });
|
|
61
|
+
|
|
62
|
+
segments.forEach((segment) => {
|
|
63
|
+
if (!segment.safe) return;
|
|
64
|
+
drawOffsetSegment(graphics, segment.start, segment.end, {
|
|
65
|
+
width,
|
|
66
|
+
color,
|
|
67
|
+
alpha,
|
|
68
|
+
cap,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
getOffsetGapBridges(track, segments, width).forEach((bridge) => {
|
|
73
|
+
drawOffsetSegment(graphics, bridge.start, bridge.end, {
|
|
74
|
+
width,
|
|
75
|
+
color,
|
|
76
|
+
alpha,
|
|
77
|
+
cap: 'round',
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function addGrass(asset) {
|
|
83
|
+
const grass = new Graphics();
|
|
84
|
+
const padding = Math.max(asset.world.width, asset.world.height) * WORLD_BACKGROUND_PADDING_MULTIPLIER;
|
|
85
|
+
const bounds = {
|
|
86
|
+
x: -padding,
|
|
87
|
+
y: -padding,
|
|
88
|
+
width: asset.world.width + padding * 2,
|
|
89
|
+
height: asset.world.height + padding * 2,
|
|
90
|
+
};
|
|
91
|
+
grass.label = 'world-grass';
|
|
92
|
+
grass.worldGrassBounds = bounds;
|
|
93
|
+
grass.rect(
|
|
94
|
+
bounds.x,
|
|
95
|
+
bounds.y,
|
|
96
|
+
bounds.width,
|
|
97
|
+
bounds.height,
|
|
98
|
+
).fill(GRASS_COLOR);
|
|
99
|
+
asset.container.addChild(grass);
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function addGravelRunoff(asset, track) {
|
|
104
|
+
const bands = getTrackMaterialBands(track);
|
|
105
|
+
const gravel = new Graphics();
|
|
106
|
+
gravel.label = 'track-gravel';
|
|
107
|
+
const gravelCenterOffset = (bands.gravel.inner + bands.gravel.outer) / 2;
|
|
108
|
+
const gravelWidth = bands.gravel.outer - bands.gravel.inner;
|
|
109
|
+
|
|
110
|
+
[-1, 1].forEach((side) => {
|
|
111
|
+
drawContinuousOffsetStroke(gravel, track, {
|
|
112
|
+
side,
|
|
113
|
+
offset: gravelCenterOffset,
|
|
114
|
+
width: gravelWidth,
|
|
115
|
+
color: GRAVEL_COLOR,
|
|
116
|
+
step: 4,
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
asset.container.addChild(gravel);
|
|
120
|
+
|
|
121
|
+
const runoff = new Graphics();
|
|
122
|
+
runoff.label = 'track-runoff';
|
|
123
|
+
const runoffCenterOffset = (bands.runoff.inner + bands.runoff.outer) / 2;
|
|
124
|
+
const runoffWidth = bands.runoff.outer - bands.runoff.inner;
|
|
125
|
+
[-1, 1].forEach((side) => {
|
|
126
|
+
drawContinuousOffsetStroke(runoff, track, {
|
|
127
|
+
side,
|
|
128
|
+
offset: runoffCenterOffset,
|
|
129
|
+
width: runoffWidth,
|
|
130
|
+
color: RUNOFF_GRASS_COLOR,
|
|
131
|
+
alpha: 0.82,
|
|
132
|
+
step: 4,
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
asset.container.addChild(runoff);
|
|
136
|
+
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function addBoundaryUnderlay(asset, track) {
|
|
140
|
+
const boundary = new Graphics();
|
|
141
|
+
[-1, 1].forEach((side) => {
|
|
142
|
+
drawContinuousOffsetStroke(boundary, track, {
|
|
143
|
+
side,
|
|
144
|
+
offset: track.width / 2 + OUTER_BOUNDARY_OFFSET,
|
|
145
|
+
width: OUTER_BOUNDARY_WIDTH,
|
|
146
|
+
color: 0x090a0d,
|
|
147
|
+
alpha: 0.64,
|
|
148
|
+
step: 2,
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
drawKerbStripes(asset, boundary, track, { clipUnsafeSegments: false });
|
|
153
|
+
|
|
154
|
+
[-1, 1].forEach((side) => {
|
|
155
|
+
drawContinuousOffsetStroke(boundary, track, {
|
|
156
|
+
side,
|
|
157
|
+
offset: track.width / 2 + EDGE_REVEAL_OFFSET,
|
|
158
|
+
width: EDGE_REVEAL_WIDTH,
|
|
159
|
+
color: 0xf8fafc,
|
|
160
|
+
alpha: 0.94,
|
|
161
|
+
step: 2,
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
asset.container.addChild(boundary);
|
|
166
|
+
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function addBarriers(asset, track) {
|
|
170
|
+
const barriers = new Graphics();
|
|
171
|
+
barriers.label = 'track-barriers';
|
|
172
|
+
const bands = getTrackMaterialBands(track);
|
|
173
|
+
const wallWidth = bands.barrier.width;
|
|
174
|
+
const offset = bands.barrier.center;
|
|
175
|
+
[-1, 1].forEach((side) => {
|
|
176
|
+
drawContinuousOffsetStroke(barriers, track, {
|
|
177
|
+
side,
|
|
178
|
+
offset,
|
|
179
|
+
width: wallWidth,
|
|
180
|
+
color: 0x171717,
|
|
181
|
+
alpha: 0.94,
|
|
182
|
+
step: 3,
|
|
183
|
+
});
|
|
184
|
+
drawContinuousOffsetStroke(barriers, track, {
|
|
185
|
+
side,
|
|
186
|
+
offset: offset - wallWidth * 0.7,
|
|
187
|
+
width: wallWidth * 0.35,
|
|
188
|
+
color: 0xf8fafc,
|
|
189
|
+
alpha: 0.42,
|
|
190
|
+
step: 3,
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
asset.container.addChild(barriers);
|
|
194
|
+
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function getTrackMaterialBands(track) {
|
|
198
|
+
const roadEdge = track.width / 2;
|
|
199
|
+
const kerbOuter = roadEdge + (track.kerbWidth ?? 0);
|
|
200
|
+
const gravelOuter = kerbOuter + track.gravelWidth;
|
|
201
|
+
const runoffOuter = gravelOuter + track.runoffWidth;
|
|
202
|
+
const barrierWidth = track.barrierWidth ?? BARRIER_WALL_WIDTH;
|
|
203
|
+
return {
|
|
204
|
+
road: { inner: -roadEdge, outer: roadEdge },
|
|
205
|
+
kerb: { inner: roadEdge, outer: kerbOuter },
|
|
206
|
+
gravel: { inner: kerbOuter, outer: gravelOuter },
|
|
207
|
+
runoff: { inner: gravelOuter, outer: runoffOuter },
|
|
208
|
+
barrier: {
|
|
209
|
+
inner: runoffOuter - barrierWidth / 2,
|
|
210
|
+
center: runoffOuter,
|
|
211
|
+
outer: runoffOuter + barrierWidth / 2,
|
|
212
|
+
width: barrierWidth,
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function drawKerbStripes(asset, kerbs, track, { clipUnsafeSegments }) {
|
|
218
|
+
const samples = track.samples.slice(0, -1);
|
|
219
|
+
|
|
220
|
+
for (let index = 0; index < samples.length; index += KERB_STEP) {
|
|
221
|
+
const sample = samples[index];
|
|
222
|
+
const next = samples[(index + KERB_STEP) % samples.length];
|
|
223
|
+
const curvature = Math.max(sample.curvature, next.curvature);
|
|
224
|
+
if (curvature < KERB_CURVATURE_THRESHOLD) continue;
|
|
225
|
+
|
|
226
|
+
const color = Math.floor(index / KERB_STEP) % 2 === 0 ? 0xe10600 : 0xf8fafc;
|
|
227
|
+
[-1, 1].forEach((side) => {
|
|
228
|
+
const offset = side * (track.width / 2 + KERB_OFFSET);
|
|
229
|
+
const start = offsetTrackPoint(sample, offset);
|
|
230
|
+
const end = offsetTrackPoint(next, offset);
|
|
231
|
+
if (clipUnsafeSegments && !offsetSegmentIsSafe(track, sample, next, start, end, offset)) return;
|
|
232
|
+
kerbs.moveTo(start.x, start.y);
|
|
233
|
+
kerbs.lineTo(end.x, end.y);
|
|
234
|
+
kerbs.stroke({
|
|
235
|
+
width: KERB_WIDTH,
|
|
236
|
+
color,
|
|
237
|
+
alpha: 1,
|
|
238
|
+
join: 'round',
|
|
239
|
+
cap: 'butt',
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function addMaskedMaterial(asset, { track, texture, strokeWidth, alpha, tileScale }) {
|
|
247
|
+
const sprite = new TilingSprite({
|
|
248
|
+
texture: textureOrWhite(texture),
|
|
249
|
+
width: asset.world.width,
|
|
250
|
+
height: asset.world.height,
|
|
251
|
+
tileScale,
|
|
252
|
+
});
|
|
253
|
+
sprite.alpha = alpha;
|
|
254
|
+
|
|
255
|
+
const mask = makeTrackPath(track);
|
|
256
|
+
mask.stroke({
|
|
257
|
+
width: strokeWidth,
|
|
258
|
+
color: 0xffffff,
|
|
259
|
+
alpha: 1,
|
|
260
|
+
join: 'round',
|
|
261
|
+
cap: 'butt',
|
|
262
|
+
});
|
|
263
|
+
mask.renderable = false;
|
|
264
|
+
sprite.mask = mask;
|
|
265
|
+
asset.container.addChild(sprite, mask);
|
|
266
|
+
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function addAsphalt(asset, track) {
|
|
270
|
+
const asphaltBase = makeTrackPath(track);
|
|
271
|
+
asphaltBase.label = 'track-asphalt';
|
|
272
|
+
asphaltBase.stroke({
|
|
273
|
+
width: track.width,
|
|
274
|
+
color: ASPHALT_COLOR,
|
|
275
|
+
alpha: 1,
|
|
276
|
+
join: 'round',
|
|
277
|
+
cap: 'butt',
|
|
278
|
+
});
|
|
279
|
+
asset.container.addChild(asphaltBase);
|
|
280
|
+
|
|
281
|
+
addMaskedMaterial(asset, {
|
|
282
|
+
track,
|
|
283
|
+
texture: asset.textures.asphalt,
|
|
284
|
+
strokeWidth: track.width,
|
|
285
|
+
alpha: 0.18,
|
|
286
|
+
tileScale: MATERIAL_TILE_SCALE.asphalt,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function addBorders(asset, track) {
|
|
293
|
+
const borders = new Graphics();
|
|
294
|
+
borders.label = 'track-borders';
|
|
295
|
+
|
|
296
|
+
[-1, 1].forEach((side) => {
|
|
297
|
+
drawSegmentedOffsetStroke(borders, track, {
|
|
298
|
+
side,
|
|
299
|
+
offset: track.width / 2,
|
|
300
|
+
width: 5,
|
|
301
|
+
color: 0xf8fafc,
|
|
302
|
+
alpha: 0.86,
|
|
303
|
+
step: 4,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
drawSegmentedOffsetStroke(borders, track, {
|
|
307
|
+
side,
|
|
308
|
+
offset: track.width / 2 + 15,
|
|
309
|
+
width: 7,
|
|
310
|
+
color: 0x090a0d,
|
|
311
|
+
alpha: 0.56,
|
|
312
|
+
step: 4,
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
asset.container.addChild(borders);
|
|
317
|
+
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function addKerbs(asset, track) {
|
|
321
|
+
const kerbs = new Graphics();
|
|
322
|
+
kerbs.label = 'track-kerbs';
|
|
323
|
+
drawKerbStripes(asset, kerbs, track, { clipUnsafeSegments: true });
|
|
324
|
+
asset.container.addChild(kerbs);
|
|
325
|
+
|
|
326
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { metersToSimUnits } from '../../simulation/units.js';
|
|
2
|
+
import { TRACK } from '../../simulation/track/trackConstants.js';
|
|
3
|
+
|
|
4
|
+
export const MATERIAL_TILE_SCALE = {
|
|
5
|
+
asphalt: { x: 0.66, y: 0.66 },
|
|
6
|
+
};
|
|
7
|
+
export const WORLD_BACKGROUND_PADDING_MULTIPLIER = 8;
|
|
8
|
+
export const GRASS_COLOR = 0x2e7d32;
|
|
9
|
+
export const RUNOFF_GRASS_COLOR = 0x2f8a3a;
|
|
10
|
+
export const GRAVEL_COLOR = 0xb49a68;
|
|
11
|
+
export const ASPHALT_COLOR = 0x4a4d52;
|
|
12
|
+
export const PIT_ASPHALT_COLOR = ASPHALT_COLOR;
|
|
13
|
+
export const PIT_BOX_COLOR = 0x242831;
|
|
14
|
+
export const PIT_LINE_COLOR = 0xf8fafc;
|
|
15
|
+
export const PIT_SPEED_LINE_COLOR = 0xffd166;
|
|
16
|
+
export const PIT_CONNECTOR_WIDTH = metersToSimUnits(12);
|
|
17
|
+
export const PIT_EDGE_WIDTH = metersToSimUnits(0.35);
|
|
18
|
+
export const EDGE_REVEAL_OFFSET = metersToSimUnits(0.35);
|
|
19
|
+
export const EDGE_REVEAL_WIDTH = metersToSimUnits(0.5);
|
|
20
|
+
export const OUTER_BOUNDARY_OFFSET = metersToSimUnits(1.1);
|
|
21
|
+
export const OUTER_BOUNDARY_WIDTH = metersToSimUnits(0.75);
|
|
22
|
+
export const BARRIER_WALL_WIDTH = TRACK.barrierWidth;
|
|
23
|
+
export const KERB_OFFSET = metersToSimUnits(0.45);
|
|
24
|
+
export const KERB_WIDTH = metersToSimUnits(1.25);
|
|
25
|
+
export const FINISH_LINE_DEPTH = metersToSimUnits(8);
|
|
26
|
+
export const FINISH_LINE_COLUMNS = 10;
|
|
27
|
+
export const START_GRID_SLOT_COUNT = 20;
|
|
28
|
+
export const START_GRID_SLOT_SPACING = metersToSimUnits(8);
|
|
29
|
+
export const START_GRID_FIRST_DISTANCE = metersToSimUnits(-6);
|
|
30
|
+
export const START_GRID_LATERAL_OFFSET = metersToSimUnits(3.2);
|
|
31
|
+
export const START_GRID_BOX_LENGTH = metersToSimUnits(7);
|
|
32
|
+
export const START_GRID_BOX_WIDTH = metersToSimUnits(3.2);
|
|
33
|
+
export const SEGMENTED_STROKE_STEP = 2;
|
|
34
|
+
export const KERB_STEP = 4;
|
|
35
|
+
export const KERB_CURVATURE_THRESHOLD = 0.00038;
|
|
36
|
+
export const OFFSET_SEGMENT_SAMPLE_COUNT = 7;
|
|
37
|
+
export const NON_LOCAL_SAMPLE_STEP = 8;
|
|
38
|
+
export const OFFSET_GAP_SAMPLE_COUNT = 4;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Graphics, Texture } from 'pixi.js';
|
|
2
|
+
import { offsetTrackPoint } from '../../simulation/trackModel.js';
|
|
3
|
+
import { PIT_LINE_COLOR } from './trackRenderConstants.js';
|
|
4
|
+
|
|
5
|
+
export function colorToNumber(color, fallback = PIT_LINE_COLOR) {
|
|
6
|
+
if (typeof color === 'number' && Number.isFinite(color)) return color;
|
|
7
|
+
if (typeof color !== 'string') return fallback;
|
|
8
|
+
const normalized = color.trim().replace(/^#/, '');
|
|
9
|
+
if (!/^[0-9a-f]{6}$/i.test(normalized)) return fallback;
|
|
10
|
+
return Number.parseInt(normalized, 16);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function makeTrackPath(track, offset = 0) {
|
|
14
|
+
const path = new Graphics();
|
|
15
|
+
const samples = track.samples.slice(0, -1);
|
|
16
|
+
const first = offset === 0 ? samples[0] : offsetTrackPoint(samples[0], offset);
|
|
17
|
+
path.moveTo(first.x, first.y);
|
|
18
|
+
|
|
19
|
+
samples.slice(1).forEach((sample) => {
|
|
20
|
+
const point = offset === 0 ? sample : offsetTrackPoint(sample, offset);
|
|
21
|
+
path.lineTo(point.x, point.y);
|
|
22
|
+
});
|
|
23
|
+
path.closePath();
|
|
24
|
+
|
|
25
|
+
return path;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function textureOrWhite(texture) {
|
|
29
|
+
return texture ?? Texture.WHITE;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function destroyDisplayChildren(container) {
|
|
33
|
+
container.removeChildren().forEach((child) => {
|
|
34
|
+
child.destroy?.({ children: true, texture: false, textureSource: false });
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function arcDistance(track, first, second) {
|
|
39
|
+
const delta = Math.abs(first - second);
|
|
40
|
+
return Math.min(delta, track.length - delta);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function interpolatedSegmentPoint(start, end, amount) {
|
|
44
|
+
return {
|
|
45
|
+
x: start.x + (end.x - start.x) * amount,
|
|
46
|
+
y: start.y + (end.y - start.y) * amount,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function pointDistance(first, second) {
|
|
51
|
+
return Math.hypot(second.x - first.x, second.y - first.y);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function drawPolyline(graphics, points, {
|
|
55
|
+
width,
|
|
56
|
+
color,
|
|
57
|
+
alpha = 1,
|
|
58
|
+
cap = 'round',
|
|
59
|
+
join = 'round',
|
|
60
|
+
}) {
|
|
61
|
+
if (!Array.isArray(points) || points.length < 2) return;
|
|
62
|
+
graphics.moveTo(points[0].x, points[0].y);
|
|
63
|
+
points.slice(1).forEach((point) => {
|
|
64
|
+
graphics.lineTo(point.x, point.y);
|
|
65
|
+
});
|
|
66
|
+
graphics.stroke({
|
|
67
|
+
width,
|
|
68
|
+
color,
|
|
69
|
+
alpha,
|
|
70
|
+
cap,
|
|
71
|
+
join,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function offsetVectorPoint(point, vector, amount) {
|
|
76
|
+
return {
|
|
77
|
+
x: point.x + vector.x * amount,
|
|
78
|
+
y: point.y + vector.y * amount,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function createTeamPitGroupCorners(boxes) {
|
|
83
|
+
const ordered = [...boxes].sort((left, right) => left.index - right.index);
|
|
84
|
+
const first = ordered[0];
|
|
85
|
+
const last = ordered.at(-1);
|
|
86
|
+
if (!first || !last) return [];
|
|
87
|
+
|
|
88
|
+
return [
|
|
89
|
+
first.corners[0],
|
|
90
|
+
last.corners[1],
|
|
91
|
+
last.corners[2],
|
|
92
|
+
first.corners[3],
|
|
93
|
+
];
|
|
94
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { metersToSimUnits } from '../units.js';
|
|
2
|
+
|
|
3
|
+
export const LANE_OFFSETS = [-8, -5.6, -3.2, 0, 3.2, 5.6, 8].map(metersToSimUnits);
|
|
4
|
+
export const EDGE_RECOVERY_MIN_LOOKAHEAD = metersToSimUnits(28);
|
|
5
|
+
export const LOOKAHEAD_BASE_DISTANCE = metersToSimUnits(52);
|
|
6
|
+
export const LOOKAHEAD_MAX_DISTANCE = metersToSimUnits(115);
|
|
7
|
+
export const BRAKING_LOOKAHEAD_MAX_DISTANCE = metersToSimUnits(560);
|
|
8
|
+
export const TRAFFIC_GAP_AHEAD = metersToSimUnits(58);
|
|
9
|
+
export const TRAFFIC_SIDE_GAP = metersToSimUnits(4.1);
|
|
10
|
+
export const TRAFFIC_SIDE_OVERLAP = metersToSimUnits(4.6);
|
|
11
|
+
export const TRAFFIC_REAR_WINDOW = metersToSimUnits(42);
|
|
12
|
+
export const OVERTAKE_GAP_BASE = metersToSimUnits(26);
|
|
13
|
+
export const OVERTAKE_GAP_AGGRESSION = metersToSimUnits(28);
|
|
14
|
+
export const OVERTAKE_LATERAL_MIN = metersToSimUnits(2.4);
|
|
15
|
+
export const OVERTAKE_LATERAL_REWARD = metersToSimUnits(1.35);
|
|
16
|
+
export const PASS_SIDE_BASE = metersToSimUnits(3.4);
|
|
17
|
+
export const PASS_SIDE_AGGRESSION = metersToSimUnits(4.6);
|
|
18
|
+
export const DEFEND_REAR_GAP = metersToSimUnits(46);
|
|
19
|
+
export const DEFEND_MIN_OFFSET = metersToSimUnits(2.4);
|
|
20
|
+
export const LANE_EDGE_CLEARANCE_TARGET = metersToSimUnits(0.75);
|
|
21
|
+
export const REJOIN_LOOKAHEAD_BASE = metersToSimUnits(56);
|
|
22
|
+
export const REJOIN_LOOKAHEAD_MAX = metersToSimUnits(105);
|
|
23
|
+
export const SAFETY_CAR_LOOKAHEAD_MIN = metersToSimUnits(24);
|
|
24
|
+
export const SAFETY_CAR_LOOKAHEAD_MAX = metersToSimUnits(48);
|
|
25
|
+
export const CURVATURE_LOOKAHEAD_SAMPLES = 5;
|
|
26
|
+
export const PREVIEW_CURVATURE_CAP = 0.00115;
|
|
27
|
+
export const REJOIN_HOLD_FRAMES = 150;
|
|
28
|
+
export const OVERTAKE_COMMIT_FRAMES = 150;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { REJOIN_HOLD_FRAMES } from './driverControlConstants.js';
|
|
2
|
+
import { createDriverInput } from './driverInput.js';
|
|
3
|
+
import { decideRacingControls } from './racingControls.js';
|
|
4
|
+
import { decideRejoinControls } from './rejoinControls.js';
|
|
5
|
+
import { shouldContinueRejoinRecovery } from './edgeRecovery.js';
|
|
6
|
+
import { decideSafetyCarControls } from './safetyCarControls.js';
|
|
7
|
+
import { VEHICLE_LIMITS } from '../vehicle/vehiclePhysics.js';
|
|
8
|
+
|
|
9
|
+
export function decideDriverControls({ car, orderIndex, race }) {
|
|
10
|
+
if (car.manualControls) return car.manualControls;
|
|
11
|
+
|
|
12
|
+
if (car.gridLocked) {
|
|
13
|
+
return createDriverInput().brake(1).controls();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (race.safetyCar.deployed) {
|
|
17
|
+
return decideSafetyCarControls(car, orderIndex, race);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!car.trackState.onTrack) {
|
|
21
|
+
car.rejoinRecoveryFrames = REJOIN_HOLD_FRAMES;
|
|
22
|
+
car.attackCommitmentFrames = 0;
|
|
23
|
+
return decideRejoinControls(car, race);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (shouldStabilizeSimulatorCar(car, race)) {
|
|
27
|
+
car.rejoinRecoveryFrames = Math.max(car.rejoinRecoveryFrames ?? 0, Math.floor(REJOIN_HOLD_FRAMES * 0.45));
|
|
28
|
+
car.attackCommitmentFrames = 0;
|
|
29
|
+
return decideRejoinControls(car, race);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (shouldContinueRejoinRecovery(car, race)) {
|
|
33
|
+
return decideRejoinControls(car, race);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return decideRacingControls(car, orderIndex, race);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function shouldStabilizeSimulatorCar(car, race) {
|
|
40
|
+
if (race.physicsMode !== 'simulator') return false;
|
|
41
|
+
const gripUsage = car.gripUsage ?? 0;
|
|
42
|
+
const slipAngle = Math.abs(car.slipAngleRadians ?? 0);
|
|
43
|
+
const edgeDistance = race.track.width / 2 - VEHICLE_LIMITS.carWidth * 2.25;
|
|
44
|
+
const nearEdge = (car.trackState?.crossTrackError ?? 0) > edgeDistance;
|
|
45
|
+
const unstable = car.stabilityState === 'spin-risk' || gripUsage > 1.25 || slipAngle > 0.24;
|
|
46
|
+
return unstable && (nearEdge || slipAngle > 0.34 || gripUsage > 1.7);
|
|
47
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { clamp } from '../simMath.js';
|
|
2
|
+
import { VEHICLE_LIMITS } from '../vehicle/vehiclePhysics.js';
|
|
3
|
+
|
|
4
|
+
export function createDriverInput() {
|
|
5
|
+
const state = {
|
|
6
|
+
steering: 0,
|
|
7
|
+
throttle: 0,
|
|
8
|
+
brake: 0,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
steer(amount) {
|
|
13
|
+
state.steering = clamp(amount, -VEHICLE_LIMITS.maxSteer, VEHICLE_LIMITS.maxSteer);
|
|
14
|
+
return this;
|
|
15
|
+
},
|
|
16
|
+
accelerate(amount) {
|
|
17
|
+
state.throttle = clamp(amount, 0, 1);
|
|
18
|
+
return this;
|
|
19
|
+
},
|
|
20
|
+
brake(amount) {
|
|
21
|
+
state.brake = clamp(amount, 0, 1);
|
|
22
|
+
return this;
|
|
23
|
+
},
|
|
24
|
+
controls() {
|
|
25
|
+
return { ...state };
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { clamp, seededRange } from '../simMath.js';
|
|
2
|
+
|
|
3
|
+
export function buildDriverPersonality(driver, index, racecraft, random) {
|
|
4
|
+
const numberSeed = Number(driver.driverNumber ?? index + 1);
|
|
5
|
+
const numberBias = (((Number.isFinite(numberSeed) ? numberSeed : index + 1) % 11) - 5) * 0.014;
|
|
6
|
+
const baseAggression = clamp(
|
|
7
|
+
driver.personality?.aggression ?? 0.36 + racecraft * 0.34 + numberBias + seededRange(random, -0.045, 0.045),
|
|
8
|
+
0.18,
|
|
9
|
+
0.88,
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
baseAggression,
|
|
14
|
+
riskTolerance: clamp(
|
|
15
|
+
driver.personality?.riskTolerance ?? baseAggression * 0.72 + racecraft * 0.24 + seededRange(random, -0.04, 0.04),
|
|
16
|
+
0.12,
|
|
17
|
+
0.95,
|
|
18
|
+
),
|
|
19
|
+
patience: clamp(
|
|
20
|
+
driver.personality?.patience ?? 0.72 - baseAggression * 0.36 + racecraft * 0.12 + seededRange(random, -0.05, 0.05),
|
|
21
|
+
0.18,
|
|
22
|
+
0.9,
|
|
23
|
+
),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { clamp, normalizeAngle } from '../simMath.js';
|
|
2
|
+
import { metersToSimUnits } from '../units.js';
|
|
3
|
+
import { pointAt } from '../track/trackModel.js';
|
|
4
|
+
import { VEHICLE_LIMITS } from '../vehicle/vehiclePhysics.js';
|
|
5
|
+
import { REJOIN_LOOKAHEAD_BASE, REJOIN_LOOKAHEAD_MAX } from './driverControlConstants.js';
|
|
6
|
+
|
|
7
|
+
export function calculateTrackEdgeGuard(car, race) {
|
|
8
|
+
const simulatorMode = race.physicsMode === 'simulator';
|
|
9
|
+
const trackLimit = race.track.width / 2;
|
|
10
|
+
const halfCarWidth = VEHICLE_LIMITS.carWidth / 2;
|
|
11
|
+
const wholeCarOutsideLimit = trackLimit + halfCarWidth;
|
|
12
|
+
const softLimit = trackLimit - halfCarWidth * (simulatorMode ? 3.0 : 1.9);
|
|
13
|
+
const recoveryOffset = trackLimit - halfCarWidth * (simulatorMode ? 2.2 : 1.62);
|
|
14
|
+
const crossTrackError = car.trackState?.crossTrackError ?? 0;
|
|
15
|
+
const side = Math.sign(car.trackState?.signedOffset ?? 0);
|
|
16
|
+
const pressure = clamp(
|
|
17
|
+
(crossTrackError - softLimit) / Math.max(1, wholeCarOutsideLimit - softLimit),
|
|
18
|
+
0,
|
|
19
|
+
1,
|
|
20
|
+
);
|
|
21
|
+
const overLimitPressure = clamp(
|
|
22
|
+
(crossTrackError - wholeCarOutsideLimit) / Math.max(1, (race.track.kerbWidth ?? 0) + halfCarWidth),
|
|
23
|
+
0,
|
|
24
|
+
1,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
softLimit,
|
|
29
|
+
recoveryOffset,
|
|
30
|
+
pressure,
|
|
31
|
+
overLimitPressure,
|
|
32
|
+
side,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function shouldContinueRejoinRecovery(car, race) {
|
|
37
|
+
const frames = Math.max(0, Math.floor(car.rejoinRecoveryFrames ?? 0));
|
|
38
|
+
if (frames <= 0) return false;
|
|
39
|
+
|
|
40
|
+
const lookahead = clamp(car.speed * 0.66 + REJOIN_LOOKAHEAD_BASE, REJOIN_LOOKAHEAD_BASE, REJOIN_LOOKAHEAD_MAX);
|
|
41
|
+
const targetBase = pointAt(race.track, car.progress + lookahead);
|
|
42
|
+
const headingError = Math.abs(normalizeAngle(targetBase.heading - car.heading));
|
|
43
|
+
const edgeBuffer = race.track.width / 2 - VEHICLE_LIMITS.carWidth * 1.35;
|
|
44
|
+
const stillUnsettled = car.trackState.crossTrackError > edgeBuffer || headingError > 0.34;
|
|
45
|
+
|
|
46
|
+
car.rejoinRecoveryFrames = stillUnsettled ? frames - 1 : 0;
|
|
47
|
+
return stillUnsettled;
|
|
48
|
+
}
|