Problem: Developing high-performance browser games using traditional rendering loops often introduces heavy CPU overhead, layout thrashing, and frame-time spikes that drop frame rates below 60 FPS.
Solution: A data-oriented Entity-Component-System (ECS) engine in pure JavaScript that recycles DOM nodes and batches reads/writes via a dedicated frame pipeline—rendering 60 FPS without canvas or external frameworks.
A pure-JS Entity-Component-System game engine demonstrating data-oriented browser architecture. Built as an educational reference for ECS patterns — no canvas, no frameworks, 60 FPS DOM rendering with strict performance budgets.
Pac-Man × Bomberman — Eat every pellet. Bomb every wall. Survive every ghost. Built purely with Entity-Component-System (ECS) architecture.
The repository includes the current runtime/build toolchain, so the commands below are ready to use after installing dependencies.
- Node.js ≥ 24.0.0
- npm ≥ 10.x
# Clone the repository
git clone https://github.com/ertval/make-your-game.git
cd make-your-game
# Install dependencies
npm cinpm run devOpen http://localhost:5173 in your browser. Vite serves the app with hot-reload.
- Getting Started
- Overview
- Gameplay
- Architecture Overview
- Directory Structure
- Deployment
- Frame Pipeline
- Rendering & Performance
- Scripts & Commands
- Development Workflow
- Documentation Flow
- Testing & Verification
- Tech Stack & Constraints
- Contributing
- Related
- License
Ms. Ghostman is a single-player arcade game with a Pac-Man-first loop and Bomberman-style bomb mechanics:
- Pac-Man: Navigate a grid-based maze, eat all pellets to complete a level.
- Bomberman: Drop bombs to destroy walls, create paths, and eliminate enemies.
The result is a strategic game where every move matters. Block off ghost routes, set bomb traps, chain explosions, and race against the clock.
ECS means Entity-Component-System:
- Entity: a simple numeric ID (for example, player, ghost, bomb).
- Component: pure data attached to entities (position, velocity, bomb fuse, score tags).
- System: logic that reads/writes component data in deterministic order (movement, AI, collisions, rendering).
Why this project uses ECS: it keeps gameplay logic modular, deterministic, and fast enough for stable 60 FPS DOM rendering.
- 🕹️ Hold-to-move controls — smooth, responsive keyboard input processed via input systems.
- 💣 Bomb mechanics — 3-second fuse, cross-shaped explosions, chain reactions.
- 👻 4 unique ghost personalities — from aggressive to unpredictable.
- ⚡ Power-ups — increased bomb range, extra bombs, speed boosts, ghost-stunning pellets.
- ⏱️ Countdown timer — beat the clock for bonus points.
- 🏆 Scoring & combos — chain-kill ghosts for exponential bonuses.
- ⏸️ Pause menu — continue or restart without losing progress.
- 📊 3 difficulty levels — increasing maze density, ghost count, and speed.
- 🎨 60 FPS DOM rendering — no canvas, pure CSS Grid + transform animations via a dedicated Render Batcher.
| Key | Action |
|---|---|
↑ ↓ ← → |
Move Ms. Ghostman (hold for continuous movement) |
Space |
Drop a bomb |
Escape / P |
Pause / Resume |
Enter |
Confirm menu selections |
- Eat all pellets on the map to clear the level.
- Drop bombs to destroy walls blocking your path.
- Avoid or eliminate ghosts — they kill on contact.
- Don't get caught in your own explosions!
- Beat the countdown — time runs out = game over.
- Clear all 3 levels to earn VICTORY.
| Action | Points |
|---|---|
| Eat pellet | 10 |
| Eat Power Pellet | 50 |
| Kill ghost (bomb) | 200 |
| Kill stunned ghost | 400 |
| Combo kills | 200 × 2^(n-1) per ghost |
| Collect power-up | 100 |
| Level complete | 1000 + time bonus |
This project is built using a strict Entity-Component-System (ECS) architecture to ensure high performance, decoupling, and strict determinism.
graph TB
subgraph "Imperative Shell / Adapters"
MAIN["main.ecs.js (entry)"]
RENDERER["Renderer Adapter (DOM)"]
INPUT["Input Adapter"]
AUDIO["Audio Adapter"]
end
subgraph "Core ECS Simulation (Pure Data & Behavior)"
WORLD["World (Entities, Scheduling)"]
subgraph "Systems"
SYS_INPUT["Input System"]
SYS_MOVE["Movement & Collision Systems"]
SYS_BOMB["Bomb & Explosion Systems"]
SYS_AI["Ghost AI System"]
SYS_RENDER_C["Render Collect System"]
SYS_RENDER_D["Render DOM System"]
end
subgraph "Components"
COMP_POS["Position, Velocity"]
COMP_ACT["Player, Ghost, Bomb"]
COMP_REN["Renderable, VisualState"]
end
end
MAIN --> WORLD
WORLD --> SYS_INPUT
WORLD --> SYS_AI
WORLD --> SYS_MOVE
WORLD --> SYS_BOMB
WORLD --> SYS_RENDER_C
SYS_RENDER_C --> SYS_RENDER_D
SYS_INPUT -. reads .-> INPUT
SYS_RENDER_D -. writes .-> RENDERER
SYS_INPUT --> COMP_POS
SYS_MOVE --> COMP_POS
SYS_AI --> COMP_ACT
| Concept | Definition |
|---|---|
| Entity | An opaque numeric ID. Contains no behavior or methods. |
| Component | Data-only records (POJOs) attached to entities. No DOM references. |
| System | Dedicated functions that query components and mutate state in a fixed order. |
| World | Owners of entities and systems. Orchestrates frame execution and resource access. |
| Query | High-performance filters that retrieve entities matching specific component masks. |
Note: The tree below reflects the current repository layout plus the planned files that are still part of the docs-first roadmap. Entries marked as planned are not checked in yet.
make-your-game/
├── .github/ # 🔧 GitHub Actions and repository workflow files
│ └── workflows/
│ ├── deploy.yml # GitHub Pages deployment workflow
│ └── policy-gate.yml # PR merge gate workflow
├── assets/ # 🎨 Static and generated asset roots
│ ├── generated/ # Build-time generated sprite/UI/audio outputs
│ │ ├── music/
│ │ ├── sfx/
│ │ ├── sprites/
│ │ └── ui/
│ ├── manifests/ # 📄 Runtime asset manifest contracts
│ │ ├── audio-manifest.json
│ │ └── visual-manifest.json
│ └── source/ # ✏️ Hand-authored source art and audio
│ ├── audio/
│ └── visual/
├── biome.json # 🧹 Biome check/fix configuration
├── docs/ # 📚 Documentation and implementation plans
│ ├── README.md # Documentation map and workflow guide
│ ├── audit.md # Audit checklist for grading
│ ├── deployment/ # 🚀 Deployment guidance
│ │ └── github-pages.md # GitHub Pages deployment guide
│ ├── game-description.md # Full game rules & mechanics
│ ├── implementation/ # 🧭 Canonical implementation planning and tracking docs
│ │ ├── agentic-workflow-guide.md # Team workflow and PR process
│ │ ├── assets-pipeline.md # Visual/audio authoring and validation workflow
│ │ ├── audit-traceability-matrix.md # Canonical requirement/audit/ticket/test coverage mapping and status
│ │ ├── implementation-plan.md # ECS implementation milestones and integration timeline
│ │ ├── pr-template.md # PR contract and checklist template
│ │ ├── ticket-tracker.md # Live ticket progress tracker
│ │ ├── track-a.md # Track A ticket definitions and verification gates
│ │ ├── track-b.md # Track B ticket definitions and verification gates
│ │ ├── track-c.md # Track C ticket definitions and verification gates
│ │ └── track-d.md # Track D ticket definitions and verification gates
│ ├── requirements.md # Original project requirements
│ └── schemas/ # 📐 JSON Schema 2020-12 contracts
│ ├── audio-manifest.schema.json
│ └── visual-manifest.schema.json
├── index.html # 🏠 Single-page entry point
├── package-lock.json # 🔒 Locked npm dependency graph
├── package.json # 📦 Core config, dependencies, and project quality scripts
├── sbom.json # 🔒 SPDX SBOM for dependency auditing (managed and enforced by CI)
├── src/ # 🧠 Runtime source code
│ ├── adapters/ # 🔌 Imperative boundaries for DOM and IO
│ ├── adapters/dom/ # DOM adapter implementations (present)
│ ├── adapters/io/ # IO adapter implementations (present)
│ ├── debug/ # 🐞 Debug utilities and replay helpers
│ ├── ecs/ # ⚙️ ECS core
│ │ ├── components/ # Pure state data definitions (present)
│ │ ├── resources/ # Shared data (Clock, RNG, Maps)
│ │ ├── systems/ # Domain logic systems (partial/iterative)
│ │ └── world/ # World, entity store, queries
│ ├── game/ # 🎮 Game bootstrap and flow orchestration
│ ├── main.ecs.js # App entry — bootstraps the ECS World
│ └── shared/ # 🛠️ Cross-cutting utilities
├── styles/ # 💅 Global CSS
│ ├── base.css # Design tokens, reset, and layout base
│ ├── grid.css # Grid layout stylesheet
│ └── animations.css # Animation stylesheet
├── tests/ # 🧪 Automated test suites
│ ├── README.md # Coverage policy and completion rules
│ ├── e2e/ # Browser-level validation suites
│ │ └── audit/
│ │ ├── audit-question-map.js
│ │ └── audit.e2e.test.js
│ ├── integration/ # Cross-system integration tests
│ └── unit/ # Pure unit tests
├── vite.config.js # ⚡ Vite dev server config
├── vitest.config.js # ✅ Vitest config
└── playwright.config.js # 🎭 Playwright browser test config
The project is configured for automated continuous deployment (CD) via GitHub Actions. Any push to the main branch builds the production assets and deploys them directly to GitHub Pages.
For detailed information on the pipeline, Vite base path configurations, and dynamic asset path handling, see the GitHub Pages Deployment Guide.
- rAF Start:
requestAnimationFrameinitiates the frame. - Input Sync: Input adapter snapshots key states into world resources.
- Simulation (Fixed-Step):
- Input systems apply intents to components.
- Movement and AI systems update positions and states.
- Bomb and explosion systems process fuses and chain reactions.
- Collision and scoring systems resolve overlaps and points.
- Timer systems handle level countdowns and transitions.
- Visual Pre-processing: Render collect system computes transform intents based on interpolation.
- DOM Commit: Render DOM system applies a single batched style-write phase.
- HUD Update: HUD adapter updates text metrics using
textContentonly.
requestAnimationFramecontinues running to keep menus responsive.- Simulation update steps are skipped when the pause flag is active.
- Fixed-step simulation uses an accumulator with bounded catch-up (
maxStepsPerFrame). frameTimeis clamped before accumulator integration to avoid spiral-of-death bursts after stalls.- Input is tracked as hold-state and consumed from deterministic per-step snapshots.
- On resume from pause or tab restore, timing baselines are re-synchronized before simulation continues.
- Static Grid: Persistent board elements created once at startup.
- Node Pooling: Transient entities (bombs, fire, effects) use recycled DOM nodes. Prevent GC pauses.
- Compositor Friendly: All movement updates restricted to
transformandopacity. - Minimal Jank: Strict avoidance of layout thrashing through batched read/write phases.
- The board automatically scales to fill the available viewport (centered, capped at
--board-max-scale) and re-fits on window resize, coalesced to one recalculation per animation frame. - Scaling is visual-only: it applies a uniform
transform: scale()via--fit-scale. The fixed--tile-sizegrid, integer(row, col)coordinates, and all gameplay/ECS logic are unchanged. - The scale factor is computed in JS (
fitBoardToViewportinsrc/adapters/dom/renderer-adapter.js) rather than CSS. A CSScalc()would divide a length by a length (viewport ÷ board size), which Firefox/Gecko rejects per spec while Chrome/Blink accepts — so the board would render at its small intrinsic size in Firefox. A JS-computed plain number scales identically across engines.
| Metric | Target |
|---|---|
| Frame rate | 60 FPS target with no dropped frames in audited scenarios; acceptance must match docs/audit.md criteria |
| Frame budget | p95 <= 16.7ms over representative 60s scenarios |
| DOM elements | ≤ 500 |
| Layer usage | Layers must be as few as possible but non-zero, with intentional promotion justified by audit evidence |
| GC pauses | < 1ms (object pooling, in-place component mutation) |
| JS heap | < 10MB |
| Layout thrashing | Zero (batch reads → writes via render-dom-system.js) |
Tip
The unified pre-PR validation gate is the most critical workflow checkpoint. All other checks are automatically orchestrated by it.
The npm run policy command is the main pre-PR gate. It must pass successfully before any changes can be merged into main. The gate automatically collects local/branch metadata and enforces the following checks:
- Project Quality: Runs Biome formatting and linting, Vitest tests, test coverage metrics, JSON Schema validations for game maps, and lockfile-paired SBOM generation.
- Track & Ownership Boundaries: Enforces single-track ownership. Developers may only modify files within their assigned track scopes (unless using an
<owner>/bugfix-<slug>or<owner>/integration<slug>branch prefix to bypass). - Forbidden Tech Scan: Inspects changed files to ensure they do not introduce forbidden technologies (such as
<canvas>, WebGL, WebGPU, or external UI/rendering frameworks) as prohibited by AGENTS.md. - Source Headers Check: Verifies that all newly created or modified source files start with the required top-of-file block comment documenting the file's purpose, public API, and constraints.
- Traceability Gate: Validates the consistency of the requirement-to-audit traceability matrix and that all audit questions map correctly to active tests.
- Approval Check: Verifies PR approval requirements when executing in a CI or PR-review context.
For day-to-day development, use these core commands:
npm run dev: Start the Vite development server with Hot Module Replacement (HMR) athttp://localhost:5173.npm run build: Compile and bundle the game for production, outputting static files todist/.npm run test: Run the Vitest unit and integration test suite once.npm run test:audit: Run the full Playwright and Vitest E2E audit suite to verify all acceptance criteria.
The project is split into 4 parallel workflow tracks to enable multiple developers to work simultaneously with absolute ECS decoupling:
- Track A (Dev 1 — Core Engine, CI, Schema, Testing, QA, and Evidence Wiring):
src/ecs/world/*,src/ecs/resources/*,main.ecs.js,tests/**/*,vitest.config.js,playwright.config.js - Track B (Dev 2 — Physics, Input, and Gameplay Logic & Rules):
input-system.js,player-move-system.js,ghost-ai-system.js,collision-system.js - Track C (Dev 3 — Scoring, timer, lives, pause and progression, HUD and overlays, storage flow, and audio integration):
scoring-system.js,timer-system.js,life-system.js,pause-system.js,level-progress-system.js,hud-adapter.js,screens-adapter.js,audio-adapter.js - Track D (Dev 4 — Rendering, DOM Batching, and Visual Production and Integration):
render-collect-system.js,render-dom-system.js, Adapters
Note: For the full integration milestone breakdown, check
docs/implementation/implementation-plan.md. Execution tracking: Updatedocs/implementation/ticket-tracker.mdas tickets move from[ ]->[-]->[x].
Important Instruction: Every time a phase of the plan tracker is finished, each dev should run the prompt
codebase-analysis-auditagainst the whole codebase. Merge the resulting report to main. Then there should be created a deduplicated consolidated report with all issues found. Then each can fix the ones owned by the track they follow.
Recommended reading order for new contributors:
AGENTS.md(normative constraints and quality gates)docs/requirements.md(project requirement source of truth)docs/game-description.md(gameplay behavior source of truth)docs/audit.md(acceptance/pass criteria source of truth)docs/implementation/implementation-plan.md(ECS execution plan and milestones)docs/implementation/ticket-tracker.md(live line-by-line ticket status board and canonical ticket ID index for automated policy checks)docs/implementation/agentic-workflow-guide.md(team process, PR checklist, and PR Message and Gate Workflow)docs/implementation/pr-template.md(docs entrypoint for PR contract and canonical template source)docs/implementation/track-a.md+docs/implementation/track-b.md+docs/implementation/track-c.md+docs/implementation/track-d.md(detailed track ticket definitions and verification gates)docs/implementation/audit-traceability-matrix.md(single-source requirement/audit/ticket/test coverage mapping and status)docs/implementation/assets-pipeline.md(visual/audio asset creation, optimization, and validation workflow)docs/deployment/github-pages.md(GitHub Pages publishing options and static-hosting constraints)
- Requirement intent and feature scope:
docs/requirements.md+docs/game-description.md - Final pass/fail acceptance criteria:
docs/audit.md - Implementation constraints, architecture boundaries, and audit verification categories:
AGENTS.md - Ticket execution progress and dependency/block mapping board:
docs/implementation/ticket-tracker.md - Canonical ticket ID index for branch enforcement:
docs/implementation/ticket-tracker.md - PR message and gate workflow:
docs/implementation/agentic-workflow-guide.md#12-pr-message-and-gate-workflow - Cross-document requirement/audit/ticket/test traceability and coverage status:
docs/implementation/audit-traceability-matrix.md - Visual/audio authoring and asset quality gates:
docs/implementation/assets-pipeline.md - If there is ambiguity, decisions MUST be resolved against those references.
| Layer | Strategy |
|---|---|
| Unit Tests | Pure systems tested with seeded RNG and deterministic clocks via Vitest. No DOM required. |
| Integration | World scheduling and cross-system interaction (e.g., bomb chains, pause logic, respawns). |
| Adapter Tests | Verification of input normalization and DOM write batching outputs natively. |
| Determinism | Comparison of final state hashes across identical seed/input traces. |
| Pause Invariants | While paused, simulation state is frozen and rAF-driven UI remains responsive. |
| Performance | Profile-backed checks for frame-time percentiles, long tasks, layout/paint stability, and allocation behavior. |
| Accessibility | Keyboard navigation, pause-menu focus management, and meaningful HUD status updates. |
tests/
├── README.md
├── e2e/
│ └── audit/
│ ├── audit-question-map.js
│ └── audit.e2e.test.js
├── integration/
└── unit/
- The
tests/e2e/audit/audit.e2e.test.jssuite is mapped directly todocs/audit.md. - Verification follows
AGENTS.mdtest categories:- Fully Automatable:
F-01..F-16,B-01,B-02,B-03,B-04 - Semi-Automatable:
F-17,F-18,B-05 - Manual-With-Evidence:
F-19,F-20,F-21,B-06
- Fully Automatable:
- The project is complete only when all mapped automated checks pass and required manual evidence artifacts are attached.
- See the Phase Testing & Verification Report for detailed testing instructions and exit criteria for each phase.
The stack below reflects the intended implementation target for the game. It is the roadmap for the runtime that will eventually live alongside this documentation, not a claim about the current checked-in files.
- JavaScript (ES2026): Game logic, DOM manipulation
- HTML5: Semantic page structure
- CSS3: Grid layout, animations, styling
- Vite: Dev server, bundler
- Biome: Check + fix (linting + formatting)
- Vitest: Unit testing
- SVG: Sprites and visual assets
- Web Workers (profiling-gated): Optional offload for heavy computations only when profiling shows > 4 ms/frame main-thread impact
- Trusted Types / CSP: DOM Security enforcement
- JSON Schema 2020-12: Map data validation in CI
<canvas>: Project requirement — DOM/SVG only- React / Vue / Angular: No frameworks allowed
- Game engines (Phaser, etc.): Must build custom ECS engine
- jQuery: Vanilla JS only
var: ES2026 standard —const/letonly- CommonJS (
require): ES Modules only innerHTML: XSS prevention by construction
- Read
AGENTS.mdfor ECS coding standards and constraints. - Read docs/implementation/agentic-workflow-guide.md for the 4-dev agent workflow, PR gates, and security checklist.
- Use docs/implementation/pr-template.md as the docs entrypoint for the enforced PR checklist and section contract.
- Review
docs/implementation/implementation-plan.mdand the correspondingdocs/implementation/track-*.mdfile for your specific track assignment. - Feature branches should isolate specific ECS systems or component additions.
- Core systems MUST remain pure functions handling data components; systems MUST access adapters via World resources and MUST NOT import adapters directly (including
render-dom-system.js). - Run baseline checks locally:
npm run cifor the standard local wrapper, plus any scope-specific tests (npm run test:unit,npm run test:integration,npm run test:e2e,npm run test:audit). - Run the all-in-one PR gate before opening the PR:
npm run policy. - Use
npm run policy:repoand narrow reruns (policy:quality,policy:checks:local,policy:checks,policy:forbidden,policy:header,policy:forbiddenrepo,policy:headerrepo,policy:trace,policy:approve) only as needed for troubleshooting. If a branch is intentionally docs/process-only, mark the PR body withprocessso the gate can classify it without a ticket ID. - CI MUST pass all merge gates (schema validation, testing, lockfile integrity, policy gate) before merge. When coverage/SBOM scripts are configured, those gates MUST also pass.
- The policy gate workflow enforces PR review, audit alignment, security boundaries, and dependency pairing.
- Request review at integration milestones.
- ertval.github.io — Portfolio & CV
- two-tier-safe-ai-gate — Safe AI execution model (Go + Inngest + Omnigent)
- keel-multi-agent-pipeline — Multi-agent maritime intelligence (Python + LangGraph)
- social-network — Go vertical-slices full-stack monolith (Next.js)
- make-your-game — Pure JS ECS game engine
- real-time-forum — Go + Vanilla JS real-time WebSocket SPA
- forum — Go hexagonal architecture monolith (zero-dependency)
This project is licensed under the GNU General Public License v3.0. See the LICENSE file for the full text.
This project is developed as an educational exercise for strict data-oriented ECS and high-performance DOM constraints.
Ms. Ghostman — Where Pac-Man meets Bomberman. Eat. Bomb. Survive. 🎮💣👻