Skip to content

Add slam mechanic to break agents and improve input handling - #3

Merged
h1ddenpr0cess20 merged 1 commit into
mainfrom
claude/compassionate-albattani-hmhsok
Sep 14, 2026
Merged

h1ddenpr0cess20 merged 1 commit into
mainfrom
claude/compassionate-albattani-hmhsok

Conversation

@h1ddenpr0cess20

Copy link
Copy Markdown
Owner

Summary

This PR implements a slam mechanic that allows the player to break agents by landing on them while diving, adds proper input buffering to prevent dropped presses on high-refresh displays, and refactors camera positioning into a testable module.

Key Changes

Slam Mechanic

  • Added SMASH constant defining the shockwave area (2.2m deep, 1.2m wide, 1m tall) that breaks agents on landing
  • Implemented shockwave() function to detect and shatter hazards in the slam radius
  • Agents broken by slam award 60 points and emit a 'smash' event
  • Slam only affects agents in the player's current lane, requiring precise aim
  • Added 'smash' sound effect with distinctive audio signature

Input Buffering

  • Changed lane input from boolean flags to numeric counters, allowing multiple presses in one frame to accumulate (e.g., double-tap = 2 lanes)
  • Implemented hold() function to accumulate input intent across frames until a physics tick consumes it
  • Prevents dropped inputs on displays faster than 120Hz (e.g., 144Hz displays were losing ~1 in 6 presses)
  • Jump and dive remain boolean flags (pressing twice = one action)
  • Added protection against accidental interactions with page UI elements (buttons, links, inputs)

Camera Refactoring

  • Extracted camera positioning logic into new src/render/rig.js module with seat() and focus() functions
  • Defined WIDE and TALL rig configurations for different aspect ratios
  • Added comprehensive test suite (test/rig.test.js) to verify camera framing and lane ordering
  • Camera now testable without WebGL context

Game State Management

  • Added smashed counter to track broken agents
  • Implemented finish() function as the single exit point for runs
  • Added 'respawn' event when egg is placed back after falling into void
  • Added snap() method to cut camera to egg position on respawn (prevents blind running)

Player Movement

  • Updated lane movement to apply accumulated presses: step = right_presses - left_presses
  • Allows crossing multiple lanes in a single tick if multiple presses arrived

Tuning & Balance

  • Fixed laneX() function to correctly map lanes to screen positions (descending to account for mirrored camera view)
  • Added laneBounds() helper for slab collision detection
  • Added SCORE.perAgent = 60 for slam kills
  • Updated HUD to show agent count only when > 0

Bug Fixes

  • Fixed input handling to not consume presses meant for page controls
  • Prevented swipes that end on buttons from being interpreted as jumps
  • Ensured run start doesn't carry over input from previous run
  • Fixed rain particle timing to not drop remainder (was losing 20% speed at 60fps)

Implementation Details

  • Input intent is now held in game state until a physics tick spends it, rather than being passed directly to the first tick
  • Slam detection uses axis-aligned bounding box overlap in the shockwave volume
  • Lane presses accumulate across frames, enabling natural multi-lane movements
  • Camera positioning separated from rendering to enable unit testing of framing logic

https://claude.ai/code/session_01S79HqfuYJ6UNuQp6LeLJ1N

The controls were mirrored. The egg runs towards +z and the chase camera
sits behind it looking the same way, which mirrors the picture: world +x
draws on the *left* of the screen. `laneX` ascended, so lane 0 sat on the
right, and `left` moved the egg right. It now descends, and slab extents
go through `laneBounds`, which sorts the two edges that swap with it.

Nothing in the core could see that mistake, so the camera framing moved
out to `render/rig.js` as plain arithmetic, and `test/rig.test.js`
projects a lane through the real rig and checks which half of the frame
it lands on. It fails against the old mapping.

Four more bugs behind it:

- A frame shorter than the 1/120 physics step runs no tick, and intent
  was handed to the first tick or dropped — so a 144Hz display threw
  away roughly one press in six. The game now holds intent until a tick
  spends it, and lane presses accumulate, so a double tap inside one
  frame crosses two lanes.
- Falling with nowhere left to land set `state` to 'over' without
  emitting it: no overlay, no best score, an egg standing still. There
  is one `finish()` now and both routes go through it.
- Tapping `audio on` also jumped the egg. `click` can be stopped from
  bubbling; the pointer events under it are separate and reached the
  window anyway. Pointer events that land on the page's own controls are
  now left alone, and `dispose()` removes the pointercancel listener it
  had been leaking.
- The camera eased after a respawn that teleports the egg metres up the
  course, so a life was spent watching the void. It cuts instead, and
  `reset()` no longer carries the last run's ground level into the next.

The slam was the one control with no answer to anything. Come down on an
agent with it on and the landing goes through the deck and breaks it —
its own lane, a couple of metres of it, sixty points. It has to be
aimed: a ~300ms window at running speed, and only from the air.

Also: the rain lost the remainder of every tick it spent, costing it a
fifth of its speed at 60fps, and the HUD rewrote four nodes sixty times
a second to say the same thing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S79HqfuYJ6UNuQp6LeLJ1N
@h1ddenpr0cess20
h1ddenpr0cess20 merged commit cf4883e into main Sep 14, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants