Area heat. Road heat alone rewarded a strange kind of play: work one part of town hard but take a different street every time, and nothing ever got hot. A second, coarser pool now accrues from being inside a district at all — on-road or off — and bleeds into every road running through it, including ones never driven. Deliberately capped below the barricade threshold: a hot district gets roads patrolled, but concrete still takes somebody actually using that road. It also decays slower than road heat, because one patrol re-secures a road while a district's reputation lingers. Surfaces. Off the route the car now makes about 40% of its road top speed, loses grip and scrubs momentum in a fifth of the distance, so the road hierarchy is something you feel rather than something the map merely claims. A first tuning made off-road a 12 km/h crawl, which removed the choice instead of pricing it — going round a checkpoint cross-country has to stay a legitimate move. Speed. Heat scales with how fast you were going, not just how far, and stops rewarding ever-higher speed past a point so the answer is never one exact number on the clock. With the road classes, that makes the routing decision real: the trunk fast and noticed, or the lanes slow and ignored. Save format goes to v2 for the district pool; older saves are refused rather than half-restored. Also fixes a population test that hardcoded densities since tuned, and README claims about patrols and AI that Phase 6 made untrue. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
515 lines
26 KiB
Markdown
515 lines
26 KiB
Markdown
# Drive Between the Lines — prototype
|
||
|
||
Endless driving survival game.
|
||
|
||
- **Phase 0** — a drivable car with persistent wear that is felt through the wheel.
|
||
- **Phase 1** — a road network whose roads remember being driven, and escalate.
|
||
- **Phase 2** — bases, a quest board, and known-vs-new target selection.
|
||
- **Phase 3** — regional control: behind your own lines, nobody is watching.
|
||
- **Phase 4** — the front line moves, from your work and on its own.
|
||
- **Phase 5** — four distinct jobs, radio texture, and work found in the field.
|
||
- **Phase 6** — an inhabited world: traffic, people, patrols, and a live war.
|
||
- **Phase 7** — sound, synthesised from the simulation.
|
||
|
||
## Running
|
||
|
||
```bash
|
||
npm run dev
|
||
```
|
||
|
||
Then open http://localhost:5173. Append `?seed=anything` to regenerate the world —
|
||
seeds may be numbers or words.
|
||
|
||
| | |
|
||
|---|---|
|
||
| `W` / `S` | throttle / brake-reverse |
|
||
| `A` / `D` | steer |
|
||
| `Space` | handbrake (rear wheels only) |
|
||
| `R` | respawn the car — **does not** repair it |
|
||
| `1`–`3` | accept a mission, when parked at a base |
|
||
| `M` | sound on/off |
|
||
| hold `R` | wipe the campaign and start over (5 seconds) |
|
||
|
||
Progress saves itself every 20 seconds, and immediately whenever you take or
|
||
finish a job. Saves are per seed; `?seed=x` is a separate campaign.
|
||
|
||
Drive to a green beacon (a base) and stop. Take a job, drive to the amber
|
||
beacon, stop and hold position for three seconds, then return to **any** base —
|
||
the amber beacon goes out once the job at the target is done, since the target
|
||
is no longer anywhere you need to be.
|
||
|
||
```bash
|
||
npm test # sim + headless physics
|
||
npm run build # typecheck + production bundle
|
||
```
|
||
|
||
## Road heat
|
||
|
||
Every metre driven **on or alongside** a road adds heat to that segment; every
|
||
road everywhere sheds it slowly. Cross a threshold and the road escalates:
|
||
|
||
`Clear → Patrol → Barricade → Turret`
|
||
|
||
- **Patrol** sends a car to work the road for a while, which cools it back down.
|
||
- **Barricade** sends an engineer to pour concrete across it, leaving a gap you
|
||
have to slow for.
|
||
- **Turret** sends another to put up a tower, and a gunner to man it.
|
||
|
||
None of that appears on its own — see [A world with people in it](#a-world-with-people-in-it).
|
||
|
||
Roughly four traversals take a road from clear to turret; an untouched road
|
||
cools off in about four minutes. Both numbers are in `sim/heat.ts` and both are
|
||
guesses meant to be tuned.
|
||
|
||
### District heat
|
||
|
||
Roads remember being used. Districts remember *you*.
|
||
|
||
Road heat alone rewards a strange kind of play: work one part of town hard but
|
||
take a different street every time, and nothing ever gets hot. So there is a
|
||
second, coarser pool. Every metre you cover inside a district raises its
|
||
standing — on-road or off, whether or not you touch a road at all — and that
|
||
bleeds into every road running through it, including ones you have never driven.
|
||
|
||
- It **accrues from being there**, so cutting across country is not a loophole.
|
||
- It **cannot fortify a road on its own** (`AREA_INFLUENCE` sits below the
|
||
barricade threshold). A hot district gets roads patrolled; concrete still
|
||
takes somebody actually using that road.
|
||
- It **outlasts road heat**. A road is re-secured by one patrol; a district's
|
||
reputation takes far longer to fade.
|
||
|
||
### Speed is conspicuous
|
||
|
||
Heat scales with how fast you were going, not just how far. A car at walking
|
||
pace is traffic; a car doing ninety through a checkpointed district is the thing
|
||
people phone in. Roughly a 2.2× spread between crawling and flat out, and it
|
||
stops rewarding ever-higher speed past a point so the answer is never one exact
|
||
number on the clock.
|
||
|
||
Together with the road classes this is the real routing decision: the trunk fast
|
||
and noticed, or the lanes slow and ignored.
|
||
|
||
### The road hierarchy
|
||
|
||
Roads are not all the same road:
|
||
|
||
| Class | Tarmac | Speed | Attention |
|
||
|---|---|---|---|
|
||
| Track (single car) | 4.5 m | ×0.55 | ×0.55 |
|
||
| Road (two lanes) | 9 m | ×1 | ×1 |
|
||
| Trunk (four lanes) | 15 m | ×1.35 | ×1.35 |
|
||
|
||
That trade is the network doing real work rather than being the floor you drive
|
||
on: a trunk is fast and direct and exactly where anyone looking for you would
|
||
look; a lane is slow and awkward and nobody is watching it. Trunks run the whole
|
||
width of the map and are never thinned away, so there is a shape to learn — get
|
||
onto the trunk, then work down the lanes — instead of a uniform mesh where every
|
||
road is as good as every other. Route previews are costed by **travel time**, not
|
||
distance, so the board's estimate matches the way a driver would actually go.
|
||
|
||
### The route corridor
|
||
|
||
Heat is credited to a road for anything within its corridor — half the tarmac
|
||
plus a 7.5 m verge, so it scales with the class — not just the tarmac itself.
|
||
Otherwise the dominant strategy is to drive *next to* every road and never
|
||
accrue heat at all.
|
||
|
||
Two consequences follow, and both are deliberate:
|
||
|
||
- **The cutoff is hard, not a taper.** A taper leaves a gradient to optimise
|
||
along — sit at 80% of the corridor, take 20% of the heat. A cliff edge means
|
||
you either use the route or you actually leave it.
|
||
- **Barricades span the full corridor**, well past the kerb. A checkpoint that
|
||
stopped at the tarmac would be free to round on the verge at speed.
|
||
|
||
The 7.5 m verge is about two car widths, so hugging the shoulder gains nothing,
|
||
while leaving most of the map genuinely off-route. Widening it backfires: make
|
||
the corridor too generous and heat becomes unavoidable everywhere, and "take a
|
||
different route" stops being a choice at all.
|
||
|
||
### Tarmac versus everything else
|
||
|
||
Roads have to be worth using, or the hierarchy above is decoration. Off the
|
||
route the car makes about 40% of its road top speed, loses grip, and scrubs off
|
||
momentum in a fifth of the distance. It is a real cost, deliberately not a wall:
|
||
going around a checkpoint cross-country is a legitimate move, and an earlier
|
||
tuning that made off-road a 12 km/h crawl removed the choice rather than pricing
|
||
it.
|
||
|
||
### Buildings, and why they are dense
|
||
|
||
Off-road used to be open country, which made every barricade optional — you just
|
||
swung wide around it. Buildings are now laid out on a jittered lattice (random
|
||
scatter cannot pack boxes and saturated at a third of the target), which takes
|
||
bypass lanes around a checkpoint from none obstructed to about two thirds. You
|
||
can still get around one; it means threading a maze at walking pace instead of a
|
||
free detour at speed.
|
||
|
||
### Other deliberate choices
|
||
|
||
- **Concrete never closes around the car standing in the gap.** A block spawning
|
||
on top of you would put a static collider inside the chassis, so the last slab
|
||
waits until you move.
|
||
- **A checkpoint's position is seeded from its segment**, so a given stretch of
|
||
road always fortifies in the same place. Recognising it is part of learning
|
||
the map.
|
||
|
||
The road network is a jittered grid with roughly a quarter of its edges removed,
|
||
keeping the whole thing connected. The loops are the point: "take a different
|
||
road this time" is not a decision unless alternative routes exist.
|
||
|
||
## Missions and intel
|
||
|
||
The board at each base offers a mix of **known targets** and **new** ones, and
|
||
describes the route to each in words rather than numbers — "barricaded · last
|
||
word 6 min ago", "no one has been down that way".
|
||
|
||
The important part is *where those words come from*. The board never reads heat
|
||
directly. It reads `sim/intel.ts`, which is a snapshot of what the player has
|
||
actually observed and when. Drive a road and you write down what was on it;
|
||
leave it alone and your note ages while the road keeps escalating without you.
|
||
So a known route is genuinely knowable, genuinely stale, and never a guarantee —
|
||
which is the tension the brief asks for.
|
||
|
||
**Recon missions** are the tool for converting a new target into a known one:
|
||
completing one surveys every road within 130 m, filling in the map without
|
||
having to drive each one.
|
||
|
||
Four jobs run end to end, and they differ in **pacing and risk**, not just in
|
||
the noun on the board — otherwise four mission types is one mission type with
|
||
four labels:
|
||
|
||
| Job | At the target | The risk |
|
||
|---|---|---|
|
||
| **Supply run** | 3s unloading | the drive out |
|
||
| **Retrieval** | 5s loading | the drive *home*, with goods that break |
|
||
| **Intel drop** | 9s transmitting | a long wait, stationary, in the open |
|
||
| **Survey** | 2s | cheapest on site; the payoff is the map |
|
||
|
||
Retrieval is the one that carries something home, and impacts damage it. It pays
|
||
the most, but it pays for what *arrives*, not what you set off with. Hand-in
|
||
works at *any* base, not the issuing one, because the brief is explicit that a
|
||
blocked road home must never strand the player.
|
||
|
||
### Work you find rather than work you are given
|
||
|
||
Past the line, and only when you have nothing in hand, someone occasionally
|
||
turns up: a wounded stranger, a contact flashing headlights, a wreck worth
|
||
stripping. They are deliberately **not** on a board — you cannot plan a route to
|
||
one, compare it against anything, or shop between them. You take the detour in
|
||
front of you or you drive on.
|
||
|
||
### Radio
|
||
|
||
Chatter that frames who you are — an undercover driver being talked at by people
|
||
busy elsewhere. Every line is triggered by something actually happening in the
|
||
sim: crossing a border, a road you have made notorious, a car past saving, or
|
||
the front genuinely drifting your way, which is the brief's "the enemy has other
|
||
priorities" said out loud without ever showing a number.
|
||
|
||
### Rewards, and why repairs exist
|
||
|
||
New targets pay `NOVELTY_BONUS` (1.8×) more than known ones. That multiplier is
|
||
the phase's tuning dial: if you never take the new target it is too low, and if
|
||
you never take the known one it is too high.
|
||
|
||
Payment is in **parts**, which repair the car — the only thing in the game that
|
||
pushes condition back up. Repairs go to the worst subsystem first and stop at
|
||
its **ceiling**, which drops permanently with every bit of damage taken
|
||
(`PERMANENT_SHARE`, currently 30%). So a car can be patched back to what it is
|
||
still capable of, but never to what it was. The HUD bars show this directly: `█`
|
||
is current condition, `·` is what repairs could still reach, `×` is gone.
|
||
|
||
This is what gives the board's reward a reason to matter. Without an economy the
|
||
choice between a safe route and a risky one has no stakes; with one, the risky
|
||
job is what keeps the car alive a while longer.
|
||
|
||
## A world with people in it
|
||
|
||
Up to Phase 5 the world was scenery. Heat poured concrete on a road and that was
|
||
the whole of the enemy. `sim/units.ts` is the layer that makes it inhabited.
|
||
|
||
**Enemy ground is patrolled because it is enemy ground.** There are standing
|
||
patrols on the roads wherever you are past the line, and more of them the deeper
|
||
in you go — none in liberated territory, one in contested, three in occupied,
|
||
five on the frontier. Nobody summoned these; they are what hostile territory
|
||
looks like.
|
||
|
||
**On top of that, nothing appears — things are sent.** When a road turns
|
||
notorious a patrol is *dispatched*: it starts at a junction a few hundred metres
|
||
away, drives there, sweeps up and down for about a minute, and while it works the
|
||
road it brings the heat back down. Then it leaves. The enemy is not punishing
|
||
you; they are re-securing a route and going home.
|
||
|
||
**Everything on a road is built, including the concrete.** At `barricade` an
|
||
engineer is dispatched and the blocks go up over about twenty seconds of someone
|
||
standing there; at `turret` another is sent and the tower takes forty. Arrive
|
||
mid-build and you find a half-finished stump with nobody on it. Only once the
|
||
tower stands does a gunner man it, up on top so he shoots over his own barricade.
|
||
Let the road cool and the whole thing is abandoned.
|
||
|
||
**Traffic and people.** Civilian cars route across the network with somewhere to
|
||
be; people wander on foot near the buildings. Both scatter when shooting starts.
|
||
|
||
### The war you are driving through
|
||
|
||
Skirmishes break out between the two armies on contested and occupied ground,
|
||
whether or not you are anywhere near. They are not a set piece for the player —
|
||
you are not invited.
|
||
|
||
Rounds are **real objects in flight**, not instant hits resolved between two
|
||
combatants. That is the whole point: if shots resolved instantly, driving through
|
||
a battle would cost nothing, and "the only road to the target runs through a
|
||
firefight" would not be a decision. A stray round does not check whose war it is,
|
||
so it will hit a bystander, a civilian, or you.
|
||
|
||
Buildings stop bullets, which is what makes cover worth anything. Hits are routed
|
||
through the same wear model as a crash, so being shot costs you ceiling too — it
|
||
is permanent in the same way everything else is.
|
||
|
||
**The enemy does not shoot at you by default.** You are an undercover driver, not
|
||
a target. They open up only past the line *and* on a road they have already
|
||
checkpointed — that is, a road you personally made notorious.
|
||
|
||
## Sound
|
||
|
||
Everything is **synthesised** — there are no audio files. The build has to stay
|
||
self-contained, so the engine is detuned oscillators, the tyres and gunfire are
|
||
shaped noise, and the ambience is a filtered drone. That constraint turned out to
|
||
be an advantage: sound generated from the simulation can carry the simulation's
|
||
state rather than just decorating it.
|
||
|
||
- **The engine has gears**, so the note climbs and drops rather than rising
|
||
forever with the speedometer.
|
||
- **A worn engine sounds wrong**, not just slow: roughness rises and the top of
|
||
the rev range falls away as condition drops. The car's decline is audible.
|
||
- **Bald tyres squeal sooner** than good ones, off the same lateral slip the
|
||
physics actually reports.
|
||
- **Gunfire is positional** — it falls off with the square of distance and pans
|
||
as you turn, so a firefight two streets away is a direction, not just a noise.
|
||
- **Territory has a tone.** The drone drops and thickens the further past the
|
||
line you are, matching what the palette does visually.
|
||
|
||
The split is deliberate: `audio/mix.ts` holds every decision as plain numbers
|
||
and is unit-tested; `audio/audio.ts` is only Web Audio wiring, which cannot be
|
||
tested outside a browser. Browsers refuse to make noise before the user has
|
||
interacted, so the context is resumed on the first keypress or click.
|
||
|
||
## Regional control
|
||
|
||
The map is cut by a single **front line** into four bands, running from your own
|
||
territory out past the edge of the known world:
|
||
|
||
`Liberated → Contested → Occupied → Frontier`
|
||
|
||
| Band | Heat accrual | Heat decay |
|
||
|---|---|---|
|
||
| Liberated | **×0** | ×4 |
|
||
| Contested | ×0.55 | ×1.4 |
|
||
| Occupied | ×1 | ×1 |
|
||
| Frontier | ×1.3 | ×0.8 |
|
||
|
||
Liberated accrual is exactly zero, not merely small. A trickle would eventually
|
||
fortify your own back garden, which is nonsense, and it would mean the safe area
|
||
quietly punished you for using it. Behind your own lines, nobody is writing down
|
||
which roads you take.
|
||
|
||
Decay is judged by whoever holds *the road*, not whoever holds the ground the
|
||
car is standing on — your own side dismantles checkpoints on its own roads
|
||
whether or not you are there to watch.
|
||
|
||
### The line moves
|
||
|
||
Two forces, and they are independent by design.
|
||
|
||
**Your work.** Completing a mission pushes the front forward, weighted by what
|
||
the job was worth — a long haul into unknown country moves the war more than a
|
||
local errand, and supply runs move it more than surveys. Ground won is not held
|
||
forever: the player's contribution decays over about seven minutes, so the enemy
|
||
presses back on its own.
|
||
|
||
**The war elsewhere.** The line also drifts with nobody's help, in both
|
||
directions. This is not background flavour: the brief is explicit that ambient
|
||
drift is what makes the setting a war rather than a level, and that it is the
|
||
*only* mechanism for the enemy's attention being pulled elsewhere — there is no
|
||
separate "baseline threat" system alongside it.
|
||
|
||
Drift is a sum of slow waves rather than a random walk. A walk either wanders
|
||
off the map or, once reined in, moves so little the war reads as static. Waves
|
||
at incommensurate periods are unpredictable in practice, always bounded, and
|
||
reproducible from the seed — so "the region changed while you were away" is
|
||
something a test can assert. Most of the movement is shared by every boundary,
|
||
because a front that advances advances as a whole; each band then wobbles a
|
||
little on its own so the map is not one rigid object sliding about.
|
||
|
||
In practice a boundary swings about ±45 m and a spot near a border changes hands
|
||
several times over a quarter of an hour.
|
||
|
||
**Nothing announces any of this.** There is no notification, no readout, no
|
||
number. The minimap shows who held each patch of ground *when you last stood in
|
||
it* — so after a while away, your map is simply wrong, and the only way to find
|
||
out is to go back and look.
|
||
|
||
Your own ground is a pocket, not the map: roughly 15% liberated, 25% contested,
|
||
27% occupied, 33% frontier. The starting base sits at the friendly *edge* rather
|
||
than in the middle, because starting central put half the world harmlessly
|
||
behind your own line where nothing watches and nothing happens. Those splits are
|
||
measured, not guessed — the bands are strips across a square with the axis
|
||
running diagonally, so area is nowhere near linear in depth.
|
||
|
||
**You are never told which band you are in by the world itself.** Crossing a
|
||
border eases the fog, the light and the palette: your own ground is open and
|
||
green, contested goes flat and hazy, occupied turns dusty and closes in, the
|
||
frontier is cold and short-sighted. The HUD names the band for now, but that
|
||
line is debug scaffolding for the same reason the heat numbers are — Phase 3's
|
||
gate is telling them apart *without* being told.
|
||
|
||
## The map
|
||
|
||
A sketch map, top right, drawn from `Intel` rather than from the world:
|
||
|
||
- **The road network's shape is always visible**, as faint dashed lines. You are
|
||
an operative with a map, not an amnesiac.
|
||
- **Roads you have actually seen** are drawn solid and coloured by the heat you
|
||
remember on them — which is what you last saw, not what is there now.
|
||
- **Ground you have driven through** is filled in, tinted by who held it *when
|
||
you were last there*. A survey mission fills in a whole area at once.
|
||
- **The border** is drawn between adjacent remembered cells that you recall as
|
||
belonging to different sides — never from the front's true position, which
|
||
would hand you a live readout of a line that is supposed to move behind your
|
||
back.
|
||
- **Old notes fade.** An observation drawn faint is one you should not trust.
|
||
|
||
Nothing on the map reads live world state. Heat is what you last saw on a road;
|
||
territory is who held it when you last stood there. Both keep changing after you
|
||
leave, and the map has no way of knowing.
|
||
|
||
The HUD also carries an eight-point compass arrow to the active target, since
|
||
the map alone does not tell you which way to point the car.
|
||
|
||
## Layout
|
||
|
||
The one rule worth keeping: **`src/sim/` imports neither three.js nor Rapier.**
|
||
Phases 1–4 (heat, regions, front lines, quests) are all simulation, and keeping
|
||
them engine-free is what makes them unit-testable and fast-forwardable — you can
|
||
run a hundred simulated days in milliseconds to tune an escalation curve without
|
||
ever opening a browser.
|
||
|
||
```
|
||
src/
|
||
sim/ pure model, no engine imports:
|
||
world, roads, heat — the map and its memory
|
||
radio, opportunities — texture: chatter and field work
|
||
units, combat — traffic, patrols, soldiers, rounds
|
||
save — the campaign as JSON
|
||
regions — the front line and who holds what
|
||
intel, routing, quests — what the player knows and is asked to do
|
||
bases, car — where missions come from, and decline
|
||
physics/ Rapier world, raycast vehicle, input → wheel forces
|
||
render/ three.js scene, roads, markers, chase camera
|
||
core/ fixed-timestep loop, seeded RNG, keyboard
|
||
ui/ debug HUD, quest board, sketch minimap
|
||
audio/ mix (pure: what it should sound like), audio (Web Audio wiring)
|
||
persistence.ts integration layer: saves in localStorage
|
||
debug.ts ?debug=1 handle: step, render and capture frames to shots/
|
||
carSpec.ts shared car dimensions, so body and mesh cannot drift apart
|
||
heatProps.ts integration layer: heat levels → colliders + meshes
|
||
```
|
||
|
||
`heatProps.ts` sits at the top level on purpose — it is the one module allowed to
|
||
touch both Rapier and three.js, because it owns objects that must exist in both
|
||
or neither. Its tests run headless: three.js scene graphs work fine in Node, so
|
||
"the barricade's collider was removed along with its mesh" is a unit test.
|
||
|
||
Data flows one way: `sim` → `physics` → `render`. Condition reaches the physics
|
||
layer already digested into a `Handling` by `deriveHandling`, so there is exactly
|
||
one place where "how broken the car is" turns into "how it drives".
|
||
|
||
## Notes on the state of it
|
||
|
||
- **The car handles like a placeholder.** Suspension, grip, and engine numbers in
|
||
`physics/physics.ts` and `sim/car.ts` are first guesses that pass a smoke test,
|
||
not something tuned by feel. That tuning *is* Phase 0's verification gate.
|
||
- **Wear rates are deliberately aggressive** so decline is visible in minutes
|
||
rather than hours. Turn them down in `applyWear` once the curve reads right.
|
||
- **No interpolation** between physics steps. Fine at 60 Hz; revisit if the step
|
||
rate changes.
|
||
- **Nothing is persisted** across reloads — condition, heat, intel and completed
|
||
runs all reset. IndexedDB comes with the campaign layer, and `sim/intel.ts`
|
||
plus `sim/heat.ts` are the two things that most need it.
|
||
- **Repairs are automatic on hand-in.** Parts get spent the moment you report
|
||
back. The brief wants repair to be a physical, on-foot act with scavenged
|
||
parts; that is Phase 6.
|
||
- **The board's route preview assumes the shortest path.** If you drive a
|
||
different way, the intel you were shown described a route you did not take.
|
||
`findRoute` already accepts a cost function, so a "safest route" preview is a
|
||
small change when it is wanted.
|
||
- **Junctions have no heat of their own.** A point near a junction is credited to
|
||
whichever segment is nearest, so a heavily used crossroads never fortifies as a
|
||
crossroads. Known gap, deferred on purpose.
|
||
- **Barricades are visually crude.** Blocking the whole 24 m corridor means two
|
||
long concrete slabs, which reads more like a wall than a checkpoint. The
|
||
gameplay shape is right; the presentation wants berms, wire, or wreckage on the
|
||
outer sections. They can also clip scenery placed on the verge — both are
|
||
static bodies, so physics is unaffected, but it looks wrong up close.
|
||
- **Heat has no diegetic signal at a distance.** You learn a road is hot by
|
||
arriving at the checkpoint. The brief wants it readable from patrol density and
|
||
wreckage before you commit — that needs the enemy presence Phase 5 brings.
|
||
- **The heat HUD lines are debug scaffolding.** The brief is explicit that no
|
||
numeric heat meter ships; they exist to tune the curve and should come out.
|
||
- **Bundle:** ~2.7 MB raw / ~945 KB gzipped, dominated by Rapier's WASM, which
|
||
`rapier3d-compat` inlines as base64. Switching to the non-compat `@dimforge/rapier3d`
|
||
package serves the WASM as a separate file (~570 KB gzipped, compiled in
|
||
parallel with the JS) at the cost of extra Vite plugin config. Worth doing
|
||
before anyone but you plays it; not worth doing now.
|
||
|
||
## Next
|
||
|
||
Two gates are open at once, and both need a person, not a test.
|
||
|
||
**Phase 1:** you catch yourself avoiding a road because of its history, not its
|
||
distance. If it fails, check `METRES_PER_HEAT` (escalation too slow to matter in
|
||
one session), `DECAY_PER_SECOND` (nothing accumulates), or whether the props are
|
||
actually inconvenient enough to route around.
|
||
|
||
**Phase 2:** you take the new target a meaningful fraction of the time, for
|
||
reasons other than curiosity — because the known one has genuinely got
|
||
expensive. If the known option is always strictly better, raise `NOVELTY_BONUS`.
|
||
If you never take a known target, lower it.
|
||
|
||
**Phase 3:** driving into an occupied region, you can tell it from a liberated
|
||
one within a couple of minutes, from tone alone, without reading the HUD line
|
||
that names it. If you cannot, the palettes in `render/scene.ts` are too close
|
||
together — push them apart before trusting the band boundaries.
|
||
|
||
**Phase 5:** someone coming to this cold can describe what kind of game it is
|
||
and who they are playing after one session, without the premise being explained
|
||
first. If the four jobs still feel like one job with four names, the dial is
|
||
`MISSION_SHAPE` — push the work times and the cargo risk further apart before
|
||
adding anything new.
|
||
|
||
**Phase 4:** returning to a region after time away, you are genuinely unsure
|
||
what you will find until you are back on the ground — and when it has changed,
|
||
it reads as the world having moved rather than as a notification you were given.
|
||
If the world instead feels arbitrary, the drift is too fast: lengthen the wave
|
||
periods in `sim/regions.ts` before shrinking their amplitude, since it is the
|
||
*rate* of change that reads as noise, not the size of it.
|
||
|
||
**Phase 6:** the world should feel lived in before it feels dangerous — traffic
|
||
and people you have no reason to interact with, and a war that is obviously not
|
||
about you. If firefights read as encounters staged for the player, the spacing
|
||
and spawn rules in `sim/units.ts` are the dial.
|
||
|
||
Still deferred: a deeper repair economy
|
||
with scavenged parts and on-foot work, car identity and disguise feeding into
|
||
heat, and expanded combat. Nothing there should be pulled forward until the loop
|
||
below it is proven.
|
||
|
||
The most useful thing before any of that is taking the debug lines out of the
|
||
HUD. Several gates now depend on reading the world rather than a readout, and
|
||
the scaffolding actively works against you when you sit down to judge them.
|
||
|
||
Watch for one failure mode in particular: if you find yourself taking whichever
|
||
job is *nearest* and ignoring the intel line entirely, the board is decorative
|
||
and the phase has not landed, whatever the reward numbers say.
|