Skip to content

Instantly share code, notes, and snippets.

@supertommy
Created April 1, 2026 03:11
Show Gist options
  • Select an option

  • Save supertommy/2c2c622feeeab19188f8a21b798d994f to your computer and use it in GitHub Desktop.

Select an option

Save supertommy/2c2c622feeeab19188f8a21b798d994f to your computer and use it in GitHub Desktop.
Super Mario Galaxy Movie Game CLAUDE.MD

Super Mario Movie 2

Last Updated: 2026-03-30 (Yoshi shield HP + dismount via Shift/B button) Status: Early development — Galaxy-style gravity, character controller, Goomba enemies with AI, Yoshi NPC (shield HP, mount/dismount), Bowser Jr. boss (defeat sequence + victory flow), placement editor, coin system, logo letter platforms, visual polish (hemisphere lighting, env map, Fresnel rim), mobile performance (distance-based simulation culling, Rapier SIMD), AI-assisted level construction tools, Galaxy-style spin attack (combat, particles, hit-stop, rewards). ECS architecture review remediation complete (18 tasks).

A 3D browser game built with Three.js, Pixi.js (shared WebGL context), and Rapier physics. TypeScript monorepo with npm workspaces (apps/*, packages/*).

Reference Projects

  • Original 2817 monorepo: /Users/tommy/Documents/two8one7/2817/ — source of all @2817/* packages; port from here when bringing in new packages
  • Super Mario Movie 1 (SMM1): /Users/tommy/Documents/two8one7/2817/apps/supermariomovie/ — the previous Mario game; reference for game mechanics, level design patterns, and asset conventions
  • Star Spangled Solitaire: /Users/tommy/Documents/games/star-spangled-solitaire/star-spangled-solitaire/ — recently shipped game; reference for production patterns, game loop structure, and release polish
  • Library optimization audit: plans/library-optimization-audit.md — performance fixes to apply when porting packages

Stack

  • Rendering: Three.js (3D scene) + Pixi.js (2D UI overlays) sharing one WebGL2 context
  • Physics: Rapier3D (WASM) — deterministic, fixed-timestep
  • Game loop: @2817/gameloop — fixed update (60Hz) with variable render and interpolation alpha
  • ECS: @2817/ecs — custom SoA (Structure of Arrays) entity-component-system
  • FSM: @2817/fsm — finite state machine with function states and transition queuing
  • Process: @2817/process — composable sequential/parallel process orchestration. Read the process-system-design skill before writing any process code.
  • Audio: @2817/audio — Web Audio API wrapper via @2817/resources asset pipeline
  • Frontend: Astro (static shell) hosting the canvas-based game

Skills to Read Before Writing Code

  • ecs-design-patterns — Universal ECS architecture principles. Read before writing any ECS code.
  • ecs-2817-rapier-threejs — Practical ECS patterns for this stack. Includes /ecs-review checklist.
  • process-system-design — Authoritative patterns for @2817/process. A process IS a temporary update loop. ExecuteAction is instant only. Never pass the game loop around.
  • level-construction-tool — Read before any level design work. CLI tool + dev server auto-save.
  • GLB Model Inspection — Never guess model properties. Run: node ~/.claude/skills/glb-inspection/glb-inspection/scripts/inspect-glb.mjs <path>. Every model-derived constant must cite inspection output.

ECS Architecture

SoA storage: component[field][worldIndex][entityIndex] — three levels of array indirection. Direct array access is the only API (no getter/setter wrappers):

const x = Position.x[worldIdx][entity.index]
Position.x[worldIdx][entity.index] = 10

For queries, IDs, and component events API details, see the ecs-2817-rapier-threejs skill.

System Principles

  • Systems are stateless functions — called each frame, return (dt: number) => void
  • Entity state lives in components — cooldowns, timers, per-entity flags in SoA fields
  • System-internal state lives inside the system — hysteresis maps, cached lookups, elapsed timers
  • Shared dependencies are injected — only what is genuinely shared across multiple systems
  • Config components over imperative setup — data describes intent, systems execute on query.added()

Performance Principles

These apply to all game-runtime code (anything in a game loop, system, or hot path):

  • Zero allocations in hot loops — no object literals, array spreads, or closures per frame
  • Direct array access over method callsarr[i] not arr.get(i)
  • Avoid Map.get() in per-frame code — cache lookups in local variables
  • for...of over arrays is fine — V8 optimizes this; avoid forEach callbacks
  • Mutable singletons for per-frame data — callers must not hold refs across frames
  • Fixed timestep with accumulator capMAX_DT = 0.25s prevents spiral-of-death

Game Loop & Render Pipeline

Fixed 60Hz update / variable render with interpolation alpha. Three.js renders first, Pixi on top (shared WebGL2 context). See docs/render-pipeline.md for pipeline optimizations, gravity shadow multi-pass, bloom, and occlusion silhouette.

Composition Root

createGame() in index.ts delegates to focused setup files. Key principle: composition root stays under ~600 lines. See docs/composition-root.md for file table, bridge systems, late-bound systems, and loop forwarding.

FSM Conventions

Per-entity (ECS): Enum-field pattern — numeric state field on component, SoA data fields, pure functions in system's if/else chain. Reference: DonutBlock (6 states), LiftPlatform (2 states).

Non-entity (UI, game flow): @2817/fsm library — createMachine<StateName, TransitionName>() with function states and queued transitions.

Feature Documentation

Detailed architecture for each feature lives in docs/:

Feature Doc
Camera system docs/camera-system.md
Physics & gravity docs/physics-and-gravity.md
Character controller & ECS systems docs/ecs-systems.md
Spin attack docs/spin-attack.md
Enemy AI docs/enemy-ai.md
Coin system docs/coin-system.md
Brick system docs/adding-placement-objects.md
Debug editor docs/debug-editor.md
Placement editor docs/placement-editor.md
Logo letter platforms docs/logo-letter-platforms.md
Render pipeline docs/render-pipeline.md
Composition root docs/composition-root.md
Scene manager docs/scene-manager.md

Key Feature Notes

Rapier SIMD: @dimforge/rapier3d-simd via Vite alias in astro.config.mjs — all @dimforge/rapier3d imports resolve to SIMD at build time. 2-5x physics speedup, same API.

NPC System (Yoshi): Own collision group (GROUP_NPC = 0x0008). Placed via npc-spawn editor type. Eye material needs runtime polygon offset fix. Shield HP: Yoshi has ShieldHealth component (default 3 HP). When mounted, damage depletes shield before Mario's HP (overflow carries to Mario). Shield break triggers forced dismount + poof via shieldBreakResponseSystem. Shield HP persists on Yoshi across voluntary dismount/remount. Dismount triggered by Shift key (B button on gamepad/virtual controls). UI: inner ring in HP meter shows shield HP; center number shows total (Mario + shield). Mushroom healing priority: Mario HP first, then shield. Shared dismount helpers in dismount-helpers.ts.

Simulation Zone: Distance-based culling using the camera's focus point (camera pos + 8u along look direction), not player position — prevents entity pop-in during warp teleports. All enemies/NPCs get SimulationZone + Orientation except Bowser Jr (boss visibility managed by intro sequence). Never add to player or static objects. agentFacingSystem writes Orientation ECS fields (not display.quaternion) and snaps orientation on zone wake (prevZone >= 2).

Level Construction: CLI tool (tools/level.mjs) + dev server auto-save (vite-editor-save-plugin.mjs). Both read/write the same JSON files. Reload browser to see CLI changes.

Monorepo

  • Path aliases in root tsconfig.json (e.g. @2817/ecspackages/ecs/src/index.ts)
  • Each package with tests needs its own vitest.config.ts with resolve aliases matching root tsconfig
  • npm install --legacy-peer-deps (koota benchmark adapter has React peer dep)
  • Debug features lazy-load via dynamic import() behind ?debug URL param

Constraints

Hard-won lessons from bugs and edge cases. Full categorized list: docs/constraints.md. The most critical universal constraints:

  • Never guess model dimensions — always run inspect-glb.mjs first
  • Never wrap ECS array access in getter/setter functions — raw access is intentional
  • ECS SoA arrays return T | undefined — use as number / as boolean to assert
  • Files in packages/ use tabs — Edit tool struggles with tab matching; prefer Write for full-file rewrites
  • Never use Group.clone() on GLTF with armatures — use SkeletonUtils.clone()
  • Initialize unused InstancedMesh slots with zero-scale matrices to prevent phantom rendering
  • NEVER use pow() in onBeforeCompile shaders through UnrealBloomPass — Apple Silicon flicker
  • Don't move Fixed body colliders after characterControllerSystem — CC resolves against previous-step positions
  • Stored direction vectors must be re-projected to current tangent plane each frame
  • Systems that smoothly rotate entities (slerp) must read/write Orientation ECS component, NOT display.quaterniondisplaySyncSystem overwrites display from Orientation every frame
  • Hit-stop freezes game time (gameDt=0) but NOT render time — guard Rapier step: if (gameDt > 0) physics.step()
  • Boss entities must NOT have SimulationZone — breaks scripted intro sequences
  • Shadow system must read GravityBody.shadowUpX/Y/Z (stable, quantized), NEVER upX/Y/Z (spring-damped, drifts continuously)
  • Shadow candidate collection must only run on shadow render frames — otherwise caster layer assignments desync with shadow maps
  • Shadow field updates (shadowFieldId) must defer until gravity spring converges — prevents oscillation during field transitions
  • InstancedMesh shadow pass assignment must iterate ALL instances (no sample cap) — instances span multiple gravity fields. Per-instance instanceShadowPass attribute filters each instance to its correct pass in the vertex shader; object-level layer mask is a coarse pre-filter only
  • Shadow frustum size is derived from the camera's FOV/aspect/distance each frame — not a fixed constant. Ensures shadow coverage matches what's visible on screen
  • When adding a new ECS component to the ecosystem, audit ALL entity creation files — easy to miss one (e.g. Bowser Jr was skipped when Orientation was added to all other enemies)
  • When removing an entity in the same frame as spawning a poof, use processManager.add(new PoofProcess(...)) directly — do NOT use the PoofRequest component, because poofSpawnSystem runs earlier in the frame (line 578) and won't see a request added after damage response (line 591). The death system follows this same pattern.
  • replace_all in Edit tool replaces ALL substring occurrences including inside other words (e.g. replacing "pi" also hits "rapier") — use Write for full-file rewrites when renaming short identifiers

Plan Tracking

Active plans live in plans/. When working from a plan:

  • Check the plan file for current status before starting work
  • After completing any plan item, update the plan file — mark sections/rows as done with commit hashes
  • If a plan step reveals new constraints, add them to the Constraints section above

Active plans:

  • plans/library-optimization-audit.md — porting and optimizing @2817/* packages
  • plans/ecs-refactor-phases.mdECS refactor (7 phases). Check per-phase files for task-level status.

Completed plans:

  • plans/ecs-review-remediation.md — 18 tasks, all complete. Key changes: utility extraction, ShellManager, generic enemy scene factory, AIBehavior decomposition.

Continuous Improvement

Update this file when:

  • You make a mistake (add to Constraints or docs/constraints.md)
  • You discover a pattern worth documenting
  • Phase status changes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment