A Mario-style platformer built to show off a modern web-game stack:
Vite + TypeScript (strict) + PixiJS v8 (WebGPU-first) + miniplex ECS + zustand + React HUD overlay
All art is generated procedurally at boot (pixel grids + canvas) β zero binary assets in the repo.
npm install
npm run dev # http://localhost:5173
npm run build # typecheck + production build
node scripts/smoke-test.mjs # headless boot & gameplay test (needs Chromium)src/
βββ main.tsx # boots Pixi (WebGPU β WebGL fallback) + React HUD
βββ state/store.ts # zustand: score/lives/level/screen β meta-state only
βββ ui/ # React: HUD, pause menu, level select, debug panel,
β # touch controls. Subscribes to zustand. Zero React
β # in the game loop.
βββ game/
βββ Game.ts # fixed-timestep loop (1/60s accumulator) decoupled
β # from render; interpolates positions between ticks;
β # hit-stop; level lifecycle; post-processing
βββ context.ts # Ctx β the service bag every system receives
βββ ecs/ # miniplex world; components: Position, Velocity,
β # Collider(AABB), Sprite, PlayerControl, EnemyAI,
β # Health, Collectible, Platform, Pipe, Checkpoint, Light
βββ systems/ # input, physics, collision (in physics.ts), platforms,
β # ai (FSM per enemy type + 3-phase boss), interactions,
β # camera, animation, render, debug
βββ level/ # LDtk JSON schema types + loader + entity spawner;
β βββ data/*.json # 3 sample levels as LDtk-schema JSON (IntGrid
β # collision layer + Entities layer)
βββ fx/ # GPU particles (ParticleContainer, 500+ poofs),
β # custom GLSL+WGSL shaders (vignette, palette swap),
β # erase-blend 2D lighting, tweens
βββ gfx/ # procedural pixel-art textures + parallax builders
βββ input/ # keyboard + Gamepad API + touch, edge-buffered
- Coyote time 100 ms, jump buffer 120 ms, variable jump height (release to cut), asymmetric gravity (1.6Γ falling)
- Corner correction β clipping a ceiling edge by β€5 px nudges you around it
- Squash & stretch on jump/land/stomp, hit-stop 60 ms on stomps (more on boss hits)
- Camera: deadzone box, velocity lookahead, trauma-based screen shake (shake = traumaΒ², decays over time)
- WebGPU-first (
preference: "webgpu"), automatic WebGL fallback β custom filters ship both WGSL and GLSL - Bloom (pixi-filters AdvancedBloom) on collectibles + sparkles, custom vignette, optional CRT shader (toggle in pause menu)
- Palette-swap fragment shader recolors enemy tier variants (tier 2 = blue elite, tier 3 = red brute) from one texture
- GPU particles via Pixi v8
ParticleContainerβ dust, sparkles, coin bursts, 500+ particle death poofs, boss shockwaves - 4-layer parallax (sky 0 / far 0.2 / near 0.5 / world 1.0) with per-theme backdrops
- Dynamic 2D lighting in the cave: screen darkness rendered through an
AlphaFilterwitherase-blend light sprites punching soft holes, plus additive glow sprites
- 3 levels (Green Hills β Crystal Cave β Castle Keep) authored as LDtk-schema JSON (
src/game/level/data/); regenerate withnode scripts/generate-levels.mjs - Enemies: walker, flyer (sine hover), shell-kicker (stomp β shell β kick, shells mow down enemies) β one FSM per type
- Question blocks (coins + mushroom power-up; big players smash bricks), pipes as level warps (hold βΌ), moving platforms that carry you, checkpoints
- Boss with a 3-phase FSM: jump-shockwaves β charges + minion summons β enraged everything, stomp it 9 times
| Action | Keyboard | Gamepad | Touch |
|---|---|---|---|
| Move | β/β or A/D | left stick / d-pad | β βΆ |
| Jump (hold = higher) | Space / Z / K | A | A |
| Run | Shift / X / J | B / X | B |
| Enter pipe | β / S | d-pad down | βΌ |
| Pause | Esc / P | Start | β |
| Debug overlay (hitboxes, FPS, entity count) | F1 | β | β |