Skip to content

feat(hedgehog-mode): knockable block piles for Max to throw#2312

Draft
fercgomes wants to merge 5 commits into
mainfrom
posthog-code/hedgehog-mode-pile-spawner
Draft

feat(hedgehog-mode): knockable block piles for Max to throw#2312
fercgomes wants to merge 5 commits into
mainfrom
posthog-code/hedgehog-mode-pile-spawner

Conversation

@fercgomes
Copy link
Copy Markdown
Contributor

Summary

While hedgehog mode is on, stacks of 3–5 colored blocks now spawn near the ground (capped at 3 piles, refreshed every ~15s) so you can fling Max at them and knock them over Angry-Birds-style. Blocks use the existing matter-js world inside @posthog/hedgehog-mode and are rendered with Pixi Graphics on the same canvas — no new sprite assets, no fork of the upstream package.

  • New PileSpawner (apps/code/src/renderer/components/hedgehog-mode/PileSpawner.ts) owns the spawn timer, per-block GameElement, and lifecycle.
  • Blocks use category: PROJECTILE (0x0004), mask: 0xFFFF so Max and the ground collide with them out of the box; the hedgehog's existing mask already includes PROJECTILE.
  • Cleanup is automatic: blocks that fall off-screen or stay at rest for ~8s are removed, freeing slots for the next pile.
  • HedgehogMode.tsx instantiates the spawner once the game is rendered and tears it down before destroying the game on unmount.
  • pixi.js and matter-js were already transitive deps via @posthog/hedgehog-mode; promoted to direct deps and added @types/matter-js so the imports type-check.

Test plan

  • pnpm dev (run code app)
  • Toggle hedgehog mode on in Settings → General → a pile appears within ~1s
  • Grab Max, fling him into the pile — blocks tumble
  • Blocks that fly off-screen disappear; blocks at rest disappear after ~8s
  • New pile appears every ~15s, capped at 3 simultaneous piles
  • Toggle hedgehog mode off → all blocks cleaned up, no lingering timers/console errors
  • Resize the window mid-game — already-spawned blocks behave sensibly; new piles spawn at the new dimensions

fercgomes added 5 commits May 22, 2026 17:47
Adds Angry-Birds-style block piles to hedgehog mode. While the mode
is on, stacks of 3-5 colored blocks spawn near the ground (capped at
3 piles, refreshed every ~15s) so the user has something to fling
Max at. Blocks use the existing matter-js world inside
`@posthog/hedgehog-mode` and are rendered with Pixi `Graphics` on
the same canvas; cleanup happens when a block leaves the viewport
or stays at rest for ~8s.

- New `PileSpawner` owns the timer, the per-block `GameElement`,
  and lifecycle. Blocks use `category: PROJECTILE, mask: 0xFFFF`
  so Max (and the ground) collide with them out of the box.
- `HedgehogMode.tsx` instantiates the spawner once the game is
  rendered and tears it down before destroying the game.
- `pixi.js` and `matter-js` were already transitive deps via
  `@posthog/hedgehog-mode`; promoted to direct deps and added
  `@types/matter-js` so the imports type-check.

Generated-By: PostHog Code
Task-Id: f26ab8fc-0c36-4bf9-959b-891c6eda2e7b
Two fixes:

1. Stop leaking `window.pointerdown` listeners in dev. The upstream
   `@posthog/hedgehog-mode` `destroy()` doesn't remove its
   capture-phase window listeners, so every StrictMode simulated
   unmount and every Vite HMR cycle stacked another stale listener
   on top. The old listener fired first on each click, hit-tested
   against the destroyed game's hedgehog, and called
   `preventDefault/stopPropagation` — so the live game never saw
   the click and you couldn't grab Max in dev.

   Fix locally by only calling `destroy()` when `hedgehogMode`
   actually transitions to false, instead of on every effect
   cleanup. Production was unaffected (no HMR, no StrictMode
   double-mount), which matches the reported symptom.

2. Bump pile height range from 3-5 to 6-10 blocks so towers feel
   like real obstacles instead of speed bumps.

Generated-By: PostHog Code
Task-Id: f26ab8fc-0c36-4bf9-959b-891c6eda2e7b
Three small changes to figure out why piles aren't rendering in dev:

- Drop the Pixi v8 `.rect().fill().stroke()` chain in favor of
  separate calls. Stroke was decorative and the chain is the
  likeliest silent-failure point.
- Bump pile-block `zIndex` to 1000 so blocks render above the
  hedgehog if Pixi z-order is the issue.
- Add a scoped `pile-spawner` logger that logs pile id, x,
  height, ground top, stage child count, and matter-js world body
  count on every spawn.
- Expose the game on `window.__hedgehogGame` so we can inspect
  `app.stage.children`, `engine.world.bodies`, and `elements`
  from the renderer devtools console.

Generated-By: PostHog Code
Task-Id: f26ab8fc-0c36-4bf9-959b-891c6eda2e7b
The PileSpawner was being skipped whenever the effect re-ran while
`game.render(container)` was still awaiting. The deps array
includes `user?.hedgehog_config`, which React Query returns as a
fresh object on every refetch — that retriggers the effect, fires
the cleanup (`cancelled = true`), and the re-run bails on
`gameRef.current` already being set. When the original `render`
finally resolves, `!cancelled` is false, so the spawner is never
constructed. The hedgehog itself survives because it's added
inside `render`, before the await resolves.

`cancelled` tracks effect lifecycle, which is the wrong signal
here. Gate on game identity instead: if `gameRef.current` still
points to the game we just rendered, we're still the active
owner and the spawner should be set up.

Generated-By: PostHog Code
Task-Id: f26ab8fc-0c36-4bf9-959b-891c6eda2e7b
… chunk

CI integration-test was OOMing during electron-forge's renderer
Vite build (4GB heap exhausted). The regression was the static
import of `pixi.js` in `PileSpawner.ts`, combined with
`PileSpawner` being statically imported by `HedgehogMode.tsx` —
that pulled pixi.js (multi-MB WebGL bundle) into the main
renderer chunk. Before this PR, pixi was only reachable through
the dynamic `import("@posthog/hedgehog-mode")` and lived in a
lazy chunk.

Switch `PileSpawner` to a type-only import at the top of
`HedgehogMode.tsx`, and load the actual module via `import()`
alongside `@posthog/hedgehog-mode` inside the same effect. Both
now share the lazy boundary, and pixi.js stays out of the main
bundle.

Generated-By: PostHog Code
Task-Id: f26ab8fc-0c36-4bf9-959b-891c6eda2e7b
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.

1 participant