@hypersoniclabs/helix-mcp 0.2.5 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +81 -11
  2. package/dist/continuumCanary.d.ts +17 -0
  3. package/dist/continuumCanary.js +17 -0
  4. package/dist/continuumCanary.js.map +1 -0
  5. package/dist/server.d.ts +14 -1
  6. package/dist/server.js +4058 -162
  7. package/dist/server.js.map +1 -1
  8. package/dist/tsconfig.build.tsbuildinfo +1 -1
  9. package/dist/vehicleTools.d.ts +86 -0
  10. package/dist/vehicleTools.js +229 -0
  11. package/dist/vehicleTools.js.map +1 -0
  12. package/docs/avatar-face.md +115 -0
  13. package/docs/bridge.md +98 -0
  14. package/docs/bring-your-world.md +117 -0
  15. package/docs/catalog.md +69 -1
  16. package/docs/character-animation.md +442 -0
  17. package/docs/character-attachments.md +166 -0
  18. package/docs/character-world.md +785 -130
  19. package/docs/continuum.md +153 -0
  20. package/docs/items.md +73 -0
  21. package/docs/lighting-world.md +667 -0
  22. package/docs/locomotion-clip-spec.md +294 -0
  23. package/docs/manifest.md +31 -6
  24. package/docs/multiplayer-logic.md +460 -17
  25. package/docs/multiplayer-templates/chrono-orchard.md +36 -22
  26. package/docs/multiplayer-templates/collect-a-thon.md +28 -38
  27. package/docs/multiplayer-templates/collections.md +24 -24
  28. package/docs/multiplayer-templates/hangout.md +124 -111
  29. package/docs/multiplayer-templates/npc-wave.md +310 -0
  30. package/docs/multiplayer-templates/obby.md +13 -16
  31. package/docs/multiplayer-templates/persistent-progress.md +218 -0
  32. package/docs/multiplayer-templates/physics-bumper.md +27 -9
  33. package/docs/multiplayer-templates/physics-football.md +22 -8
  34. package/docs/multiplayer-templates/relic-bearers.md +12 -15
  35. package/docs/multiplayer-templates/server-motion.md +16 -19
  36. package/docs/multiplayer-templates/shooter-range.md +275 -0
  37. package/docs/multiplayer-templates/team-control.md +28 -15
  38. package/docs/multiplayer-templates/turn-arena.md +31 -22
  39. package/docs/multiplayer-templates/voice-radio.md +166 -0
  40. package/docs/multiplayer-templates/wave-survival.md +7 -8
  41. package/docs/multiplayer-templates/world-shop.md +240 -0
  42. package/docs/multiplayer-world.md +222 -129
  43. package/docs/npc-world.md +623 -0
  44. package/docs/publishing.md +108 -28
  45. package/docs/purchases.md +223 -0
  46. package/docs/scene-performance.md +64 -0
  47. package/docs/screenshots.md +140 -0
  48. package/docs/sdk.md +324 -5
  49. package/docs/shooter-worlds.md +537 -0
  50. package/docs/terrain.md +173 -0
  51. package/docs/upgrades.md +324 -0
  52. package/docs/vehicles.md +727 -0
  53. package/docs/world-inspect.md +156 -0
  54. package/docs/world-look.md +241 -0
  55. package/docs/world-recipe.md +65 -6
  56. package/package.json +15 -4
  57. package/skills/README.md +91 -0
  58. package/skills/helix-assets/SKILL.md +491 -0
  59. package/skills/helix-assets/references/asset-sources.md +143 -0
  60. package/skills/helix-assets/references/vault-api.md +105 -0
  61. package/skills/helix-avatar-qa/SKILL.md +85 -0
  62. package/skills/helix-avatars/SKILL.md +206 -0
  63. package/skills/helix-avatars/references/contract.md +166 -0
  64. package/skills/helix-avatars/references/dynamics.md +367 -0
  65. package/skills/helix-avatars/references/face.md +50 -0
  66. package/skills/helix-avatars/references/publish.md +76 -0
  67. package/skills/helix-avatars/references/qa.md +251 -0
  68. package/skills/helix-avatars/references/rigging.md +88 -0
  69. package/skills/helix-avatars/references/source-generated.md +190 -0
  70. package/skills/helix-avatars/references/source-model.md +90 -0
  71. package/skills/helix-avatars/references/source-rigid.md +90 -0
  72. package/skills/helix-avatars/references/source-vrm.md +61 -0
  73. package/skills/helix-gauntlet/SKILL.md +128 -0
  74. package/skills/helix-multiplayer/SKILL.md +150 -0
  75. package/skills/helix-multiplayer/references/dsl-capability-map.md +107 -0
  76. package/skills/helix-vehicles/SKILL.md +218 -0
  77. package/skills/helix-vehicles/references/addons.md +212 -0
  78. package/skills/helix-vehicles/references/appearance.md +339 -0
  79. package/skills/helix-vehicles/references/audio-import.md +138 -0
  80. package/skills/helix-vehicles/references/audio.md +580 -0
  81. package/skills/helix-vehicles/references/cabin.md +225 -0
  82. package/skills/helix-vehicles/references/host-manifest.md +174 -0
  83. package/skills/helix-vehicles/references/physics-from-specs.md +463 -0
  84. package/skills/helix-vehicles/references/publish.md +214 -0
  85. package/skills/helix-vehicles/references/qa.md +177 -0
  86. package/skills/helix-vehicles/references/reference-package.json +3481 -0
  87. package/skills/helix-vehicles/references/reference-package.md +69 -0
  88. package/skills/helix-vehicles/references/source-beamng.md +167 -0
  89. package/skills/helix-vehicles/references/source-concept.md +38 -0
  90. package/skills/helix-vehicles/references/source-model.md +100 -0
  91. package/skills/helix-vehicles/references/source-scratch.md +60 -0
  92. package/skills/helix-world-build/SKILL.md +376 -0
  93. package/skills/helix-world-build/references/config-gates.md +104 -0
  94. package/skills/helix-world-director/SKILL.md +210 -0
  95. package/skills/helix-world-qa/SKILL.md +371 -0
  96. package/skills/helix-world-qa/references/perf-budgets.md +240 -0
  97. package/skills/helix-world-qa/references/perf-handle.md +125 -0
  98. package/skills/helix-world-qa/references/visual-scorecard.md +85 -0
@@ -0,0 +1,463 @@
1
+ # Physics from real-world specs
2
+
3
+ Physics is **stated, not invented**. Every number is `Sourced`: either
4
+ `{ value, unit, sources: [citationId] }` or `{ value, unit, derivedFrom: "<arithmetic>" }`. Only
5
+ two knobs are ours to tune toward the targets: `grip.peak` and `suspension.*`. Everything else
6
+ belongs to the real car. If 0–100 is off and the engine is cited, the cause is grip, gearing or
7
+ launch behaviour. It is not the engine.
8
+
9
+ ## 1. Research (before any mesh work)
10
+
11
+ Collect citations first. Give each one an `id`, a `kind` and a title that quotes the numbers it
12
+ supports.
13
+
14
+ | Kind | Use for |
15
+ | --- | --- |
16
+ | `manufacturer` | Press kit or technical data: power and torque with their rpm, mass (state DIN or EU and whether it includes a driver), gear ratios, final drive, Cd, tyre sizes, dimensions, top speed, and the manufacturer's 0–100. |
17
+ | `instrumented-test` | Magazine road tests: 0–60, quarter mile, 60–0, skidpad g. **Collect several and take the middle (the median); record the spread in `note`.** Never drop the test you like least as an "outlier" unless you can say why it is wrong. |
18
+ | `reference-database` | Wikipedia, spec databases: dimensions and track widths. Cross-check them against the manufacturer. |
19
+ | `measured-fixture` | Values measured off your own GLB: wheelbase, track, wheel radius, ride height. |
20
+ | `engineering-literature` | Ride frequency, damping ratio, roll gradient and tyre conventions. |
21
+
22
+ **Torque curve.** You need at least 10 points from idle to the limiter, in strictly ascending rpm,
23
+ and the curve must reach the limiter. Sources, best first:
24
+
25
+ 1. a published dyno chart from the manufacturer or a magazine;
26
+ 2. the shape of a known curve for the same engine family, scaled to the cited peak torque;
27
+ 3. a derived curve built from cited peak power and peak torque, with the derivation written in
28
+ `derivedFrom`.
29
+
30
+ Peak power must agree with torque × rpm: `P[kW] = T[N·m] × rpm / 9549`.
31
+
32
+ **A fictional or concept car** gets a named real analogue for each subsystem, with
33
+ `derivedFrom: "analogue: <real car> <field>, scaled by <reason>"`. Never write a bare number.
34
+
35
+ **Never take physics from a game mod as truth.** One mod's engine was a renamed template making
36
+ 360 N·m and 238 kW, stronger than any real 993. Use mod data only for *relative* add-on deltas.
37
+
38
+ ## 2. Deriving the fields
39
+
40
+ All units are SI.
41
+
42
+ | Field | How |
43
+ | --- | --- |
44
+ | `chassis.mass` | The configuration's own published kerb mass. Not an estimate. |
45
+ | `weightDistributionFront` | Published. For example, the 993 is 0.39. |
46
+ | `centreOfMassHeight` | Published if available, else derived as `≈ 0.35–0.40 × height` for road cars (lower for supercars, as the class presets assume). Write the derivation. |
47
+ | `wheelbase`, `trackFront`, `trackRear`, `length`, `width`, `height` | Published, then cross-checked against your mesh. The runtime measures wheel positions from the mesh, so a disagreement means your mesh is wrong. |
48
+ | `wheelRadius` | The **measured rolling radius of the drawn tyre**. Compute the nominal radius from the tyre size: `(rim_in × 25.4 / 2 + width_mm × aspect/100) / 1000`; a 205/55R16 is 0.316. Then use what the mesh actually draws. Visible geometry wins. A car with different front and rear tyres: see "Staggered tyres" below. |
49
+ | `staticRideHeight` | Anchor to wheel centre, in m. **Set it so that `wheelRadius + staticRideHeight = groundToBodyOrigin`.** |
50
+ | `groundToBodyOrigin` | The body origin's height above the ground at rest. It is locked after the first publish. |
51
+ | `engine.torqueCurve`, `idleRpm`, `limiterRpm` | Cited, as described above. |
52
+ | `engine.drivetrainEfficiency` | 0.85–0.91 for a manual RWD car. Label it as an assumption if it is not published. This, and aero drag, is where real-world losses live (see "Torque: target the declared crank figures"). |
53
+ | `engine.coastTorque` | Engine braking. About 10–15 % of peak torque is typical; label it derived. |
54
+ | `gearbox.ratios`, `reverseRatio` (negative), `finalDrive` | Published. |
55
+ | `shiftTime` | 0.2–0.3 s manual, 0.1–0.15 s DCT. |
56
+ | `autoUpRpm`, `autoDownRpm` | Just below the limiter, and roughly 40 % of the limiter. |
57
+ | `driveBiasFront` | The nominal **front share of drive torque at zero slip**: 0 RWD, 1 FWD. An AWD car is **strictly between 0 and 1** and equal to `centre.torqueSplit` (see "All-wheel drive"); exactly 0 or 1 flags an axle's wheels unpowered, whatever the centre diff says. |
58
+ | `aero.dragCoefficient` | Published Cd. If it is unpublished, derive it from the measured top speed: `Cd = 2·P_wheel / (ρ v³ A)`, with ρ = 1.225, `P_wheel = kW × 1000 × efficiency` and v in m/s. |
59
+ | `aero.frontalArea` | Published, else `0.80 × width × height`. |
60
+ | `aero.downforceCoefficient` | 0 for a road car without aero. A `mul` add-on on 0 does nothing. |
61
+ | `linearDamping` | **0 when aero is modelled.** A value of 0.02 removed 65 km/h of top speed. |
62
+ | `brakes.maxTorque` | Whole car, in N·m. It must at least reach the tyre limit, `T ≥ m·g·μ·r`; real cars carry headroom over that (the 993 used about 1.7×). With enough torque, stopping distance is tyre-limited. |
63
+ | `brakes.biasFront` | Tracks the static weight plus the transfer, typically 0.6–0.7. |
64
+ | `grip.peak` | At least the average braking g implied by the published 60–0 distance. `a = v² / 2d` gives 0.97 g for 36 m from 26.8 m/s, so pick ≥ 0.98; the 993 used 1.05. Pick `sliding` a little below peak. |
65
+ | `grip.lateralMultiplier` | About 0.9. The derived lateral g is about 0.85 × longitudinal. Check it against the published skidpad. |
66
+ | `suspension.rideHzFront` / `rideHzRear` | 1.2–1.6 Hz for road cars, 1.8–2.5 Hz for track cars. Rear about 10 % above front (flat ride). |
67
+ | `suspension.dampingRatio` | 0.2–0.3 for a soft road car, 0.4–0.5 for a sports car, 0.65–0.7 for a race car. |
68
+ | `bumpScale`, `reboundScale` | For example 0.9 and 1.1: rebound firmer than bump. |
69
+ | `bumpTravel` | Measured travel to the bump stop, about 0.07 m on a sports car. |
70
+ | `antiRollBarFront` / `Rear` | **Size by roll gradient, not by feel.** A road car runs 4–5° per g, and the bars add about 1/5 of the roll stiffness. The 993 used 14,000 / 9,000 N/m; an earlier 38,000 / 28,000 N/m gave a racing car's 2.3° per g. |
71
+ | `steering.roadWheelLockDeg` | Published, else about 34°. Use a `rackTime` of about 0.14 s. |
72
+ | `inertiaMultiplier` | `[1, 1.1, 1.3]`. This is ours, not the car's: say so in `note`. |
73
+
74
+ ### `simulation.land` (the v2 solver; required for manual gears and a real drivetrain)
75
+
76
+ | Part | How |
77
+ | --- | --- |
78
+ | `driveline.engine` | `torqueCurve: { points }` is the same as the physics curve. Then: `inertia` 0.1–0.2 kg·m², `friction` and `dynamicFriction` small, `engineBrakeTorque` equal to `coastTorque`, `burnEfficiency` as a load LUT, `drivetrainEfficiency`, `revLimiterType` soft or hard. Set `boostTorqueCoef: 0` for NA, or when the curve already includes boost. |
79
+ | `driveline.clutch` | `capacity` ≈ 1.6 × peak torque. Typical values: `inertia` ~0.1, `freePlay` 0.2, launch rpm. |
80
+ | `driveline.gearbox` | `kind` is `manual` or `sequential` for manual mode. Also `ratios`, `reverseRatio`, `shiftDelay` and `auto { enabled, upRpm, downRpm }`. |
81
+ | Differentials | `rear` for RWD, `front` for FWD, both plus `centre` for AWD. **`gearRatio` on the driven diff is the final drive.** Types: `open`, `locked`, `viscous`, `lsd` (with `lsdPreload`, `lsdLockCoef`, `lsdRevLockCoef`). For AWD, read "All-wheel drive" below. |
82
+ | `abs` | `{ targetSlip: 0.12, releaseRate: 12, applyRate: 5, minSpeed: 2.5 }` if the car has ABS. If it is absent, wheels lock. |
83
+ | `tractionControl` | Optional; absent means none. `{ targetSlip: 0.2, cutRate: 25, restoreRate: 3, minTorque: 0.45, upshiftTorqueRamp: true }` if the car had traction control, or as a declared playability aid. See "Traction control" below. |
84
+ | `tyreByAxle[]` | Front and rear tyre models, one per axle: `radius` (that axle's measured rolling radius), `angularInertia`, `alphaPeakRad`, `muLut { fz[], mu[], slideRatio }`, `loadSensitivity`, `sigmaX/Y`, `cxMult`, `dxOverDy`, `sFall`, `camberGain` and `rollingResistance`. Start from the reference package's tyre and scale `muLut.mu` to your grip, or use the BeamNG importer's sampled LUT. |
85
+ | `turbine` | Turbo cars only: `pressureMap`, `wastegateStartBar` and `wastegateLimitBar`, `displacementM3`, and so on. A turbo add-on does nothing on a v2 car without one. |
86
+
87
+ The fully worked example is the `simulation` block in the `reference-package` reference. That
88
+ car is naturally aspirated.
89
+
90
+ ### All-wheel drive
91
+
92
+ The v2 solver reads the split from `simulation.land.driveline`: `front` and `rear` axle diffs (their `gearRatio` is the final
93
+ drive; an axle with no diff is not driven) and a `centre` diff. Every diff is `{ type, torqueSplit, gearRatio, friction,
94
+ dynamicFriction, lockTorque, viscousCoef, viscousTorque, lsdPreload, lsdLockCoef, lsdRevLockCoef }`.
95
+
96
+ - **`centre.torqueSplit` is the FRONT share of drive torque before any locking.** `physics.driveBiasFront` is the same number
97
+ and must equal it, strictly between 0 and 1. (The solver takes the split from the centre diff; `driveBiasFront` decides which
98
+ wheels are powered, and an add-on that moves it scales `torqueSplit`.)
99
+ - **Locking.** The centre sends `front = split·Q + lock`, `rear = (1−split)·Q − lock`, with `lock = capacity · tanh(Δω / 0.5 rad/s)` and
100
+ Δω = front − rear axle speed. The lock **moves torque to the slower axle**: a spinning rear axle pulls torque forward, a spinning
101
+ front axle pushes it back, and a corner (front faster) leans rearward. `Q` is the torque entering the centre diff in N·m.
102
+ - **Capacity by type:** `open` 0; `locked` `lockTorque` (a very stiff coupling, not a rigid weld); `viscous`
103
+ `min(viscousCoef·|Δω|, viscousTorque)`; `lsd` `lsdPreload + lsdLockCoef·|Q|` on power and `lsdPreload + lsdRevLockCoef·|Q|` on coast.
104
+ Power and coast are two numbers on purpose.
105
+ - **Maximum share to the other axle** at full lock ≈ `torqueSplit + lsdLockCoef + lsdPreload/Q` (rear-biased car, rear slipping).
106
+ Set the coefficients so that equals the maker's stated maximum, and keep it below 1.
107
+
108
+ | Kind (e.g.) | `centre.type` | `torqueSplit` = `driveBiasFront` | Lock fields |
109
+ | --- | --- | --- | --- |
110
+ | Clutch-pack, slip-metered, rear-biased (ATTESA-, xDrive-type) | `lsd` | The maker's **zero-slip** front share: `0.05` for "0:100", about `0.4` for "40:60". Never the average of a launch. | `lsdPreload` 30–80 N·m; `lsdLockCoef` = (maximum front share − `torqueSplit`), e.g. `0.40` for a 50 % maximum from `0.05`, less the preload term; `lsdRevLockCoef` 0.05–0.15 (the pack opens on coast and under ABS). |
111
+ | Clutch-pack, front-biased (Haldex-type) | `lsd` | `0.9`–`0.95` (almost all front at rest; the lock sends torque back). | Same fields; the rear's maximum share ≈ `(1 − torqueSplit) + lsdLockCoef`. |
112
+ | Permanent torque-biasing (Torsen-type) | `lsd` | The design split (`0.4` for 40:60). | `lsdPreload` 0–20; `lsdLockCoef` so that `(slow share + coef)/(fast share − coef)` = the torque-bias ratio (2.5–4); `lsdRevLockCoef` ≈ `lsdLockCoef`. |
113
+ | Permanent viscous | `viscous` | The design split (`0.35`–`0.5`). | `viscousCoef` 8–30 N·m per rad/s; `viscousTorque` 600–800 N·m. No lock at steady cruise. |
114
+ | Part-time 4WD (engaged) | `locked` | `0.5`, or the fixed-gearing split. | `lockTorque` 10,000. Author the engaged mode only. |
115
+ | Open centre | `open` | The split. | none (no transfer: one slipping axle stays slipping). |
116
+ | RWD / FWD | no `centre`; one axle diff | `0` / `1` | — |
117
+
118
+ A worked check on one 1.6 t turbo scratch car (full-throttle launch, front share of drive at 0.25 / 0.75 / 1.75 / 3.75 s, from the
119
+ instrument; `driveBiasFront` held at 0.30, which the solver reads only as powered / unpowered): clutch-pack `lsd` split 0.02, preload 40, coef 0.40, coast 0.10 → 52 / 35 / 29 / 31 %; Torsen-like `lsd` 0.40, coef 0.25 →
120
+ 42 / 35 / 42 / 45 %; viscous 0.35, coef 8, cap 800 → 38 / 35 / 37 / 38 %; locked 0.5 → 54 / 35 / 46 / 49 %; the same car with
121
+ `driveBiasFront` 0 and an AWD centre → **0 %** to the front axle, and 37 km/h slower after 6 s. `validate_vehicle` warns
122
+ `AWD_DRIVE_BIAS_UNPOWERS_AN_AXLE` and `AWD_DRIVE_BIAS_DISAGREES_WITH_CENTRE_SPLIT`.
123
+
124
+ **Slip-metered centre (engine ≥ 0.3.207; prefer it for any on-demand clutch-pack system).** `simulation.land.driveline.centre.slipMetered:
125
+ { transferGain, maxFrontShare?, minFrontShare? }` makes the front share follow axle slip instead of a fixed `lsd` lock (e.g. an
126
+ ATTESA-, Haldex- or xDrive-type coupling). Absent = the constant split, exactly as before.
127
+
128
+ - `centre.torqueSplit` is the share with both axles turning together. Use a small non-zero value for a rear-primary system
129
+ (`0.02`–`0.05`) or `0.9`–`0.95` for a front-primary one, and set `driveBiasFront` to the same number: exactly 0 or 1 unpowers an axle.
130
+ - `transferGain` (0, 5] is the share moved per rad/s of centre-carrier speed difference near zero slip. `0.01`–`0.03` stays
131
+ rear-biased under steady power; `0.05` reaches the maximum in launch wheelspin.
132
+ - `maxFrontShare` ∈ [torqueSplit, 1] applies when the REAR slips, and `minFrontShare` ∈ [0, torqueSplit] when the FRONT slips. Set at least one.
133
+ Torque only moves to the slower (gripping) axle, so a corner (front faster) does not move a rear-primary car's share.
134
+ - Pair it with `type: "open"` and zero LSD coefficients: the metering is the coupling, so an LSD preload on top double-counts it.
135
+ It is allowed on the centre only, and `validate_vehicle` range-checks it.
136
+
137
+ ```json
138
+ "centre": { "type": "open", "torqueSplit": 0.02, "lsdPreload": 0, "lsdLockCoef": 0, "lsdRevLockCoef": 0,
139
+ "slipMetered": { "transferGain": 0.05, "maxFrontShare": 0.5 } }
140
+ ```
141
+
142
+ On a 1.56 t AWD package this gave 49 % front in launch wheelspin, 3 % once the rears hooked up, and 26–30 % under full power in higher gears.
143
+
144
+ ### The shift scheme (auto vs manual — and the live shift key)
145
+
146
+ How a car changes gear is part of its physics and must be authored, never left to a default:
147
+
148
+ - **`driveline.gearbox.auto { enabled, upRpm, downRpm }` is the automatic upshift.** In the default
149
+ drive mode the engine shifts itself at `upRpm` and down at `downRpm`. For a road car the player
150
+ drives by holding throttle, leave `enabled: true` and set `upRpm` just under the limiter and
151
+ `downRpm` ~40 % of it.
152
+ - **`driveline.gearbox.kind` is `manual` or `sequential` for a manual car.** A manual/sequential kind
153
+ makes the live drive expose a manual shift layer on top of the automatic one, keyed on the standard
154
+ action ids: **`ability7` toggles automatic ⇄ manual, `sprint` is the clutch, `ability1`–`ability6`
155
+ select forward gears 1–6, `crouch` is neutral, `reload` is reverse.** In automatic mode these keys are
156
+ inert and the `auto` block does the shifting.
157
+ - **The two must agree, or the car cannot be driven.** A manual-kind box with `auto.enabled: false`
158
+ pins 1st gear and never upshifts in the default drive (round 5's F40 could not be driven live for
159
+ exactly this class of mismatch) — the player would have to toggle to manual and shift with
160
+ `ability1`–`ability6`, which no one is told to do. So either author `auto { enabled: true, upRpm,
161
+ downRpm }` (a road car the default drive should shift itself), or document the manual keys as the
162
+ intended scheme and say so.
163
+ - **`simulate_vehicle` proves the automatic path only.** It drives in automatic mode, so a car whose
164
+ `auto` block is wrong (or absent) shows it there: the figures cannot be reached and the comparison
165
+ fails, with a shift warning. But it cannot prove the live manual layer — when the box is a manual kind
166
+ it reports "simulated with auto-shift; live manual-shift unverified". **Verify the live car upshifts**
167
+ (drive it in a hosted flat world, median of 3, and confirm it pulls through the gears) before you ship;
168
+ round 5 passed the simulator's 0–100 4.28 s while the live car pinned 1st at ~20 km/h.
169
+
170
+ ### Turbo or supercharged cars
171
+
172
+ Published torque figures for a turbo car are **boosted**. Use that curve as-is and tell the
173
+ solver so; otherwise the validator refuses the double-counted boost. In `physics`:
174
+
175
+ ```json
176
+ "induction": { "kind": "turbo",
177
+ "maxBoostBar": { "value": 1.15, "unit": "bar", "sources": ["…"] },
178
+ "wastegateBar": { "value": 1.0, "unit": "bar", "sources": ["…"] },
179
+ "referenceRpm": { "value": 3800, "unit": "rpm", "derivedFrom": "start of the torque plateau" },
180
+ "gamma": { "value": 2.0, "unit": "ratio", "derivedFrom": "spool shape prior" },
181
+ "spoolUpTime": { "value": 0.7, "unit": "s", "derivedFrom": "…" },
182
+ "spoolDownTime": { "value": 1.0, "unit": "s", "derivedFrom": "…" },
183
+ "blowOffThresholdBar": { "value": 0.35, "unit": "bar", "derivedFrom": "…" },
184
+ "torqueCurveIncludesBoost": true }
185
+ ```
186
+
187
+ In `simulation.land.driveline`, set `engine.boostTorqueCoef: 0`, because the curve already holds
188
+ the boost and the turbine only drives the spool and blow-off sound and signals. Then add a
189
+ `turbine` block.
190
+
191
+ **Author the turbo lag into the curve.** On the v2 solver the engine torque each step is
192
+ `torqueCurve(rpm) × throttle × limiter cut × boost factor`. With `boostTorqueCoef: 0` and no
193
+ `curveBoostBar`, the boost factor is 1: the turbine makes sound but adds no torque, and the curve
194
+ applies as written at every rpm, instantly. (A set `curveBoostBar` only ever raises torque above
195
+ the curve.) A published boosted
196
+ curve then launches the car with full boost from low rpm, and it runs too quick (round 1's F40
197
+ did 0–100 in 3.5 s against a real 4.5 s). So **derate the static curve below the spool point**:
198
+ below the rpm where the real engine comes on boost, use the off-boost torque (the naturally
199
+ aspirated figure for that displacement, or the published curve's own shape below its knee), and
200
+ ramp steeply to the boosted figure across the spool band. Write the arithmetic in `derivedFrom`,
201
+ cite the dyno chart that shows the knee, and confirm the 0–100 and `turboLagS` (with the onset
202
+ rpm it reports) with `simulate_vehicle`. The lag rig holds a low rpm in third gear, goes to full
203
+ throttle, and times how long the pull takes to reach 90 % of its peak acceleration. The engine's turbocharged reference car uses:
204
+
205
+ ```json
206
+ "turbine": { "kind": "turbo",
207
+ "pressureMap": { "points": [[0,-6.5],[30000,0],[60000,2],[90000,12],[150000,14],[200000,20],[250000,20.5]] },
208
+ "wastegateStartBar": 0.827, "wastegateLimitBar": 1.413, "inertiaScale": 5, "frictionCoef": 35,
209
+ "maxExhaustPower": 35000, "pressureRatePsi": 35, "sizeCoef": 0.7,
210
+ "exhaustFactor": { "points": [[0,0],[650,0.05],[1400,0.35],[2000,0.7],[3000,0.85],[5000,0.95],[7000,1.0],[9000,0.9]] },
211
+ "volumetricEfficiency": { "points": [[0,0],[650,0.25],[1400,0.65],[2000,0.85],[3000,0.98],[5000,0.8],[7000,0.68],[9000,0.6]] },
212
+ "displacementM3": 0.002457, "blowOffThresholdBar": 0.35, "driveRatio": 1 }
213
+ ```
214
+
215
+ Also set `driveline.displacementM3`. The `pressureMap` is in turbine rpm against PSI gauge, and
216
+ the wastegate values are in bar; scale them to your car's published boost. The driveline has a
217
+ single optional `turbine`, so a twin-turbo engine is modelled as one turbine for the pair.
218
+
219
+ ### Drivable on a keyboard (the full-throttle launch)
220
+
221
+ A keyboard player cannot modulate the throttle. What the platform sends is fixed:
222
+
223
+ - **W is a step.** The drive ability passes it straight to the controller; nothing ramps a digital
224
+ throttle.
225
+ - **W with A or D is 0.707 throttle and 0.707 steer.** The keyboard `move` vector is normalised, so
226
+ every heading correction under full throttle cuts the throttle to 71 % while the key is down, and
227
+ full lock is never available with W held.
228
+ - **A land-v2 car has traction control only if its package authors it**
229
+ (`simulation.land.tractionControl`, engine humanoid-character 0.3.204+; see "Traction control"
230
+ below). With ABS (`simulation.land.abs`), it is one of the two driver aids you can author. There
231
+ is no stability control or launch control. The old anti-wheelie limiter works on the v1
232
+ powertrain only.
233
+ - **Every full-throttle upshift chirps the driven tyres.** The auto box cuts fuel for
234
+ `shiftDelay`, then restores full throttle in one step while the clutch re-clamps over 120 ms.
235
+ The crank has only fallen by engine braking, so it re-engages 1,300–2,400 rpm above the new
236
+ gear's road speed. Wheel torque peaks at 1.5–2× steady state for about 0.1 s, on every car measured.
237
+ - **The launch clutch** (`clutch.launchStartRpm` / `launchTargetRpm`) slips until the gearbox
238
+ input passes `launchStartRpm`, and is fully closed above it. A turbo curve whose boost step sits
239
+ inside that window makes the launch bistable: a clean W overshoots onto boost and spins the
240
+ rears, while a launch where the throttle dips (W+A) parks at the window with little wheelspin.
241
+
242
+ `simulate_vehicle` therefore also runs a **keyboard launch** in the same runtime solver.
243
+
244
+ - **The run.** W goes from 0 to 1 in one tick from an auto-hold stop. The player taps A or D with
245
+ 100 ms reactions to hold a 3.5 m lane, from a start 2° off it. The report gives the keyboard 0–100
246
+ next to the instrument's, wheelspin time and peak driven slip per gear, the peak yaw rate, heading
247
+ error and sideslip for 1.5 s after each upshift re-engages, and the worst lane offset.
248
+ - **The probe.** The same launch is run hands-off, with a 0.2 rad/s yaw nudge at 20, 40, 60 and
249
+ 100 km/h and as each of the first two upshifts re-engages. 0.2 rad/s is less than one short tap.
250
+ - **FAIL ("not drivable on a keyboard")** when a nudge grows into a spin (sideslip ≥ 20°, or the
251
+ yaw rate grows to ≥ 3× the nudge), or when the player spins or leaves the lane.
252
+ - **WARN** when the player needs more than 5.7° of heading or sideslip, when a nudge has not
253
+ settled after 1 s, or when the keyboard 0–100 is more than 15 % slower than the instrument's.
254
+ - `simulate_vehicle` exits 1 on FAIL. `validate_vehicle` and `publish_vehicle` report it as the
255
+ warning `SIMULATED_KEYBOARD_LAUNCH_UNDRIVABLE` (or `…_MARGINAL`). It stays a warning because a
256
+ faithful car without traction control may have no accurate way to pass.
257
+ - The report has a `traction control:` line (none, ON with its tuning, or switched off), and the
258
+ keyboard launch says how long the controller was cutting torque.
259
+
260
+ Calibration: round 8's turbo supercar failed the probe at 20, 40 and 60 km/h and at the 1→2
261
+ re-engagement. Live, it hit the arena wall in 3 of 3 keyboard launches. The live 993 passed, and
262
+ reached 100 km/h in 3 of 3 with the same harness: 5.8–6.6 s live, against 5.92 s from the gate.
263
+
264
+ **Tuning for it without leaving the real figures.** The figures in `targets` are fixed. These
265
+ levers were measured one at a time on round 8's car (577 N·m turbo, rear drive, 39/61, 1st gear ×
266
+ final = 10.1):
267
+
268
+ | Lever | Effect on the keyboard launch | Cost to accuracy |
269
+ | --- | --- | --- |
270
+ | LSD `lsdLockCoef` 0.1–0.5, `lsdPreload` 0–150 | **None.** In a straight full-throttle launch both rears spin together and the diff stays locked: identical to 0.01 s, still spins | none. Author the real diff and don't expect it to fix a snap |
271
+ | `rear.type: "open"` | 60 km/h settles; 20/40 km/h and the 1→2 re-engagement still marginal or spinning | wrong for an LSD car |
272
+ | Rear `muLut.mu` × 1.15 | 1st-gear wheelspin 3.5 s → 0.2 s; 20/40 km/h still spin | 0–100 4.38 → 3.88 s: off the real figure. Only if the tyre data supports it, and recheck skidpad and 60–0 |
273
+ | Rear `muLut.slideRatio` 0.85 → 0.95–1.0 (less grip lost past the peak) | the 1→2 snap goes; 1st gear still spins | 0–100 → 4.0 s: off the real figure |
274
+ | `launchStartRpm`/`TargetRpm` 3,000/3,500 → 2,000/2,500 (launch below the boost step) | 20 km/h settles; 40/60 km/h still spin | 0–60 4.23 → 4.70 s: out of tolerance |
275
+ | Dynamic boost (`boostTorqueCoef: 1`, NA base curve = boosted curve ÷ (1 + 1.1 bar)) | none; the turbine spools within 1st gear | none (0–100 4.33 s) |
276
+
277
+ The rule that follows: if the engine's torque through 1st gear exceeds what the rear tyres hold,
278
+ no accurate package lever makes the car stable flat out. Don't hide the wheelspin with extra grip
279
+ or an invented torque curve; both move the car off its figures. Real boost-by-gear behaviour is
280
+ fine to model when it is documented (a turbine with real lag, or an ECU that limits boost in 1st
281
+ and 2nd): cite it. Otherwise, author traction control as the next section describes.
282
+
283
+ ### Traction control (`simulation.land.tractionControl`)
284
+
285
+ Opt-in and per car: absent means the car has none, and every published car without the block
286
+ drives exactly as before. It needs engine humanoid-character **0.3.204** or later. `simulate_vehicle`
287
+ and `validate_vehicle` run the same runtime the platform serves, so what they report is what a
288
+ player gets. If the block is authored but the runtime is older, the report says it is NOT MODELLED.
289
+
290
+ ```json
291
+ "tractionControl": { "targetSlip": 0.2, "cutRate": 25, "restoreRate": 3, "minTorque": 0.45, "upshiftTorqueRamp": true }
292
+ ```
293
+
294
+ What it does: while the fastest driven wheel's slip ratio is above `targetSlip`, it cuts engine
295
+ torque, faster the further past the target the wheel is. Below the target, it puts the torque back.
296
+ It cuts torque at the crank (like an ignition cut), so boost and engine braking are unaffected. It
297
+ reads the same tyre slip state the tyre model uses, so the controller and the tyre always agree.
298
+
299
+ | Field | Meaning | Start | Valid |
300
+ | --- | --- | --- | --- |
301
+ | `targetSlip` | Driven-wheel slip ratio to hold. The tyre peaks near 0.14; 0.15–0.2 keeps the launch | 0.2 | 0.02–1 |
302
+ | `cutRate` | Torque fraction cut per second at the target. It grows with the overshoot | 25 | > 0, ≤ 100 |
303
+ | `restoreRate` | Torque fraction restored per second once the wheel is back under the target | 3 | > 0, ≤ 100 |
304
+ | `minTorque` | The least torque fraction it leaves. Below ~0.4 the crank falls under the launch clutch's bite point and the car bogs | 0.45 | 0 – < 1 |
305
+ | `launchSpeed` | Road speed in m/s below which wheelspin is judged as tyre overspeed (`targetSlip × launchSpeed` m/s) instead of a ratio, because a ratio is meaningless at walking pace | omit (5) | 0–30 |
306
+ | `upshiftTorqueRamp` | After each upshift, torque follows the clutch back in until the pack is closed. This smooths the 1.5–2× re-clamp spike every car has | true | boolean; omitted = off |
307
+ | `enabled` | `false` keeps the tuned block in the package with the system off | omit (on) | boolean |
308
+
309
+ **When to author it.**
310
+
311
+ - **The maker fitted traction control** (ASR, ASC+T, TCS, a stability system with a traction
312
+ mode): author it and cite the source next to the car's other equipment. It is part of the real
313
+ car, so its figures were measured with it working.
314
+ - **The maker fitted none** (the F40, the 993 Carrera, most turbo supercars before the 2000s): you
315
+ may still author it, as a **playability aid**, when the keyboard launch FAILs and no accurate
316
+ lever above fixes it. Declare it. Say "traction control: playability aid, not fitted to the real
317
+ car" in the item description and in the ledger. Every figure must stay inside its tolerance.
318
+ - **The car already passes the keyboard launch**: leave it out. It costs a car whose launch already
319
+ works some time: the live 993 goes from 0–100 5.62 s to 5.90 s with the starting block.
320
+
321
+ **How to tune it.** Run `simulate_vehicle` after each change, and read the keyboard-launch verdict,
322
+ the probes, "traction control cutting … s", and 0–100 against its target.
323
+
324
+ 1. Start from the block above.
325
+ 2. A probe still spins: lower `targetSlip` (0.15) or raise `cutRate` (40). If only the probe at
326
+ an upshift re-engagement spins, check that `upshiftTorqueRamp` is `true`.
327
+ 3. The car is now slower than its tolerance, or the keyboard 0–100 gap is a WARN: raise
328
+ `minTorque` (0.5), then `restoreRate` (4–8), then `targetSlip` (0.25).
329
+ 4. The car is now **quicker** than its tolerance. This can happen: the instrument's own launch
330
+ stops wasting time in wheelspin. The F40 with `cutRate: 40` ran 0–100 in 4.23 s against
331
+ 4.60 s ±6 %, and failed. Soften the controller (lower `cutRate`, raise `targetSlip`). Never
332
+ move the targets.
333
+
334
+ Measured on engine 0.3.204 (`helix vehicle simulate`):
335
+
336
+ | Car | Traction control | 0–100 (target) | Keyboard launch |
337
+ | --- | --- | --- | --- |
338
+ | F40, 577 N·m turbo RWD (none fitted) | none | 4.38 s (4.60 ±6 %) | FAIL: spins at 20, 40 and 60 km/h and at the 1→2 |
339
+ | F40 | starting block | 4.38 s | PASS: every probe settles; 2nd-gear post-shift slip 0.39 → 0.22 |
340
+ | 993 Carrera | none | 5.62 s (5.60 ±6 %) | PASS |
341
+ | 993 Carrera | starting block | 5.90 s | PASS. It costs 0.28 s, so leave it off |
342
+ | 993 with 1.6× torque (more than its rears hold) | none | 4.23 s | FAIL |
343
+ | 993 with 1.6× torque | starting block | 4.22 s | PASS |
344
+
345
+ ## Staggered tyres
346
+
347
+ Many sports cars run wider, taller tyres at the rear (a 245/40R17 front and 335/35R17 rear has
348
+ rolling radii of 0.314 m and 0.333 m). Model it as the real car is:
349
+
350
+ - **Draw each axle's tyre at its own size**, with each wheel node's origin at its own hub
351
+ (`rim_lf`/`rim_rf` at the front radius, `rim_lr`/`rim_rr` at the rear).
352
+ - Set each **`simulation.land.tyreByAxle[].radius`** to that axle's measured rolling radius, and
353
+ `physics.chassis.tyreSize` to the real sizes (for example `"245/40R17 / 335/35R17"`).
354
+ - Set `physics.chassis.wheelRadius` to the **driven axle's** radius (the rear on a rear-drive car):
355
+ it sets the rolling radius and so the gearing. Keep
356
+ `groundToBodyOrigin = wheelRadius + staticRideHeight`. The physics contacts still use this one
357
+ radius at all four corners, so the other axle's contact sits off its drawn tyre by the radius
358
+ difference (about 19 mm on a 245/40R17 front and 335/35R17 rear). Look at the parked stance in
359
+ `preview_vehicle`.
360
+ - Visual QA judges spin and the drawn tyre radius per wheel, against that axle's `tyreByAxle`
361
+ radius, so a staggered car passes as it is. Never equalise the tyres to satisfy a check: round
362
+ 1's F40 drew both axles at one size and lost accuracy for nothing.
363
+
364
+ ## 3. Targets
365
+
366
+ `targets` holds the published behaviour, each value with a `tolerance` in (0, 1):
367
+
368
+ | Target | Notes |
369
+ | --- | --- |
370
+ | `zeroToSixtyMphS` | |
371
+ | `zeroToHundredKphS` | |
372
+ | `quarterMileS` | |
373
+ | `topSpeedKph` | The **ungoverned** figure (see "Top speed" below). |
374
+ | `ungovernedTopSpeedKph` | Derestricted, drag-limited speed. Equal to `topSpeedKph`. |
375
+ | `lateralG` | |
376
+ | `brakingSixtyToZeroM` | |
377
+
378
+ The width of each tolerance is itself a claim. Use about ±6 % on a manufacturer figure, and
379
+ ±10–15 % on derived figures such as a Fox/Hale quarter mile
380
+ (`ET = 6.290 × (lb/hp)^(1/3)`) or a lateral g.
381
+
382
+ **Braking:** take the median of the published stopping distances, from the same speed. Convert
383
+ before comparing (70–0 mph = 1.36 × the 60–0 distance at the same deceleration), and never keep
384
+ the shortest figure because it flatters the car.
385
+
386
+ ### Top speed: governed vs ungoverned
387
+
388
+ **The platform car runs ungoverned.** A market or gentlemen's-agreement governor (a domestic-market 180 km/h cap, a 250 km/h agreement, a
389
+ tyre-rated limiter) is a rule about the car, not its physics, and the live land solver never applies `physics.engine.speedLimiterKph`:
390
+ only the `simulate_vehicle` instrument reads it. Authoring one makes the instrument, `targets.topSpeedKph`, the customizer figures and the
391
+ description all say a number the live car does not obey.
392
+
393
+ - `physics.engine.speedLimiterKph.value` is `null`. Say the real car's governor, if it had one, in the description only.
394
+ - `targets.topSpeedKph` and `targets.ungovernedTopSpeedKph` are the **ungoverned** figure (the best derestricted instrumented test, else
395
+ the maker's claim, else the gearing/drag figure), with an honest tolerance.
396
+ - `SPEED_LIMITER_IS_A_MARKET_GOVERNOR` (a warning from `simulate_vehicle` and `validate_vehicle`) fires when the limiter is below
397
+ 0.9 × the ungoverned figure. Its fix is the two lines above.
398
+ - No add-on can add or remove a limiter: the ECU slot's tune paths (`torqueCurve`, `limiterRpm`, …) have no speed-limiter path and the
399
+ backend refuses one. Do not describe an ECU part as "removing the limiter" on a car that has none.
400
+
401
+ ### Torque: target the declared crank figures
402
+
403
+ The maker's declared peak crank torque and power (with their rpm) **are** the engine. Scale the curve to them, in both places
404
+ (`physics.engine.torqueCurve` and `simulation.land.driveline.engine.torqueCurve.points`, which must stay identical), and state
405
+ `peakTorqueNm` / `peakPowerKw` to match. Never tune to dyno folklore (a wheel dyno divided by a guessed efficiency, a forum "real"
406
+ figure) or to a top-speed number.
407
+
408
+ - Real-world losses live in `drivetrainEfficiency` (cited) and aero drag. 0–100 and top speed are met with grip, gearing, launch and
409
+ exact mass, not with a bigger engine.
410
+ - Put the maker's figures in a `manufacturer` citation title (`"200 kW / 272 PS at 6,100 rpm, 330 N·m at 5,000 rpm"`):
411
+ `TORQUE_CURVE_EXCEEDS_DECLARED_CRANK_FIGURES` (a warning from `simulate_vehicle` and `validate_vehicle`) reads N·m / kgm / lb-ft and
412
+ kW / PS / hp from those titles, ignores "at the wheels" and dyno lines, and falls back to the package's own peak fields. It fires when
413
+ a curve or peak field is more than 5 % above the declared figure.
414
+ - If a documented market cap (an industry agreement) understates the real car and the targets then miss, keep the declared curve, say so
415
+ in the description and the ledger, and close the gap only with the levers above. A car at its honest crank figure that is 10 % slow is
416
+ a better product than an inflated one.
417
+
418
+ ## 4. Checking it
419
+
420
+ - **Before publish, structure:** `validate_vehicle`.
421
+ - **Before publish, behaviour:** `simulate_vehicle({ target: <dir> })` drives the package on the
422
+ platform's own vehicle runtime (the live `humanoid-character` bundle, fetched and cached; it
423
+ prints the runtime version it ran) and reports 0–100 km/h, 0–60 mph, the quarter mile, top
424
+ speed (governed and ungoverned), 100–0 km/h and 60–0 mph braking, skidpad lateral g, turbo lag,
425
+ the gears used and the rest pose.
426
+ - Each figure with a `targets` entry is compared: it passes when
427
+ `|simulated − target| / target ≤ tolerance` (a fraction: 0.06 is ±6 %).
428
+ - The rest pose fails when the car is not grounded or rests with pitch or roll beyond 3°.
429
+ - `spec` adds an extra target file: a package, `{ targets: {…} }`, or a reference spec of
430
+ `perf.*` figures (`perf.0_100_kmh_s`, `perf.braking_100_0_m`, `perf.braking_60mph_0_m`,
431
+ `perf.turbo_lag_s`, …; a leaf is a number or `{ value, tol_pct }`, default ±10 %). Use it for
432
+ figures `targets` cannot hold, such as 100–0 braking and turbo lag.
433
+ - It also runs the keyboard launch and its hands-off yaw probe (see "Drivable on a keyboard"
434
+ above): keyboard 0–100 against the instrument, wheelspin per gear, post-shift yaw, lane offset.
435
+ - Exit 0: every compared figure passes and the keyboard launch does not FAIL. 1: a figure is out
436
+ of tolerance, the rest pose is wrong, or the car is not drivable on a keyboard. 2: it could not
437
+ simulate.
438
+ - `simulate_vehicle({ itemId })` drives a **published** car's stored package the same way. Run
439
+ it on a control car with known real figures (the live 993) to see the instrument's own bias.
440
+ - `validate_vehicle` runs the same check whenever the package has `targets`
441
+ (`SIMULATED_PERFORMANCE_OUT_OF_TOLERANCE`, `SIMULATED_REST_POSE_INVALID`), and so does
442
+ `publish_vehicle`, which refuses before the mint. `noSimulate` skips it.
443
+
444
+ Iterate on `grip.peak`, `suspension.*`, launch behaviour and, for a turbo car, the derated low
445
+ end, never on cited engine or gearing values. Record the final figures in your ledger beside
446
+ the targets.
447
+ - **On the live car:** time 0–100 in a hosted world, in a median of 3 runs, with a control car
448
+ of known real figures in the same world. A hosted control read 0.7–1 s slow against its real
449
+ figure, so judge the delta.
450
+ - Discard any sample after a wheel leaves the ground. The `universal-items-empty` floor ends
451
+ about 80 m from spawn, and an airborne car chain-shifts.
452
+ - Top speed cannot be measured live in that world.
453
+ - **The clock origin matters.** Time from throttle application, not from motion.
454
+ - **If the car is "slow" but revs high:** check for a kerb or wall contact (the chassis box clears
455
+ 0.16 m) and for sim time against wall time before touching the drivetrain.
456
+
457
+ The worked 993 values are: 1,370 kg; 0.39 front; CoM 0.48 m; wheel radius 0.315; static ride
458
+ height 0.165; ride 1.55/1.75 Hz; damping 0.45; anti-roll bars 14,000/9,000; Cd 0.33; area 1.8 m²;
459
+ 330 N·m at 5,000 rpm; 200 kW at 6,100 rpm; limiter 6,800; gears
460
+ 3.818/2.15/1.56/1.242/1.027/0.821; final drive 3.444; reverse −3.545; shift 0.25 s; brakes
461
+ 7,400 N·m with 0.66 front bias; tyre peak 1.18 and sliding 1.08; lock 34°. They reproduced the
462
+ manufacturer's 0–100 of 5.6 s offline (5.58 s on the solver `simulate_vehicle` runs), and
463
+ 5.3–5.55 s live.