@inventure71/paddockjs 4.2.0 → 4.3.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 +40 -0
- package/INSTALL_AND_UPDATE.md +24 -4
- package/README.md +31 -2
- package/docs/advanced_ai_pace.md +131 -0
- package/docs/advanced_physics_rebuild.md +166 -0
- package/docs/architecture.md +109 -30
- package/docs/cleanup_coverage.md +284 -0
- package/docs/component_inventory.md +31 -15
- 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/grid_walk.md +84 -0
- package/docs/index.md +1 -0
- package/docs/keyboard_driving_fix.md +118 -0
- package/docs/limit_driving_2026-10-14.md +88 -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 +38 -11
- package/docs/testing-reliability-guide.md +5 -0
- package/docs/v11_improvements.md +108 -0
- package/package.json +24 -13
- 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 +106 -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 +13 -259
- 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 +21 -140
- 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 +34 -259
- 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 +5 -11
- 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 +14 -3
- package/src/simulation/driver/edgeRecovery.js +0 -1
- package/src/simulation/driver/racingControls.js +10 -406
- package/src/simulation/driver/racingDriverProfile.js +48 -0
- package/src/simulation/driver/racingLinePlan.js +48 -15
- package/src/simulation/driver/rejoinControls.js +58 -111
- package/src/simulation/driver/safetyCarControls.js +12 -0
- package/src/simulation/driver/trafficScan.js +89 -6
- 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 +1 -1
- package/src/simulation/race/redFlag.js +2 -4
- package/src/simulation/race/safetyCar.js +3 -8
- 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/rulesReview.js +1 -7
- 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 +3 -3
- 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 +1 -25
- 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 +4 -241
- 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 +20 -6
- 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 +0 -56
- package/src/simulation/vehicle/vehicleKinematics.js +10 -0
- package/src/simulation/vehicle/vehiclePhysics.js +35 -194
- package/src/simulation/vehicle/vehicleSnapshots.js +13 -4
- package/src/simulation/vehicle/vehicleState.js +2 -1
- 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/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
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { DEFAULT_F1_SIMULATOR_OPTIONS } from './simulatorDefaults.js';
|
|
2
|
+
import { normalizeTelemetryModules } from './telemetryModules.js';
|
|
3
|
+
import { normalizeTimingGapMode } from './timingGapMode.js';
|
|
4
|
+
|
|
5
|
+
export function resolveSimulatorUiOptions(requestedUi = {}, presetUi = {}, initialCameraMode = 'leader') {
|
|
6
|
+
const ui = {
|
|
7
|
+
...DEFAULT_F1_SIMULATOR_OPTIONS.ui,
|
|
8
|
+
...presetUi,
|
|
9
|
+
...requestedUi,
|
|
10
|
+
raceDataBanners: {
|
|
11
|
+
...DEFAULT_F1_SIMULATOR_OPTIONS.ui.raceDataBanners,
|
|
12
|
+
...(presetUi.raceDataBanners ?? {}),
|
|
13
|
+
...(requestedUi.raceDataBanners ?? {}),
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
ui.driverCamera = Boolean(ui.driverCamera || initialCameraMode === 'driver');
|
|
17
|
+
ui.viewControls = ui.viewControls === 'toggle' ? 'toggle' : 'switch';
|
|
18
|
+
ui.raceDataBanners.enabled = normalizeEnabledBanners(ui.raceDataBanners.enabled);
|
|
19
|
+
ui.telemetryModules = normalizeTelemetryModules(ui.telemetryModules, DEFAULT_F1_SIMULATOR_OPTIONS.ui.telemetryModules);
|
|
20
|
+
if (!['project', 'radio', 'hidden'].includes(ui.raceDataBanners.initial)) {
|
|
21
|
+
ui.raceDataBanners.initial = DEFAULT_F1_SIMULATOR_OPTIONS.ui.raceDataBanners.initial;
|
|
22
|
+
}
|
|
23
|
+
if (ui.raceDataBanners.initial !== 'hidden' && !ui.raceDataBanners.enabled.includes(ui.raceDataBanners.initial)) {
|
|
24
|
+
ui.raceDataBanners.initial = 'hidden';
|
|
25
|
+
}
|
|
26
|
+
if (!['expand-race-view', 'scroll'].includes(ui.timingTowerVerticalFit)) {
|
|
27
|
+
ui.timingTowerVerticalFit = DEFAULT_F1_SIMULATOR_OPTIONS.ui.timingTowerVerticalFit;
|
|
28
|
+
}
|
|
29
|
+
ui.timingGapMode = normalizeTimingGapMode(ui.timingGapMode);
|
|
30
|
+
ui.timingGapModeToggle = ui.timingGapModeToggle !== false;
|
|
31
|
+
ui.timingEntryVerticalPadding = normalizeNonNegativeFiniteNumber(
|
|
32
|
+
ui.timingEntryVerticalPadding,
|
|
33
|
+
DEFAULT_F1_SIMULATOR_OPTIONS.ui.timingEntryVerticalPadding,
|
|
34
|
+
);
|
|
35
|
+
ui.responsiveNarrowLayout = ui.responsiveNarrowLayout !== false;
|
|
36
|
+
if (!['auto', 'custom'].includes(ui.raceDataBannerSize)) {
|
|
37
|
+
ui.raceDataBannerSize = DEFAULT_F1_SIMULATOR_OPTIONS.ui.raceDataBannerSize;
|
|
38
|
+
}
|
|
39
|
+
ui.raceDataTelemetryDetail = Boolean(ui.raceDataTelemetryDetail);
|
|
40
|
+
return ui;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalizeNonNegativeFiniteNumber(value, fallback) {
|
|
44
|
+
const numericValue = Number(value);
|
|
45
|
+
return Number.isFinite(numericValue) && numericValue >= 0 ? numericValue : fallback;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeEnabledBanners(value) {
|
|
49
|
+
if (value === false) return [];
|
|
50
|
+
if (value === true || value == null) return [...DEFAULT_F1_SIMULATOR_OPTIONS.ui.raceDataBanners.enabled];
|
|
51
|
+
if (!Array.isArray(value)) return [...DEFAULT_F1_SIMULATOR_OPTIONS.ui.raceDataBanners.enabled];
|
|
52
|
+
return [...new Set(value.filter((item) => item === 'project' || item === 'radio'))];
|
|
53
|
+
}
|
package/src/data/championship.js
CHANGED
|
@@ -1,147 +1,26 @@
|
|
|
1
1
|
import { DriverData } from './driverData.js';
|
|
2
2
|
import { DEMO_PROJECT_DRIVERS } from './demoDrivers.js';
|
|
3
3
|
import { VehicleData } from './vehicleData.js';
|
|
4
|
-
import {
|
|
4
|
+
import { normalizeEntryPresentation } from './entryPresentation.js';
|
|
5
|
+
import { DEFAULT_DARK_THEME_TOKENS } from '../config/theme/themeTokens.js';
|
|
6
|
+
import { normalizeCssColorToken } from '../config/theme/cssTokens.js';
|
|
7
|
+
import { CHAMPIONSHIP, CHAMPIONSHIP_ENTRY_BLUEPRINTS, CHAMPIONSHIP_DRIVER_ENTRIES } from './championshipDefaults.js';
|
|
8
|
+
import { normalizeTeam, pickUniqueTimingCode } from './entryIdentity.js';
|
|
5
9
|
|
|
6
10
|
export { DriverData, DRIVER_STAT_DEFINITIONS } from './driverData.js';
|
|
7
11
|
export { VehicleData, VEHICLE_STAT_DEFINITIONS } from './vehicleData.js';
|
|
8
|
-
|
|
9
|
-
export
|
|
10
|
-
id: 'project-race-2026',
|
|
11
|
-
name: 'Project Race',
|
|
12
|
-
season: 2026,
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export const CHAMPIONSHIP_ENTRY_BLUEPRINTS = [
|
|
16
|
-
{
|
|
17
|
-
driverId: 'budget',
|
|
18
|
-
driverNumber: 71,
|
|
19
|
-
timingName: 'Budget',
|
|
20
|
-
driver: new DriverData({ pace: 52, racecraft: 74, aggression: 38, riskTolerance: 47, patience: 81, consistency: 86 }),
|
|
21
|
-
vehicle: new VehicleData({ id: 'budget-bb01', name: 'BB-01 Ledger', power: 48, braking: 72, aero: 55, dragEfficiency: 66, mechanicalGrip: 63, weightControl: 58, tireCare: 82 }),
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
driverId: 'noir',
|
|
25
|
-
driverNumber: 13,
|
|
26
|
-
timingName: 'Noir',
|
|
27
|
-
driver: new DriverData({ pace: 68, racecraft: 83, aggression: 61, riskTolerance: 72, patience: 57, consistency: 64 }),
|
|
28
|
-
vehicle: new VehicleData({ id: 'noir-nn13', name: 'NN-13 Shadow', power: 64, braking: 60, aero: 73, dragEfficiency: 57, mechanicalGrip: 69, weightControl: 54, tireCare: 51 }),
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
driverId: 'vinyl',
|
|
32
|
-
driverNumber: 33,
|
|
33
|
-
timingName: 'HoloVinyl',
|
|
34
|
-
driver: new DriverData({ pace: 71, racecraft: 66, aggression: 52, riskTolerance: 59, patience: 62, consistency: 58 }),
|
|
35
|
-
vehicle: new VehicleData({ id: 'vinyl-hv33', name: 'HV-33 Groove', power: 70, braking: 49, aero: 58, dragEfficiency: 74, mechanicalGrip: 52, weightControl: 68, tireCare: 46 }),
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
driverId: 'drsorriso',
|
|
39
|
-
driverNumber: 55,
|
|
40
|
-
timingName: 'DrSorriso',
|
|
41
|
-
driver: new DriverData({ pace: 46, racecraft: 69, aggression: 44, riskTolerance: 43, patience: 76, consistency: 73 }),
|
|
42
|
-
vehicle: new VehicleData({ id: 'sorriso-ds55', name: 'DS-55 Intake', power: 54, braking: 67, aero: 62, dragEfficiency: 49, mechanicalGrip: 71, weightControl: 45, tireCare: 69 }),
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
driverId: 'victoria',
|
|
46
|
-
driverNumber: 11,
|
|
47
|
-
timingName: 'VictorIA',
|
|
48
|
-
driver: new DriverData({ pace: 78, racecraft: 79, aggression: 58, riskTolerance: 65, patience: 55, consistency: 68 }),
|
|
49
|
-
vehicle: new VehicleData({ id: 'victoria-vi11', name: 'VI-11 Vector', power: 76, braking: 58, aero: 65, dragEfficiency: 63, mechanicalGrip: 61, weightControl: 60, tireCare: 48 }),
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
driverId: 'reminderz',
|
|
53
|
-
driverNumber: 7,
|
|
54
|
-
timingName: 'ReminderZ',
|
|
55
|
-
driver: new DriverData({ pace: 41, racecraft: 61, aggression: 34, riskTolerance: 39, patience: 85, consistency: 80 }),
|
|
56
|
-
vehicle: new VehicleData({ id: 'reminderz-rz07', name: 'RZ-07 Recall', power: 42, braking: 76, aero: 50, dragEfficiency: 72, mechanicalGrip: 58, weightControl: 63, tireCare: 88 }),
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
driverId: 'clipclop',
|
|
60
|
-
driverNumber: 14,
|
|
61
|
-
timingName: 'ClipClop',
|
|
62
|
-
driver: new DriverData({ pace: 73, racecraft: 88, aggression: 76, riskTolerance: 78, patience: 48, consistency: 61 }),
|
|
63
|
-
vehicle: new VehicleData({ id: 'clipclop-cc14', name: 'CC-14 Sync', power: 68, braking: 63, aero: 59, dragEfficiency: 61, mechanicalGrip: 78, weightControl: 56, tireCare: 44 }),
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
driverId: 'evolve',
|
|
67
|
-
driverNumber: 23,
|
|
68
|
-
timingName: 'Evolve',
|
|
69
|
-
driver: new DriverData({ pace: 56, racecraft: 70, aggression: 63, riskTolerance: 61, patience: 58, consistency: 52 }),
|
|
70
|
-
vehicle: new VehicleData({ id: 'evolve-ev23', name: 'EV-23 Morph', power: 60, braking: 53, aero: 79, dragEfficiency: 46, mechanicalGrip: 66, weightControl: 49, tireCare: 57 }),
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
driverId: 'clash',
|
|
74
|
-
driverNumber: 47,
|
|
75
|
-
timingName: 'Clash',
|
|
76
|
-
driver: new DriverData({ pace: 44, racecraft: 67, aggression: 70, riskTolerance: 69, patience: 42, consistency: 59 }),
|
|
77
|
-
vehicle: new VehicleData({ id: 'clash-cr47', name: 'CR-47 Arena', power: 57, braking: 48, aero: 52, dragEfficiency: 55, mechanicalGrip: 73, weightControl: 52, tireCare: 64 }),
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
driverId: 'core',
|
|
81
|
-
driverNumber: 91,
|
|
82
|
-
timingName: 'Core',
|
|
83
|
-
driver: new DriverData({ pace: 82, racecraft: 85, aggression: 67, riskTolerance: 74, patience: 53, consistency: 72 }),
|
|
84
|
-
vehicle: new VehicleData({ id: 'core-ai91', name: 'AI-91 Core', power: 80, braking: 61, aero: 72, dragEfficiency: 59, mechanicalGrip: 75, weightControl: 51, tireCare: 50 }),
|
|
85
|
-
},
|
|
86
|
-
];
|
|
87
|
-
|
|
88
|
-
export const CHAMPIONSHIP_DRIVER_ENTRIES = CHAMPIONSHIP_ENTRY_BLUEPRINTS.map((entry) => ({
|
|
89
|
-
driverId: entry.driverId,
|
|
90
|
-
driverNumber: entry.driverNumber,
|
|
91
|
-
timingName: entry.timingName,
|
|
92
|
-
}));
|
|
93
|
-
|
|
94
|
-
const LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
95
|
-
|
|
96
|
-
function normalizeLetters(value) {
|
|
97
|
-
return String(value ?? '')
|
|
98
|
-
.normalize('NFKD')
|
|
99
|
-
.replace(/[\u0300-\u036f]/g, '')
|
|
100
|
-
.replace(/[^a-zA-Z]/g, '')
|
|
101
|
-
.toUpperCase();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function timingCodeCandidates(source) {
|
|
105
|
-
const letters = normalizeLetters(source);
|
|
106
|
-
if (!letters) return ['XXX'];
|
|
107
|
-
if (letters.length <= 3) return [letters.padEnd(3, 'X')];
|
|
108
|
-
|
|
109
|
-
const candidates = [letters.slice(0, 3)];
|
|
110
|
-
for (let index = 1; index <= letters.length - 3; index += 1) {
|
|
111
|
-
candidates.push(letters.slice(index, index + 3));
|
|
112
|
-
}
|
|
113
|
-
candidates.push(`${letters[0]}${letters[1]}${letters[letters.length - 1]}`);
|
|
114
|
-
return [...new Set(candidates)];
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function pickUniqueTimingCode(source, usedCodes) {
|
|
118
|
-
for (const candidate of timingCodeCandidates(source)) {
|
|
119
|
-
if (!usedCodes.has(candidate)) {
|
|
120
|
-
usedCodes.add(candidate);
|
|
121
|
-
return candidate;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const letters = normalizeLetters(source).padEnd(2, 'X');
|
|
126
|
-
for (const suffix of LETTERS) {
|
|
127
|
-
const candidate = `${letters[0]}${letters[1]}${suffix}`;
|
|
128
|
-
if (!usedCodes.has(candidate)) {
|
|
129
|
-
usedCodes.add(candidate);
|
|
130
|
-
return candidate;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
throw new Error(`Unable to create a unique three-letter timing code for "${source}"`);
|
|
135
|
-
}
|
|
12
|
+
export { CHAMPIONSHIP, CHAMPIONSHIP_ENTRY_BLUEPRINTS, CHAMPIONSHIP_DRIVER_ENTRIES } from './championshipDefaults.js';
|
|
13
|
+
export { formatDriverNumber } from './entryIdentity.js';
|
|
136
14
|
|
|
137
15
|
function assertUniqueDriverNumbers(entries) {
|
|
138
|
-
const seen = new
|
|
16
|
+
const seen = new Map();
|
|
139
17
|
entries.forEach((entry) => {
|
|
140
18
|
if (entry.driverNumber == null) return;
|
|
19
|
+
const driverId = entry.driverId ?? entry.id;
|
|
141
20
|
if (seen.has(entry.driverNumber)) {
|
|
142
|
-
throw new Error(`Duplicate championship driver number: ${entry.driverNumber}`);
|
|
21
|
+
throw new Error(`Duplicate championship driver number: ${entry.driverNumber} (${seen.get(entry.driverNumber)} and ${driverId})`);
|
|
143
22
|
}
|
|
144
|
-
seen.
|
|
23
|
+
seen.set(entry.driverNumber, driverId);
|
|
145
24
|
});
|
|
146
25
|
}
|
|
147
26
|
|
|
@@ -170,29 +49,6 @@ function buildVehicleConstructorArgs(entry) {
|
|
|
170
49
|
return vehicle.toConstructorArgs();
|
|
171
50
|
}
|
|
172
51
|
|
|
173
|
-
export function formatDriverNumber(driverNumber) {
|
|
174
|
-
return String(driverNumber ?? '').padStart(2, '0');
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function normalizeTeamIcon(source) {
|
|
178
|
-
const letters = normalizeLetters(source);
|
|
179
|
-
return letters.slice(0, 2).padEnd(2, 'X');
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function normalizeTeam(team, driver, timingCode) {
|
|
183
|
-
const source = team ?? driver.team;
|
|
184
|
-
if (!source) return null;
|
|
185
|
-
const name = source.name ?? source.id ?? `${driver.name ?? timingCode} Team`;
|
|
186
|
-
const id = (source.id ?? normalizeLetters(name).toLowerCase()) || `${driver.id}-team`;
|
|
187
|
-
return {
|
|
188
|
-
...source,
|
|
189
|
-
id,
|
|
190
|
-
name,
|
|
191
|
-
color: normalizeCssColorToken(source.color ?? driver.color, driver.color),
|
|
192
|
-
icon: source.icon ?? normalizeTeamIcon(source.name ?? source.id ?? timingCode ?? driver.name),
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
52
|
export function buildChampionshipDriverGrid(drivers = DEMO_PROJECT_DRIVERS, entries = CHAMPIONSHIP_DRIVER_ENTRIES) {
|
|
197
53
|
assertUniqueEntryDriverIds(entries);
|
|
198
54
|
assertUniqueDriverNumbers(entries);
|
|
@@ -201,7 +57,7 @@ export function buildChampionshipDriverGrid(drivers = DEMO_PROJECT_DRIVERS, entr
|
|
|
201
57
|
const blueprintByDriverId = new Map(CHAMPIONSHIP_ENTRY_BLUEPRINTS.map((entry) => [entry.driverId, entry]));
|
|
202
58
|
const usedTimingCodes = new Set();
|
|
203
59
|
|
|
204
|
-
|
|
60
|
+
const grid = drivers.map((driver, index) => {
|
|
205
61
|
const safeDriver = {
|
|
206
62
|
...driver,
|
|
207
63
|
color: normalizeCssColorToken(driver.color, DEFAULT_DARK_THEME_TOKENS.primary),
|
|
@@ -217,6 +73,7 @@ export function buildChampionshipDriverGrid(drivers = DEMO_PROJECT_DRIVERS, entr
|
|
|
217
73
|
|
|
218
74
|
return {
|
|
219
75
|
...safeDriver,
|
|
76
|
+
presentation: normalizeEntryPresentation(safeDriver, entry.presentation ?? safeDriver.presentation),
|
|
220
77
|
code: timingCode,
|
|
221
78
|
timingCode,
|
|
222
79
|
raceName: timingCode,
|
|
@@ -226,6 +83,7 @@ export function buildChampionshipDriverGrid(drivers = DEMO_PROJECT_DRIVERS, entr
|
|
|
226
83
|
racecraft: driverArgs.racecraft,
|
|
227
84
|
consistency: driverArgs.consistency,
|
|
228
85
|
personality: driverArgs.personality,
|
|
86
|
+
driverModel: safeDriver.driverModel ?? driverArgs.driverModel ?? vehicleArgs.driverModel ?? null,
|
|
229
87
|
vehicle: vehicleArgs,
|
|
230
88
|
constructorArgs: {
|
|
231
89
|
driver: driverArgs,
|
|
@@ -238,6 +96,6 @@ export function buildChampionshipDriverGrid(drivers = DEMO_PROJECT_DRIVERS, entr
|
|
|
238
96
|
},
|
|
239
97
|
};
|
|
240
98
|
});
|
|
99
|
+
assertUniqueDriverNumbers(grid);
|
|
100
|
+
return grid;
|
|
241
101
|
}
|
|
242
|
-
|
|
243
|
-
export const CHAMPIONSHIP_PROJECT_DRIVERS = buildChampionshipDriverGrid();
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { DriverData } from './driverData.js';
|
|
2
|
+
import { VehicleData } from './vehicleData.js';
|
|
3
|
+
|
|
4
|
+
export const CHAMPIONSHIP = {
|
|
5
|
+
id: 'project-race-2026',
|
|
6
|
+
name: 'Project Race',
|
|
7
|
+
season: 2026,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const CHAMPIONSHIP_ENTRY_BLUEPRINTS = [
|
|
11
|
+
{
|
|
12
|
+
driverId: 'budget',
|
|
13
|
+
driverNumber: 71,
|
|
14
|
+
timingName: 'Budget',
|
|
15
|
+
driver: new DriverData({ pace: 52, racecraft: 74, aggression: 38, riskTolerance: 47, patience: 81, consistency: 86 }),
|
|
16
|
+
vehicle: new VehicleData({ id: 'budget-bb01', name: 'BB-01 Ledger', power: 48, braking: 72, aero: 55, dragEfficiency: 66, mechanicalGrip: 63, weightControl: 58, tireCare: 82 }),
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
driverId: 'noir',
|
|
20
|
+
driverNumber: 13,
|
|
21
|
+
timingName: 'Noir',
|
|
22
|
+
driver: new DriverData({ pace: 68, racecraft: 83, aggression: 61, riskTolerance: 72, patience: 57, consistency: 64 }),
|
|
23
|
+
vehicle: new VehicleData({ id: 'noir-nn13', name: 'NN-13 Shadow', power: 64, braking: 60, aero: 73, dragEfficiency: 57, mechanicalGrip: 69, weightControl: 54, tireCare: 51 }),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
driverId: 'vinyl',
|
|
27
|
+
driverNumber: 33,
|
|
28
|
+
timingName: 'HoloVinyl',
|
|
29
|
+
driver: new DriverData({ pace: 71, racecraft: 66, aggression: 52, riskTolerance: 59, patience: 62, consistency: 58 }),
|
|
30
|
+
vehicle: new VehicleData({ id: 'vinyl-hv33', name: 'HV-33 Groove', power: 70, braking: 49, aero: 58, dragEfficiency: 74, mechanicalGrip: 52, weightControl: 68, tireCare: 46 }),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
driverId: 'drsorriso',
|
|
34
|
+
driverNumber: 55,
|
|
35
|
+
timingName: 'DrSorriso',
|
|
36
|
+
driver: new DriverData({ pace: 46, racecraft: 69, aggression: 44, riskTolerance: 43, patience: 76, consistency: 73 }),
|
|
37
|
+
vehicle: new VehicleData({ id: 'sorriso-ds55', name: 'DS-55 Intake', power: 54, braking: 67, aero: 62, dragEfficiency: 49, mechanicalGrip: 71, weightControl: 45, tireCare: 69 }),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
driverId: 'victoria',
|
|
41
|
+
driverNumber: 11,
|
|
42
|
+
timingName: 'VictorIA',
|
|
43
|
+
driver: new DriverData({ pace: 78, racecraft: 79, aggression: 58, riskTolerance: 65, patience: 55, consistency: 68 }),
|
|
44
|
+
vehicle: new VehicleData({ id: 'victoria-vi11', name: 'VI-11 Vector', power: 76, braking: 58, aero: 65, dragEfficiency: 63, mechanicalGrip: 61, weightControl: 60, tireCare: 48 }),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
driverId: 'reminderz',
|
|
48
|
+
driverNumber: 7,
|
|
49
|
+
timingName: 'ReminderZ',
|
|
50
|
+
driver: new DriverData({ pace: 41, racecraft: 61, aggression: 34, riskTolerance: 39, patience: 85, consistency: 80 }),
|
|
51
|
+
vehicle: new VehicleData({ id: 'reminderz-rz07', name: 'RZ-07 Recall', power: 42, braking: 76, aero: 50, dragEfficiency: 72, mechanicalGrip: 58, weightControl: 63, tireCare: 88 }),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
driverId: 'clipclop',
|
|
55
|
+
driverNumber: 14,
|
|
56
|
+
timingName: 'ClipClop',
|
|
57
|
+
driver: new DriverData({ pace: 73, racecraft: 88, aggression: 76, riskTolerance: 78, patience: 48, consistency: 61 }),
|
|
58
|
+
vehicle: new VehicleData({ id: 'clipclop-cc14', name: 'CC-14 Sync', power: 68, braking: 63, aero: 59, dragEfficiency: 61, mechanicalGrip: 78, weightControl: 56, tireCare: 44 }),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
driverId: 'evolve',
|
|
62
|
+
driverNumber: 23,
|
|
63
|
+
timingName: 'Evolve',
|
|
64
|
+
driver: new DriverData({ pace: 56, racecraft: 70, aggression: 63, riskTolerance: 61, patience: 58, consistency: 52 }),
|
|
65
|
+
vehicle: new VehicleData({ id: 'evolve-ev23', name: 'EV-23 Morph', power: 60, braking: 53, aero: 79, dragEfficiency: 46, mechanicalGrip: 66, weightControl: 49, tireCare: 57 }),
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
driverId: 'clash',
|
|
69
|
+
driverNumber: 47,
|
|
70
|
+
timingName: 'Clash',
|
|
71
|
+
driver: new DriverData({ pace: 44, racecraft: 67, aggression: 70, riskTolerance: 69, patience: 42, consistency: 59 }),
|
|
72
|
+
vehicle: new VehicleData({ id: 'clash-cr47', name: 'CR-47 Arena', power: 57, braking: 48, aero: 52, dragEfficiency: 55, mechanicalGrip: 73, weightControl: 52, tireCare: 64 }),
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
driverId: 'core',
|
|
76
|
+
driverNumber: 91,
|
|
77
|
+
timingName: 'Core',
|
|
78
|
+
driver: new DriverData({ pace: 82, racecraft: 85, aggression: 67, riskTolerance: 74, patience: 53, consistency: 72 }),
|
|
79
|
+
vehicle: new VehicleData({ id: 'core-ai91', name: 'AI-91 Core', power: 80, braking: 61, aero: 72, dragEfficiency: 59, mechanicalGrip: 75, weightControl: 51, tireCare: 50 }),
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
export const CHAMPIONSHIP_DRIVER_ENTRIES = CHAMPIONSHIP_ENTRY_BLUEPRINTS.map((entry) => ({
|
|
84
|
+
driverId: entry.driverId,
|
|
85
|
+
driverNumber: entry.driverNumber,
|
|
86
|
+
timingName: entry.timingName,
|
|
87
|
+
}));
|
package/src/data/driverData.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { normalizeCustomFields } from './customFields.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
const RATING_NEUTRAL = 50;
|
|
5
|
-
const RATING_MAXIMUM = 100;
|
|
3
|
+
import { RATING_MINIMUM, RATING_NEUTRAL, RATING_MAXIMUM, clampRating, applyRating } from './ratingScale.js';
|
|
6
4
|
|
|
7
5
|
export const DRIVER_STAT_DEFINITIONS = {
|
|
8
6
|
pace: { minimum: RATING_MINIMUM, neutral: RATING_NEUTRAL, maximum: RATING_MAXIMUM, base: 1, variance: 0.08 },
|
|
@@ -13,17 +11,6 @@ export const DRIVER_STAT_DEFINITIONS = {
|
|
|
13
11
|
consistency: { minimum: RATING_MINIMUM, neutral: RATING_NEUTRAL, maximum: RATING_MAXIMUM, base: 0.75, variance: 0.16 },
|
|
14
12
|
};
|
|
15
13
|
|
|
16
|
-
function clampRating(value, label) {
|
|
17
|
-
const rating = Number(value);
|
|
18
|
-
if (!Number.isFinite(rating)) throw new Error(`Invalid driver rating for ${label}: ${value}`);
|
|
19
|
-
return Math.min(Math.max(rating, RATING_MINIMUM), RATING_MAXIMUM);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function applyRating(definition, value) {
|
|
23
|
-
const normalized = (value - definition.neutral) / (definition.maximum - definition.neutral);
|
|
24
|
-
return definition.base + normalized * definition.variance;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
14
|
export class DriverData {
|
|
28
15
|
constructor({
|
|
29
16
|
pace = RATING_NEUTRAL,
|
|
@@ -33,14 +20,16 @@ export class DriverData {
|
|
|
33
20
|
patience = RATING_NEUTRAL,
|
|
34
21
|
consistency = RATING_NEUTRAL,
|
|
35
22
|
customFields = [],
|
|
23
|
+
driverModel = null,
|
|
36
24
|
} = {}) {
|
|
37
|
-
this.pace = clampRating(pace, 'pace');
|
|
38
|
-
this.racecraft = clampRating(racecraft, 'racecraft');
|
|
39
|
-
this.aggression = clampRating(aggression, 'aggression');
|
|
40
|
-
this.riskTolerance = clampRating(riskTolerance, 'riskTolerance');
|
|
41
|
-
this.patience = clampRating(patience, 'patience');
|
|
42
|
-
this.consistency = clampRating(consistency, 'consistency');
|
|
25
|
+
this.pace = clampRating(pace, 'pace', 'driver');
|
|
26
|
+
this.racecraft = clampRating(racecraft, 'racecraft', 'driver');
|
|
27
|
+
this.aggression = clampRating(aggression, 'aggression', 'driver');
|
|
28
|
+
this.riskTolerance = clampRating(riskTolerance, 'riskTolerance', 'driver');
|
|
29
|
+
this.patience = clampRating(patience, 'patience', 'driver');
|
|
30
|
+
this.consistency = clampRating(consistency, 'consistency', 'driver');
|
|
43
31
|
this.customFields = normalizeCustomFields(customFields);
|
|
32
|
+
this.driverModel = driverModel;
|
|
44
33
|
}
|
|
45
34
|
|
|
46
35
|
ratings() {
|
|
@@ -58,6 +47,7 @@ export class DriverData {
|
|
|
58
47
|
return {
|
|
59
48
|
ratings: this.ratings(),
|
|
60
49
|
customFields: this.customFields,
|
|
50
|
+
driverModel: this.driverModel,
|
|
61
51
|
pace: applyRating(DRIVER_STAT_DEFINITIONS.pace, this.pace),
|
|
62
52
|
racecraft: applyRating(DRIVER_STAT_DEFINITIONS.racecraft, this.racecraft),
|
|
63
53
|
consistency: applyRating(DRIVER_STAT_DEFINITIONS.consistency, this.consistency),
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { normalizeCssColorToken } from '../config/theme/cssTokens.js';
|
|
2
|
+
|
|
3
|
+
const LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
4
|
+
|
|
5
|
+
function normalizeLetters(value) {
|
|
6
|
+
return String(value ?? '')
|
|
7
|
+
.normalize('NFKD')
|
|
8
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
9
|
+
.replace(/[^a-zA-Z]/g, '')
|
|
10
|
+
.toUpperCase();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function timingCodeCandidates(source) {
|
|
14
|
+
const letters = normalizeLetters(source);
|
|
15
|
+
if (!letters) return ['XXX'];
|
|
16
|
+
if (letters.length <= 3) return [letters.padEnd(3, 'X')];
|
|
17
|
+
|
|
18
|
+
const candidates = [letters.slice(0, 3)];
|
|
19
|
+
for (let index = 1; index <= letters.length - 3; index += 1) {
|
|
20
|
+
candidates.push(letters.slice(index, index + 3));
|
|
21
|
+
}
|
|
22
|
+
candidates.push(`${letters[0]}${letters[1]}${letters[letters.length - 1]}`);
|
|
23
|
+
return [...new Set(candidates)];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function pickUniqueTimingCode(source, usedCodes) {
|
|
27
|
+
for (const candidate of timingCodeCandidates(source)) {
|
|
28
|
+
if (!usedCodes.has(candidate)) {
|
|
29
|
+
usedCodes.add(candidate);
|
|
30
|
+
return candidate;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const letters = normalizeLetters(source).padEnd(2, 'X');
|
|
35
|
+
for (const suffix of LETTERS) {
|
|
36
|
+
const candidate = `${letters[0]}${letters[1]}${suffix}`;
|
|
37
|
+
if (!usedCodes.has(candidate)) {
|
|
38
|
+
usedCodes.add(candidate);
|
|
39
|
+
return candidate;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
throw new Error(`Unable to create a unique three-letter timing code for "${source}"`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function formatDriverNumber(driverNumber) {
|
|
47
|
+
return String(driverNumber ?? '').padStart(2, '0');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeTeamIcon(source) {
|
|
51
|
+
const letters = normalizeLetters(source);
|
|
52
|
+
return letters.slice(0, 2).padEnd(2, 'X');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function normalizeTeam(team, driver, timingCode) {
|
|
56
|
+
const source = team ?? driver.team;
|
|
57
|
+
if (!source) return null;
|
|
58
|
+
const name = source.name ?? source.id ?? `${driver.name ?? timingCode} Team`;
|
|
59
|
+
const id = (source.id ?? normalizeLetters(name).toLowerCase()) || `${driver.id}-team`;
|
|
60
|
+
return {
|
|
61
|
+
...source,
|
|
62
|
+
id,
|
|
63
|
+
name,
|
|
64
|
+
color: normalizeCssColorToken(source.color ?? driver.color, driver.color),
|
|
65
|
+
icon: source.icon ?? normalizeTeamIcon(source.name ?? source.id ?? timingCode ?? driver.name),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { normalizePublicUrlOption } from '../config/urlOptions.js';
|
|
2
|
+
|
|
3
|
+
function text(value, fallback = '') {
|
|
4
|
+
return typeof value === 'string' ? value : fallback;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function lines(value) {
|
|
8
|
+
return Array.isArray(value) ? value.filter((line) => typeof line === 'string' && line.trim()) : [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function normalizeEntryPresentation(driver, source = driver.presentation ?? {}) {
|
|
12
|
+
const image = source?.image;
|
|
13
|
+
const imageSrc = normalizePublicUrlOption(image?.src, '');
|
|
14
|
+
return {
|
|
15
|
+
name: text(source?.name).trim() || driver.name,
|
|
16
|
+
summary: text(source?.summary, text(driver.raceData?.[0])),
|
|
17
|
+
technologies: lines(source?.technologies),
|
|
18
|
+
details: lines(source?.details ?? driver.raceData?.slice(1)),
|
|
19
|
+
image: imageSrc ? { src: imageSrc, alt: text(image.alt) } : null,
|
|
20
|
+
href: normalizePublicUrlOption(source?.href ?? driver.link, ''),
|
|
21
|
+
linkLabel: text(source?.linkLabel).trim() || 'Open project',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { TeamData } from './teamTypes.js';
|
|
2
|
+
|
|
3
|
+
export type TireCompound = 'S' | 'M' | 'H';
|
|
4
|
+
export interface CustomField {
|
|
5
|
+
label: string;
|
|
6
|
+
value: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type CustomFieldInput = CustomField[] | Record<string, string>;
|
|
10
|
+
export interface EntryPresentation {
|
|
11
|
+
name?: string;
|
|
12
|
+
summary?: string;
|
|
13
|
+
technologies?: string[];
|
|
14
|
+
details?: string[];
|
|
15
|
+
image?: { src: string; alt?: string } | null;
|
|
16
|
+
href?: string;
|
|
17
|
+
linkLabel?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SimulatorDriver {
|
|
21
|
+
presentation?: EntryPresentation;
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
color: string;
|
|
25
|
+
link?: string;
|
|
26
|
+
icon?: string;
|
|
27
|
+
code?: string;
|
|
28
|
+
timingName?: string;
|
|
29
|
+
tire?: TireCompound;
|
|
30
|
+
raceData?: string[];
|
|
31
|
+
customFields?: CustomFieldInput;
|
|
32
|
+
team?: TeamData | null;
|
|
33
|
+
driverNumber?: number;
|
|
34
|
+
driverModel?: unknown;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DriverRatings {
|
|
38
|
+
pace: number;
|
|
39
|
+
racecraft: number;
|
|
40
|
+
aggression: number;
|
|
41
|
+
riskTolerance: number;
|
|
42
|
+
patience: number;
|
|
43
|
+
consistency: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface VehicleRatings {
|
|
47
|
+
power: number;
|
|
48
|
+
braking: number;
|
|
49
|
+
aero: number;
|
|
50
|
+
dragEfficiency: number;
|
|
51
|
+
mechanicalGrip: number;
|
|
52
|
+
weightControl: number;
|
|
53
|
+
tireCare: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface DriverBlueprint extends Partial<DriverRatings> {
|
|
57
|
+
customFields?: CustomFieldInput;
|
|
58
|
+
driverModel?: unknown;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface VehicleBlueprint extends Partial<VehicleRatings> {
|
|
62
|
+
id?: string | null;
|
|
63
|
+
name?: string | null;
|
|
64
|
+
customFields?: CustomFieldInput;
|
|
65
|
+
driverModel?: unknown;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface DriverConstructorArgs {
|
|
69
|
+
ratings: DriverRatings;
|
|
70
|
+
customFields: CustomField[];
|
|
71
|
+
driverModel: unknown | null;
|
|
72
|
+
pace: number;
|
|
73
|
+
racecraft: number;
|
|
74
|
+
consistency: number;
|
|
75
|
+
personality: {
|
|
76
|
+
aggression: number;
|
|
77
|
+
riskTolerance: number;
|
|
78
|
+
patience: number;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface VehicleConstructorArgs {
|
|
83
|
+
id: string | null;
|
|
84
|
+
name: string | null;
|
|
85
|
+
ratings: VehicleRatings;
|
|
86
|
+
customFields: CustomField[];
|
|
87
|
+
driverModel: unknown | null;
|
|
88
|
+
powerNewtons: number;
|
|
89
|
+
brakeNewtons: number;
|
|
90
|
+
downforceCoefficient: number;
|
|
91
|
+
dragCoefficient: number;
|
|
92
|
+
tireGrip: number;
|
|
93
|
+
mass: number;
|
|
94
|
+
tireCare: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export class DriverData {
|
|
98
|
+
constructor(input?: DriverBlueprint);
|
|
99
|
+
readonly pace: number;
|
|
100
|
+
readonly racecraft: number;
|
|
101
|
+
readonly aggression: number;
|
|
102
|
+
readonly riskTolerance: number;
|
|
103
|
+
readonly patience: number;
|
|
104
|
+
readonly consistency: number;
|
|
105
|
+
readonly customFields: CustomField[];
|
|
106
|
+
readonly driverModel: unknown | null;
|
|
107
|
+
ratings(): DriverRatings;
|
|
108
|
+
toConstructorArgs(): DriverConstructorArgs;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export class VehicleData {
|
|
112
|
+
constructor(input?: VehicleBlueprint);
|
|
113
|
+
readonly id: string | null;
|
|
114
|
+
readonly name: string | null;
|
|
115
|
+
readonly power: number;
|
|
116
|
+
readonly braking: number;
|
|
117
|
+
readonly aero: number;
|
|
118
|
+
readonly dragEfficiency: number;
|
|
119
|
+
readonly mechanicalGrip: number;
|
|
120
|
+
readonly weightControl: number;
|
|
121
|
+
readonly tireCare: number;
|
|
122
|
+
readonly customFields: CustomField[];
|
|
123
|
+
readonly driverModel: unknown | null;
|
|
124
|
+
ratings(): VehicleRatings;
|
|
125
|
+
toConstructorArgs(): VehicleConstructorArgs;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface ChampionshipEntryBlueprint {
|
|
129
|
+
presentation?: EntryPresentation;
|
|
130
|
+
driverId: string;
|
|
131
|
+
driverNumber?: number;
|
|
132
|
+
timingName?: string;
|
|
133
|
+
driver?: DriverData | DriverBlueprint;
|
|
134
|
+
vehicle?: VehicleData | VehicleBlueprint;
|
|
135
|
+
team?: TeamData;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface NormalizedSimulatorDriver extends SimulatorDriver {
|
|
139
|
+
code: string;
|
|
140
|
+
timingCode: string;
|
|
141
|
+
raceName: string;
|
|
142
|
+
driverNumber: number;
|
|
143
|
+
team: TeamData | null;
|
|
144
|
+
pace: number;
|
|
145
|
+
racecraft: number;
|
|
146
|
+
consistency: number;
|
|
147
|
+
personality: DriverConstructorArgs['personality'];
|
|
148
|
+
vehicle: VehicleConstructorArgs;
|
|
149
|
+
constructorArgs: {
|
|
150
|
+
driver: DriverConstructorArgs;
|
|
151
|
+
vehicle: VehicleConstructorArgs;
|
|
152
|
+
};
|
|
153
|
+
championship: {
|
|
154
|
+
id: string;
|
|
155
|
+
name: string;
|
|
156
|
+
season: number;
|
|
157
|
+
entryIndex: number;
|
|
158
|
+
vehicleId: string | null;
|
|
159
|
+
};
|
|
160
|
+
}
|