@inventure71/paddockjs 1.0.0 → 2.0.1
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 +58 -0
- package/INSTALL_AND_UPDATE.md +4 -3
- package/README.md +219 -12
- package/docs/architecture.md +126 -45
- package/docs/concepts.md +9 -5
- package/docs/custom_model_controller.md +329 -0
- package/docs/data_contract.md +317 -22
- package/docs/data_model.md +1 -1
- package/docs/index.md +10 -0
- package/docs/learnings.md +22 -0
- package/docs/rules.md +71 -14
- package/docs/sense_contract.md +59 -0
- package/docs/system_specs.md +75 -13
- package/docs/training.md +280 -32
- 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 +6 -33
- package/src/app/BrowserExpertAdapter.js +76 -9
- package/src/app/BrowserExpertExternalRenderer.js +303 -0
- package/src/app/F1SimulatorApp.js +291 -1630
- 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/config/restartOptions.js +34 -0
- package/src/environment/actions.js +13 -5
- package/src/environment/controllerLoop.js +261 -0
- package/src/environment/episode.js +98 -2
- package/src/environment/evaluation.js +54 -32
- package/src/environment/index.d.ts +376 -10
- package/src/environment/index.js +1 -0
- package/src/environment/metrics.js +159 -0
- package/src/environment/observationOptions.js +22 -1
- package/src/environment/observationVector.js +200 -0
- package/src/environment/observations.js +199 -141
- package/src/environment/options.js +37 -5
- package/src/environment/recorder.js +13 -6
- package/src/environment/rewards.js +13 -2
- package/src/environment/runtime.js +272 -23
- package/src/environment/scenarios.js +57 -9
- 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 +75 -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 +66 -0
- package/src/environment/sensors/surfaceRays.js +360 -0
- package/src/environment/sensors/trackRays.js +424 -0
- package/src/environment/sensors.js +1 -367
- package/src/environment/specs.js +159 -26
- package/src/environment/workerProtocol.js +13 -1
- package/src/index.d.ts +171 -6
- package/src/index.js +6 -32
- 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 +239 -0
- package/src/simulation/pit/pitIntent.js +92 -0
- package/src/simulation/pit/pitKinematics.js +24 -0
- package/src/simulation/pit/pitOccupancy.js +47 -0
- package/src/simulation/pit/pitPenaltyService.js +71 -0
- package/src/simulation/pit/pitQueue.js +60 -0
- package/src/simulation/pit/pitRouteMovement.js +167 -0
- package/src/simulation/pit/pitRouting.js +319 -0
- package/src/simulation/pit/pitService.js +43 -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 +94 -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 +67 -0
- package/src/simulation/race/raceOrder.js +113 -0
- package/src/simulation/race/raceProgress.js +129 -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 +92 -0
- package/src/simulation/race/stalledDnf.js +77 -0
- package/src/simulation/raceSimulation.js +231 -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 +144 -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 +193 -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/trackQueryGrid.js +94 -0
- package/src/simulation/track/trackQueryIndex.js +659 -0
- package/src/simulation/track/trackQueryPitIndex.js +140 -0
- package/src/simulation/track/trackQueryScratch.js +46 -0
- package/src/simulation/track/trackQueryStats.js +28 -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 +181 -0
- package/src/simulation/vehicle/mainTrackWheelSurface.js +82 -0
- package/src/simulation/vehicle/pitWheelSurface.js +69 -0
- package/src/simulation/vehicle/runoffResponse.js +87 -0
- package/src/simulation/vehicle/surfacePriority.js +28 -0
- package/src/simulation/vehicle/vehicleGeometry.js +247 -0
- package/src/simulation/vehicle/vehicleKinematics.js +37 -0
- package/src/simulation/vehicle/vehiclePhysics.js +364 -0
- package/src/simulation/vehicle/vehicleSnapshots.js +327 -0
- package/src/simulation/vehicle/vehicleState.js +220 -0
- package/src/simulation/vehicle/wheelSurface.js +152 -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,191 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Neutral PaddockJS policy server base example.
|
|
3
|
+
|
|
4
|
+
This file is intentionally minimal and transport-focused:
|
|
5
|
+
- HTTP policy endpoints:
|
|
6
|
+
- POST /policy/reset
|
|
7
|
+
- POST /policy/reset-state
|
|
8
|
+
- POST /policy/decide-batch
|
|
9
|
+
- WebSocket preview endpoint:
|
|
10
|
+
- /preview (broadcasts {snapshot, observation, meta})
|
|
11
|
+
|
|
12
|
+
Extend `BasePolicyServer` and override hook methods to plug your own model/runtime.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import time
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from typing import Any, Dict, Iterable, List, Mapping, MutableMapping, Optional
|
|
21
|
+
|
|
22
|
+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
|
23
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
24
|
+
from fastapi.responses import JSONResponse
|
|
25
|
+
import uvicorn
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _zero_action() -> Dict[str, float]:
|
|
29
|
+
return {
|
|
30
|
+
"steering": 0.0,
|
|
31
|
+
"throttle": 0.0,
|
|
32
|
+
"brake": 0.0,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ServerState:
|
|
38
|
+
session_id: int = 0
|
|
39
|
+
controlled_drivers: List[str] = field(default_factory=list)
|
|
40
|
+
connections: List[WebSocket] = field(default_factory=list)
|
|
41
|
+
latest_preview_frame: Optional[Dict[str, Any]] = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class BasePolicyServer:
|
|
45
|
+
"""Base extension surface for custom policy/runtime integrations."""
|
|
46
|
+
|
|
47
|
+
def __init__(self) -> None:
|
|
48
|
+
self.app = FastAPI(title="PaddockJS Base Policy Server")
|
|
49
|
+
self.app.add_middleware(
|
|
50
|
+
CORSMiddleware,
|
|
51
|
+
allow_origins=["*"],
|
|
52
|
+
allow_methods=["*"],
|
|
53
|
+
allow_headers=["*"],
|
|
54
|
+
)
|
|
55
|
+
self.state = ServerState()
|
|
56
|
+
self._broadcast_lock = asyncio.Lock()
|
|
57
|
+
self._register_routes()
|
|
58
|
+
|
|
59
|
+
# ----- Extension hooks -------------------------------------------------
|
|
60
|
+
|
|
61
|
+
async def init_policy(self, context: Mapping[str, Any]) -> None:
|
|
62
|
+
"""Initialize policy/runtime resources on full reset."""
|
|
63
|
+
|
|
64
|
+
async def reset_policy(self, context: Mapping[str, Any]) -> None:
|
|
65
|
+
"""Reset all policy runtime state for a new run/session."""
|
|
66
|
+
|
|
67
|
+
async def reset_policy_state(self, driver_ids: Iterable[str]) -> None:
|
|
68
|
+
"""Reset only selected driver-local model state."""
|
|
69
|
+
|
|
70
|
+
async def decide_batch(self, context: Mapping[str, Any]) -> Dict[str, Dict[str, float]]:
|
|
71
|
+
"""Return per-driver controls for one batched policy step."""
|
|
72
|
+
driver_ids = list(context.get("driverIds") or [])
|
|
73
|
+
return {driver_id: _zero_action() for driver_id in driver_ids}
|
|
74
|
+
|
|
75
|
+
async def publish_preview_frame(self, frame: Mapping[str, Any]) -> None:
|
|
76
|
+
"""Optional extension hook called before broadcasting preview frames."""
|
|
77
|
+
|
|
78
|
+
# ----- Public helpers --------------------------------------------------
|
|
79
|
+
|
|
80
|
+
async def broadcast_preview_frame(
|
|
81
|
+
self,
|
|
82
|
+
snapshot: Mapping[str, Any],
|
|
83
|
+
observation: Mapping[str, Any],
|
|
84
|
+
meta: Optional[Mapping[str, Any]] = None,
|
|
85
|
+
) -> None:
|
|
86
|
+
"""Broadcast one authoritative preview frame to websocket clients."""
|
|
87
|
+
frame = {
|
|
88
|
+
"type": "preview:snapshot",
|
|
89
|
+
"snapshot": dict(snapshot),
|
|
90
|
+
"observation": dict(observation),
|
|
91
|
+
"meta": dict(meta or {}),
|
|
92
|
+
}
|
|
93
|
+
self.state.latest_preview_frame = frame
|
|
94
|
+
await self.publish_preview_frame(frame)
|
|
95
|
+
payload = frame
|
|
96
|
+
async with self._broadcast_lock:
|
|
97
|
+
stale: List[WebSocket] = []
|
|
98
|
+
for ws in self.state.connections:
|
|
99
|
+
try:
|
|
100
|
+
await ws.send_json(payload)
|
|
101
|
+
except Exception:
|
|
102
|
+
stale.append(ws)
|
|
103
|
+
if stale:
|
|
104
|
+
self.state.connections = [ws for ws in self.state.connections if ws not in stale]
|
|
105
|
+
|
|
106
|
+
# ----- Internal routing ------------------------------------------------
|
|
107
|
+
|
|
108
|
+
def _register_routes(self) -> None:
|
|
109
|
+
@self.app.get("/preview/frame")
|
|
110
|
+
async def preview_frame() -> JSONResponse:
|
|
111
|
+
if self.state.latest_preview_frame:
|
|
112
|
+
return JSONResponse(self.state.latest_preview_frame)
|
|
113
|
+
return JSONResponse(
|
|
114
|
+
{
|
|
115
|
+
"type": "preview:status",
|
|
116
|
+
"status": "waiting-for-frame",
|
|
117
|
+
"session": {"id": self.state.session_id},
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
@self.app.post("/policy/reset")
|
|
122
|
+
async def policy_reset(body: MutableMapping[str, Any]) -> JSONResponse:
|
|
123
|
+
self.state.session_id += 1
|
|
124
|
+
self.state.controlled_drivers = list(body.get("driverIds") or [])
|
|
125
|
+
await self.init_policy(body)
|
|
126
|
+
await self.reset_policy(body)
|
|
127
|
+
return JSONResponse(
|
|
128
|
+
{
|
|
129
|
+
"ok": True,
|
|
130
|
+
"session": {
|
|
131
|
+
"id": self.state.session_id,
|
|
132
|
+
"controlledDrivers": self.state.controlled_drivers,
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
@self.app.post("/policy/reset-state")
|
|
138
|
+
async def policy_reset_state(body: MutableMapping[str, Any]) -> JSONResponse:
|
|
139
|
+
driver_ids = list(body.get("driverIds") or [])
|
|
140
|
+
await self.reset_policy_state(driver_ids)
|
|
141
|
+
return JSONResponse(
|
|
142
|
+
{
|
|
143
|
+
"ok": True,
|
|
144
|
+
"session": {"id": self.state.session_id},
|
|
145
|
+
"resetDrivers": driver_ids,
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
@self.app.post("/policy/decide-batch")
|
|
150
|
+
async def policy_decide_batch(body: MutableMapping[str, Any]) -> JSONResponse:
|
|
151
|
+
actions = await self.decide_batch(body)
|
|
152
|
+
if not isinstance(actions, dict):
|
|
153
|
+
actions = {}
|
|
154
|
+
now_ms = int(time.time() * 1000)
|
|
155
|
+
return JSONResponse(
|
|
156
|
+
{
|
|
157
|
+
"ok": True,
|
|
158
|
+
"session": {"id": self.state.session_id},
|
|
159
|
+
"actions": actions,
|
|
160
|
+
"meta": {"serverTsMs": now_ms},
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
@self.app.websocket("/preview")
|
|
165
|
+
async def preview_stream(ws: WebSocket) -> None:
|
|
166
|
+
await ws.accept()
|
|
167
|
+
self.state.connections.append(ws)
|
|
168
|
+
try:
|
|
169
|
+
await ws.send_json(
|
|
170
|
+
{
|
|
171
|
+
"type": "preview:status",
|
|
172
|
+
"status": "connected",
|
|
173
|
+
"session": {"id": self.state.session_id},
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
while True:
|
|
177
|
+
# Keep connection alive; ignore client messages.
|
|
178
|
+
await ws.receive_text()
|
|
179
|
+
except WebSocketDisconnect:
|
|
180
|
+
pass
|
|
181
|
+
finally:
|
|
182
|
+
self.state.connections = [conn for conn in self.state.connections if conn is not ws]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def run(host: str = "127.0.0.1", port: int = 8787) -> None:
|
|
186
|
+
server = BasePolicyServer()
|
|
187
|
+
uvicorn.run(server.app, host=host, port=port, log_level="info")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == "__main__":
|
|
191
|
+
run()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inventure71/paddockjs",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A data-driven browser F1 simulator component for host websites.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -53,7 +53,11 @@
|
|
|
53
53
|
"LICENSE",
|
|
54
54
|
"assets",
|
|
55
55
|
"docs/*.md",
|
|
56
|
-
"examples",
|
|
56
|
+
"examples/python/README.md",
|
|
57
|
+
"examples/python/base_policy_server.py",
|
|
58
|
+
"examples/python/requirements.txt",
|
|
59
|
+
"examples/train-basic-policy.mjs",
|
|
60
|
+
"examples/trainingData.mjs",
|
|
57
61
|
"src/api",
|
|
58
62
|
"src/app",
|
|
59
63
|
"src/config",
|
|
@@ -68,12 +72,16 @@
|
|
|
68
72
|
"README.md"
|
|
69
73
|
],
|
|
70
74
|
"scripts": {
|
|
71
|
-
"test": "
|
|
75
|
+
"test": "node scripts/run-vitest.mjs --slow",
|
|
76
|
+
"test:fast": "node scripts/run-vitest.mjs",
|
|
72
77
|
"types:check": "tsc -p tsconfig.types.json --noEmit",
|
|
73
|
-
"check": "npm test && npm run types:check && npm run pack:dry && npm run consumer:smoke && npm run showcase:ci && npm run browser:smoke",
|
|
78
|
+
"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",
|
|
79
|
+
"check:release": "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",
|
|
74
80
|
"pack:dry": "npm pack --dry-run",
|
|
75
81
|
"consumer:smoke": "node scripts/consumer-package-smoke.mjs",
|
|
76
82
|
"browser:smoke": "node smoke/browser-smoke.mjs",
|
|
83
|
+
"browser:smoke:quick": "node smoke/browser-smoke.mjs --quick",
|
|
84
|
+
"browser:smoke:full": "node smoke/browser-smoke.mjs --full",
|
|
77
85
|
"showcase:install": "npm --prefix local-preview ci",
|
|
78
86
|
"showcase:dev": "npm --prefix local-preview run dev",
|
|
79
87
|
"showcase:build": "npm --prefix local-preview run build",
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
createTimingTowerMarkup,
|
|
19
19
|
} from '../ui/componentTemplates.js';
|
|
20
20
|
import { applyPaddockThemeCssVariables, resolveF1SimulatorOptions } from '../config/defaultOptions.js';
|
|
21
|
+
import { mergeRestartOptions } from '../config/restartOptions.js';
|
|
21
22
|
|
|
22
23
|
function assertMountTarget(root, label) {
|
|
23
24
|
if (!root || typeof root !== 'object' || !('innerHTML' in root)) {
|
|
@@ -31,38 +32,6 @@ function setPackageCssVariables(root, assets, theme) {
|
|
|
31
32
|
applyPaddockThemeCssVariables(root, theme);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
function mergeResolvedOptions(previousOptions, nextOptions) {
|
|
35
|
-
const resetFromPreset = Object.hasOwn(nextOptions, 'preset');
|
|
36
|
-
const previousUi = resetFromPreset ? {} : previousOptions.ui;
|
|
37
|
-
const previousTheme = resetFromPreset ? {} : previousOptions.theme;
|
|
38
|
-
return {
|
|
39
|
-
...previousOptions,
|
|
40
|
-
...nextOptions,
|
|
41
|
-
ui: {
|
|
42
|
-
...previousUi,
|
|
43
|
-
...(nextOptions.ui ?? {}),
|
|
44
|
-
raceDataBanners: {
|
|
45
|
-
...(previousUi.raceDataBanners ?? {}),
|
|
46
|
-
...(nextOptions.ui?.raceDataBanners ?? {}),
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
theme: {
|
|
50
|
-
...previousTheme,
|
|
51
|
-
...(nextOptions.theme ?? {}),
|
|
52
|
-
},
|
|
53
|
-
assets: {
|
|
54
|
-
...previousOptions.assets,
|
|
55
|
-
...(nextOptions.assets ?? {}),
|
|
56
|
-
trackTextures: {
|
|
57
|
-
...previousOptions.assets.trackTextures,
|
|
58
|
-
...(nextOptions.assets?.trackTextures ?? {}),
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
drivers: nextOptions.drivers ?? previousOptions.drivers,
|
|
62
|
-
entries: nextOptions.entries ?? previousOptions.entries,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
35
|
function createCompositeRoot(getRoots, getOptions) {
|
|
67
36
|
return {
|
|
68
37
|
style: {
|
|
@@ -203,7 +172,7 @@ export class PaddockSimulatorController {
|
|
|
203
172
|
}
|
|
204
173
|
|
|
205
174
|
restart(nextOptions = {}) {
|
|
206
|
-
const nextResolvedOptions = resolveF1SimulatorOptions(
|
|
175
|
+
const nextResolvedOptions = resolveF1SimulatorOptions(mergeRestartOptions(this.options, nextOptions));
|
|
207
176
|
this.app?.restart(nextResolvedOptions);
|
|
208
177
|
this.options = nextResolvedOptions;
|
|
209
178
|
this.compositeRoot.applyCssVariables();
|
|
@@ -250,6 +219,10 @@ export class PaddockSimulatorController {
|
|
|
250
219
|
return this.app?.getPitTargetCompound?.(driverId) ?? null;
|
|
251
220
|
}
|
|
252
221
|
|
|
222
|
+
getSimulationSpeed() {
|
|
223
|
+
return this.app?.simulationSpeed ?? 1;
|
|
224
|
+
}
|
|
225
|
+
|
|
253
226
|
servePenalty(penaltyId) {
|
|
254
227
|
return this.app?.servePenalty(penaltyId) ?? null;
|
|
255
228
|
}
|
|
@@ -1,14 +1,52 @@
|
|
|
1
1
|
import { createEnvironmentRuntime } from '../environment/runtime.js';
|
|
2
2
|
import { resolveEnvironmentOptions } from '../environment/options.js';
|
|
3
|
+
import { createBrowserExpertExternalRendererBridge } from './BrowserExpertExternalRenderer.js';
|
|
3
4
|
|
|
4
5
|
export function createBrowserExpertAdapter(app, expertOptions = {}) {
|
|
5
|
-
let resolvedOptions =
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
})
|
|
6
|
+
let resolvedOptions = resolveBrowserExpertOptions(app.options, expertOptions);
|
|
7
|
+
let frameRenderSuppressed = false;
|
|
8
|
+
const externalRenderer = createBrowserExpertExternalRendererBridge(app);
|
|
9
|
+
|
|
10
|
+
function resolveBrowserExpertOptions(appOptions, nextExpertOptions = {}) {
|
|
11
|
+
const options = resolveEnvironmentOptions({
|
|
12
|
+
...appOptions,
|
|
13
|
+
...nextExpertOptions,
|
|
14
|
+
controlledDrivers: nextExpertOptions.controlledDrivers,
|
|
15
|
+
});
|
|
16
|
+
if (options.result.stateOutput !== 'none') return options;
|
|
17
|
+
return {
|
|
18
|
+
...options,
|
|
19
|
+
result: {
|
|
20
|
+
...options.result,
|
|
21
|
+
stateOutput: 'minimal',
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function renderableSnapshot(result) {
|
|
27
|
+
return result?.state?.snapshot ?? app.sim?.snapshotObservation?.() ?? app.sim?.snapshot?.() ?? null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/*
|
|
31
|
+
* Browser expert mode is a visual adapter. Compact headless options may ask for
|
|
32
|
+
* no state payload, but the canvas still needs a snapshot to render.
|
|
33
|
+
*/
|
|
34
|
+
function renderExpertResult(result, renderOptions = {}) {
|
|
35
|
+
const snapshot = renderableSnapshot(result);
|
|
36
|
+
if (!snapshot) return;
|
|
37
|
+
app.renderExpertFrame(snapshot, {
|
|
38
|
+
...renderOptions,
|
|
39
|
+
observation: result?.observation,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function initialResolvedOptions() {
|
|
44
|
+
return resolveBrowserExpertOptions(app.options, expertOptions);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
resolvedOptions = initialResolvedOptions();
|
|
10
48
|
|
|
11
|
-
|
|
49
|
+
const runtime = createEnvironmentRuntime({
|
|
12
50
|
getSimulation: () => app.sim,
|
|
13
51
|
setSimulation(nextSim) {
|
|
14
52
|
app.sim = nextSim;
|
|
@@ -19,14 +57,43 @@ export function createBrowserExpertAdapter(app, expertOptions = {}) {
|
|
|
19
57
|
},
|
|
20
58
|
getOptions: () => resolvedOptions,
|
|
21
59
|
setOptions(nextOptions) {
|
|
22
|
-
resolvedOptions = nextOptions
|
|
60
|
+
resolvedOptions = nextOptions.result?.stateOutput === 'none'
|
|
61
|
+
? { ...nextOptions, result: { ...nextOptions.result, stateOutput: 'minimal' } }
|
|
62
|
+
: nextOptions;
|
|
23
63
|
},
|
|
24
64
|
afterReset(result) {
|
|
25
65
|
app.renderTrack();
|
|
26
|
-
|
|
66
|
+
renderExpertResult(result, { forceDomUpdate: true });
|
|
27
67
|
},
|
|
28
68
|
afterStep(result) {
|
|
29
|
-
|
|
69
|
+
if (frameRenderSuppressed) return;
|
|
70
|
+
renderExpertResult(result);
|
|
30
71
|
},
|
|
31
72
|
});
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
...runtime,
|
|
76
|
+
reset(options = {}) {
|
|
77
|
+
externalRenderer.assertDetached('reset');
|
|
78
|
+
return runtime.reset(options);
|
|
79
|
+
},
|
|
80
|
+
step(actions = {}) {
|
|
81
|
+
externalRenderer.assertDetached('step');
|
|
82
|
+
return runtime.step(actions);
|
|
83
|
+
},
|
|
84
|
+
resetDrivers(placements = {}, resultOptions = {}) {
|
|
85
|
+
externalRenderer.assertDetached('resetDrivers');
|
|
86
|
+
return runtime.resetDrivers(placements, resultOptions);
|
|
87
|
+
},
|
|
88
|
+
destroy() {
|
|
89
|
+
externalRenderer.detach();
|
|
90
|
+
runtime.destroy();
|
|
91
|
+
},
|
|
92
|
+
setFrameRenderSuppressed(suppressed) {
|
|
93
|
+
frameRenderSuppressed = Boolean(suppressed);
|
|
94
|
+
},
|
|
95
|
+
attachExternalRenderer: externalRenderer.attach,
|
|
96
|
+
detachExternalRenderer: externalRenderer.detach,
|
|
97
|
+
getExternalRendererState: externalRenderer.getState,
|
|
98
|
+
};
|
|
32
99
|
}
|