HighwaySpawner
Decides when a car comes out of a pool onto the road, and when it goes back.
What it does
- Civilians are a budget: about
civilianDensityper 100 m of road ahead, never more thanmaxCivilians. Each one goes into a free lane, faces up the road and is sent off at its own pace. - Police patrol in the traffic. One turns up ahead every
policeEveryseconds or so, from levelpoliceFrom. There are never more thanmaxPoliceat once. - Enemies come from behind, the way a car grows out of the bottom of the screen. Which kind comes is a weighted pick from the
enemiesallowed at that level. - A car that dies becomes a wreck: its driver lets go, and after
wreckSecondsit is cleared. - Cars that fall
releaseBehindmetres behind or runreleaseAheadmetres ahead go back to their pools.
A car straight out of a pool is standing still. A parked car 150 m ahead is a roadblock, not traffic, so each new car gets a carDrive.nudge that sets it moving. Civilians leave at a speed between civilianSpeedMin and civilianSpeedMax. Enemies leave enemyCatchUp m/s faster than the player, so they close from behind.
Use it in a scene
A scene is just data — a list of entities, each with its components. At startup the SceneLoader turns this JSON into a live world; edit the file and reload to rebuild it.
Build it from scratch with the bjs CLI:
- 1Scaffold a project
World, renderer, and dev server — ready to run.
- 2Install dependencies
- 3Add HighwaySpawner
Copies its source into src/ so the scene resolves.
First time? Run bjs login once.
- 4Paste the scene into src/scenes/arcade-room.ts and run
SceneLoader builds the world from the JSON; reload to rebuild.
Every name in civilians, police and enemies is a Pool. The scene also needs a HighwayRoad; with no road, nothing spawns. Only the first HighwaySpawner in a scene runs.
The civilian budget
Traffic is a count to hold:
The System passes aheadMax as the window:
When a car is released, the count drops below the target and a new one is placed. Until half the target is out, several can arrive in the same frame; after that, new civilians come 0.35 s apart.
A new car only goes where no car sits within minGap metres along the road in the same lane. An enemy needs only 0.7 of that.
The difficulty curve
Each level cuts the gap between enemies by 14%, adds an enemy slot about every one and a half levels, and adds 8% more civilians. With the defaults:
Each wait is jittered by ±25%, so the arrivals never feel like a metronome. A director raises the level with highwaySpawner.level.set.
Settings
| Field | Default | What it does |
|---|---|---|
follow | "Player" | The car the traffic is arranged around |
spawning | true | Spawn new cars. Range and wreck clean-up run either way. |
level | 1 | Difficulty level, 1 and up |
civilians | ["civilian"] | Pools of civilian cars, picked at random |
civilianSpeedMin | 20 | Slowest civilian launch speed, m/s |
civilianSpeedMax | 31 | Fastest civilian launch speed, m/s. Police launch at this speed too. |
civilianDensity | 1.2 | Civilians per 100 m of the window ahead |
maxCivilians | 10 | Most civilians at once |
enemies | rammer from 1 (weight 3), gunner from 2 (weight 2), shover from 3 (weight 1.5) | { pool, from, weight } entries: the pool, the first level it can appear on, and its chance against the others |
police | [] | Pools of police cars. Empty for none. |
policeEvery | 22 | Seconds between police patrols, give or take 25% |
maxPolice | 2 | Most police at once |
policeFrom | 1 | First level police patrol on |
enemyInterval | 10 | Seconds between enemy arrivals at level 1 |
maxEnemies | 1 | Enemies at once at level 1 |
enemyCatchUp | 10 | How much faster than the player an enemy arrives, m/s |
aheadMin | 110 | Nearest a civilian or police car appears ahead, metres |
aheadMax | 240 | Farthest a civilian or police car appears ahead, metres. Also the window the civilian budget counts. |
behindMin | 46 | Nearest an enemy appears behind, metres |
behindMax | 70 | Farthest an enemy appears behind, metres |
releaseBehind | 100 | Cars further than this behind go back to their pool |
releaseAhead | 360 | Cars further than this ahead go back to their pool |
minGap | 22 | Clear road a new car needs in its lane, metres |
wreckSeconds | 3 | Seconds a wreck burns before it is cleared |
Events
| Event | Payload | Direction |
|---|---|---|
highwaySpawner.spawned | { entityId, pool, role, enemy, police } | emits |
highwaySpawner.released | { entityId, pool, reason }, reason is range, wreck or cleared | emits |
carDrive.nudge | { entityId, dvx, dvz } | emits, to launch each new car |
highwaySpawner.level.set | { level } | listens |
highwaySpawner.enabled.set | { enabled } | listens: sets spawning |
highwaySpawner.clear | {} | listens: every car back to its pool for a new game |
health.died | { entityId } | listens: one of its cars becomes a wreck |
Needs
- Pool holds every car it places.
- HighwayRoad gives lanes, bridges (it never spawns on one) and road distance.
- CarDrive marks which entities are cars, gives the player's speed, and takes the nudge.
- CarCollision lets it skip ghost cars in the gap check.
- MeshPrimitive gives each car's position and sets a new car's facing.
- AutoRunnerDriver gives each new car its lane and lets go of a wreck.




































