Astro Drift QA Lab is a small TypeScript/Canvas arcade game and QA/SDET portfolio project.
Optimize for a small, playable, polished arcade loop with clear architecture, readable TypeScript, practical automated tests, and focused changes.
Project principle: Small game. Clear code. Strong tests. No overengineering.
This project uses Node.js 22 and npm.
See README.md for setup and primary commands. The scripts in
package.json are the executable source of truth. Use npm run check as the
canonical full quality gate; run narrower scripts such as npm test,
npm run lint, or npm run build while iterating.
Run a single unit test file:
npx vitest run tests/unit/engine.test.tsInstall the Chromium browser used by Playwright on a fresh machine:
npx playwright install chromiumDo not run npm run test:e2e and npm run check concurrently. Both manage the same Playwright preview server on localhost:4173.
If E2E appears stuck while starting its web server, diagnose it once with:
DEBUG=pw:webserver npm run test:e2eDo not manually start vite preview for normal E2E or full-check runs; Playwright starts the preview server through playwright.config.ts.
The core rule is: game logic never touches Canvas.
src/game/ owns game rules and state transitions. Its update functions return
next values without mutating caller-owned inputs. Browser, Canvas, input,
storage, time, and randomness effects stay at explicit shell boundaries:
src/main.ts, src/rendering/, src/audio/, src/input/, and
src/storage/.
See ADR-001 for the durable responsibility boundary and Engineering Principles for the functional-core / imperative-shell dependency rule.
The game state machine is:
idle -> running -> gameOver
src/main.ts holds one gameState: GameState object and tracks gameStatus
separately. This separation is intentional; see the ADR-001 addendum on state
transitions.
While the game is running, advanceRunningGame from src/game/state.ts returns
the next game state and collision information. Prefer value-returning functions
for new gameplay behavior and keep effects at the established boundaries.
Stable data-testid DOM hooks relied on by Playwright:
Game shell and regions
[data-testid="game-canvas"][data-testid="game-status-panel"][data-testid="game-stats-panel"]
State values
[data-testid="game-status"][data-testid="game-score"][data-testid="game-time"][data-testid="asteroid-count"]
Presentation state
[data-testid="radio-status"]
- Keep the game lightweight, playable, and performant.
- Improve the existing arcade loop, small visual polish, architecture, tests, CI, and documentation.
- Do not grow the project into a large game unless explicitly requested.
- Do not add React, a backend, accounts, a database, multiplayer, complex levels, shooting mechanics, power-ups, large redesigns, or broad rewrites unless explicitly requested.
- Keep the existing stack: TypeScript, Vite, Canvas, Web Audio, Vitest, Playwright, ESLint, and GitHub Actions.
- Do not switch package managers or add dependencies unless the task requires it or the trade-off is clearly justified.
- Keep game rules in
src/game/, Canvas rendering and tokens insrc/rendering/, procedural music and Web Audio lifecycle insrc/audio/, DOM styling insrc/style.css, keyboard input insrc/input/keyboard.ts, and browser persistence insrc/storage/bestScoreStorage.ts. src/game/must not depend on the audio controller or browser-audio state.src/main.tstranslates broad application transitions into audio controller operations.- Keep
src/main.tsmostly as glue. - Prefer value-returning functions for new gameplay behavior.
- Write clear, engineering-oriented TypeScript with explicit names, small functions, straightforward control flow, and readable conditionals.
- Avoid clever one-liners, premature patterns, unnecessary abstractions, and broad refactors mixed with feature work.
Before changing an invariant relied on by many call sites, audit all existing call sites first. List how each will be handled. If more than a few require special treatment, reconsider the approach instead of patching them one by one. See docs/VISUAL-STYLE-CONSTRAINTS.md, section “Techniques that change a rendering assumption”, for a previous example.
Use unit tests for game rules and small boundary modules. Keep Playwright focused on important browser contracts. The current test-layer rationale and coverage boundaries live in the test strategy.
Property-based tests use fast-check. Prefer small explicit helper factories over repeated raw object literals:
createAsteroid({ x: 500, speed: 120 });- Do not test Canvas pixels.
- Assert through stable DOM hooks such as
data-testid. - Keep E2E tests short, stable, independent, and free of test-order assumptions.
- Prefer small, focused changes.
- Identify likely files before editing.
- Do not combine unrelated refactors with feature work.
- Do not rewrite the project for a small request.
- Do not silently change product direction.
- Do not create commits unless explicitly asked.
- When a request is ambiguous, make the smallest reasonable assumption and state it briefly.
- Check
git statusbefore larger edits. - Do not reset, checkout, delete, or discard unrelated changes unless explicitly asked.
- Run the narrowest relevant checks first.
- For broader or riskier changes, run
npm run check. - If a check cannot be run, say so and run the best available alternative.
After changing code, summarize:
- what changed,
- important files touched,
- checks run and their results,
- checks that could not be run.
Use prefix/kebab-case-description.
feature/— one new capabilityfix/— a bug fix, including a visual bugdocs/— documentation onlyrefactor/— restructuring without behavior changeredesign/— multiple related concerns around an existing system, typically withinsrc/rendering/tests/— test-suite-only changesinfra/— CI, tooling, or configuration
For mixed changes entirely within src/rendering/ that combine more than one concern, use redesign/. Otherwise choose the prefix matching the riskiest part of the change.
Do not duplicate the README in agent instructions.
- README.md is the public project snapshot: purpose, setup, commands, testing, and high-level architecture.
- Update
README.mdwhen setup, commands, structure, user-facing behavior, roadmap, or scope changes. - Update the test strategy when test levels, E2E strategy, quality gates, or major coverage decisions change.
- Update shared agent instructions when architecture-level facts change, especially the layout under
src/game/, state ownership, or the role ofsrc/main.ts. - Add or update an ADR in
docs/only for meaningful architectural decisions, not small local refactors. - Keep visual invariants and rendering-assumption guidance in the visual constraints.