A game gets a walkable character that can get into a car and drive it
in three lines and never mentions a camera:
let id = Character::new(...);
player_rigs.insert(PlayerId(0), PlayerRig::new(id));
Everything crafted is engine-side with defaults nothing has to specify:
coyote time, jump buffering, variable jump height, asymmetric accel and
decel, air control, landing recovery, a boom that snaps in and eases
out, look-ahead, speed pullback, and delayed recentring.
Script surface grows by three verbs (115 -> 118): game.player_character,
game.interactable, game.interact_prompt. Cars and doors-with-interiors
are derived affordances, so a generated game with a car and a house
declares nothing; game.interactable is only for chests and switches.
The prompt and the press share one search so they cannot disagree, and
it picks the nearest candidate in front rather than merely the nearest.
Four bugs found by looking at what ran, not by reading:
- Arcade never polled the gamepad at all. No game_input_states() call
existed anywhere in the app, so the pad's state never entered the
process and every binding downstream read a struct nobody filled.
- LT drove both brake and negative throttle, and brake force opposes
reverse motion. Measured: clean reverse covers 13.8m in 2s against
22.25m forward; with brake held, 0.63m. car.rs is unchanged -- a foot
on the brake winning is a car behaving like a car.
- Mount cleared `hidden`, which means "solid to everything, drawn by
nothing" -- so boarding left the driver as an invisible collider at
the kerb. Now uses attached_to, the sim's seat pin, and saves and
restores hidden rather than asserting a value.
- GameWorld::new() never set gravity; only reset_content() did. All 70
new() call sites floated, and four files had each independently grown
their own `world.gravity = 30.0`. A floating character never reports
on_floor, so the controller silently refused to jump.
The in-vehicle boom goes 9.0 -> 13.0. The boom is a time budget, not a
length: at the car's top speed 9m was 0.37s of road ahead, too little to
plan a turn. The test states it against CarConfig::top_speed, so raising
the car's speed fails the test instead of quietly making the view tight
again. The pivot deliberately does not rise with it -- eye.y is
pivot.y + sin(pitch)*boom, so the longer boom already buys the height,
and driving should sit lower and more planted than walking.
Player rigs now survive Blocks::clear(): where you are sitting and where
you are looking are the player's state, not the game's content, so a
script edit no longer ejects the driver mid-corner.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>