This project is a strict-TypeScript game/editor prototype focused on extensibility:
- multi-level catalogs
- pluggable enemy and player behaviors
- typed obstacle pipeline
- physics adapter boundary
- Node.js:
24.13.1(latest LTS baseline for this repo) - npm:
11.8.0(bundled with Node24.13.1) - Version pins are enforced via:
.nvmrc.npmrc(engine-strict=true)package.json(engines+packageManager).github/workflows/ci.yml
- Use the pinned runtime:
nvm install
nvm use
node -v
npm -v- Install dependencies:
npm ci- Validate quality gates:
npm run check- Start the local server:
npm startOpen http://localhost:8000.
If port 8000 is unavailable, or you want to bind a specific host:
HOST=127.0.0.1 PORT=8010 npm startProject documentation:
README.mdDESIGN.mddocs/runtime-baseline.md
src/client/main.ts: bootstrap and orchestration between UI, state, and scene lifecyclesrc/client/scenes/MainScene.ts: runtime scene composition, rendering, gameplay loopsrc/client/scenes/runtime/: scene-local runtime systems (CoverageSystem,CollidableIndex,EnemyRuntimeSystem,PaintRuntimeSystem,BombRuntimeSystem,ProgressionRuntimeSystem) plus pure runtime policiessrc/client/scenes/runtime/RuntimeSceneBridge.ts: key-based runtime scene lookup helper for restart/live-update orchestrationsrc/client/systems/EditorSystem.ts: editor interactions and object manipulationsrc/client/systems/editor/EnemySpatialIndex.ts: spatial index for scalable enemy selection/placement queries in editor modesrc/client/systems/editor/EnemyHandleQuery.ts: extracted enemy handle-hit math for editor resize/wander controlssrc/client/managers/LevelManager.ts: level lifecycle orchestration, history, catalog navigationsrc/client/managers/level/LevelSchema.ts: centralized level defaults + normalization/schema parsingsrc/client/managers/level/GoalNormalization.ts: focused goal-threshold and goal-composition parsing/normalization helperssrc/client/managers/UIManager.ts: typed UI event bus and form synchronizationsrc/client/managers/ui/: focused UI modules (GoalCompositionEditor,ObjectInventory,ColorPickerPopover,EnvironmentColorRegistry)src/client/config/SettingBindings.ts: single source of truth for settings input-to-domain mappings (consumed by bothmain.tsandUIManager)src/client/content/LevelObjects.ts: obstacle/object normalization and spatial helpers for editor/runtimesrc/client/utils/ObstacleOverrideDefaults.ts: shared obstacle override baseline/default mapping used by schema, UI inspector, and context actionssrc/client/ai/: enemy behavior contracts, implementations, and registrysrc/client/ai/EnemyTuning.ts: centralized enemy tuning fallback and runtime distance conversion (used by both editor and runtime)src/client/player/: player behavior contracts, implementations, and registrysrc/client/physics/: physics adapter contracts and concrete adapter implementationssrc/client/types/: shared domain, events, and global contractssrc/server/dev-server.ts: Express server + browser log ingestion endpointsrc/server/logging/: server logging normalization/cache/type helpersdocs/runtime-baseline.md: pinned Node/npm policy and local/CI setup workflowtest/: Vitest suites for manager/content/registry/runtime-system behavior
- Add the id in
ENEMY_BEHAVIOR_TYPES(src/client/types/domain.ts) so both type checks and schema validation stay in sync. - Implement
EnemyBehaviorinsrc/client/ai/behaviors/. - Register it in
src/client/ai/EnemyBehaviorRegistry.ts. - Add it to UI controls (if configurable) in
index.html,UIManager, andmain.tssetting maps.
- Add the id in
PLAYER_BEHAVIOR_TYPES(src/client/types/domain.ts). - Implement
PlayerBehaviorinsrc/client/player/behaviors/. - Register in
src/client/player/PlayerBehaviorRegistry.ts. - Expose tunables in
LevelSettingsand sync through UI maps.
- Extend
OBSTACLE_TYPESandObstacleDatainsrc/client/types/domain.ts. - Add runtime definition in
src/client/content/LevelObjects.ts. - Provide texture/render handling in
MainScene. - Add editor tool wiring in
index.html+EditorSystem.
Current product direction is one-engine optimization: Arcade physics is fixed as runtime engine, and the editor exposes deep Arcade tuning instead of engine swapping.
If you later need a second engine:
- Add the id in
PHYSICS_ENGINE_TYPES(src/client/types/domain.ts). - Implement
PhysicsAdapterinsrc/client/physics/. - Register adapter in
PhysicsAdapterRegistry.ts. - Add UI/schema wiring only when truly needed.
- Put pure, performance-sensitive scene logic in
src/client/scenes/runtime/. - Keep the system Phaser-agnostic when possible (state updates and indexing in plain TS).
- Inject the system into
MainSceneduringcreate()with current runtime config. - Add a focused unit test under
test/for the new system.
Current runtime extraction examples:
EnemyRuntimeSystemhandles enemy attraction, debug drawing, and trail emission.PaintRuntimeSystemhandles projectile paint, owner resolution, and coverage/exit activation updates.BombRuntimeSystemhandles bomb touch/explosion flow and wall-targeted splash painting with wall-face impact projection.ProgressionRuntimeSystemhandles win-state presentation and level-advance flow.EnemyRuntimePolicy,PaintRuntimePolicy, andProgressionRuntimePolicycontain deterministic pure logic used by tests.
LevelManagersupports both:- single-level JSON payloads (editor convenience)
- catalog payloads with
currentLevelIndex+levels[]
- Runtime supports next/previous level navigation and per-level identity (
id,name). - Editor now supports level authoring actions directly in UI: add, duplicate, delete, and JSON load.
- Save now emits a canonical v3 catalog envelope (
schemaVersion: 3,currentLevelIndex,levels) for consistent multi-level workflows. - Catalog payloads with unsupported schema versions are rejected during load.
- Enemy ground checks use indexed tile lookup (avoids per-frame wall scans).
- Coverage updates run through
CoverageSystemwith per-surface ownership accounting (avoids full-grid recounts and repeated same-surface churn). - Coverage tracking is idempotent per surface/color: repainting the same surface with the same color does not increase coverage.
- Coverage paint expiration is surface-scoped with lightweight next-expiry tracking.
- Drag fill uses occupancy sets (avoids repeated O(n) duplicate scans during area writes).
- Editor history now supports delta-based undo entries for high-frequency editor mutations (move/resize/fill/erase/settings), reducing full-state clone pressure.
- Enemy attraction uses squared-distance checks (avoids unnecessary square-root work in hot loops).
- Paint owner resolution caches player/enemy palette colors per scene runtime instance.
- Projectile runtime now performs TTL/out-of-bounds recycling to prevent object-pool exhaustion.
- Paint splats now recycle pooled instances when saturated to keep visual feedback stable over long sessions.
- Player bullet paint and enemy trail paint now support optional timed fade durations (seconds-based settings) with lightweight in-runtime expiry tracking.
- Editor selection and hit-testing uses squared-distance checks with shared thresholds.
- Inspector sync during drag-resize is frame-batched (
requestAnimationFrame) to reduce DOM update pressure. - Enemy selection/placement uses a grid spatial index to avoid linear scans as enemy counts grow.
- Enemy handle hit-testing now uses override-aware spatial search distance and avoids per-move full-enemy fallback scans.
- DOM bootstrapping uses a safe
onDomReadygate to avoid missed initialization paths. - UI synchronization caches key DOM references and skips no-op form writes to lower inspector overhead.
- Help modal tab switching is now DOM-scoped to the modal, preventing accidental inspector-tab state mutations.
- Editor mutations now update scene visuals incrementally in edit mode (move/add/remove/object-fill) instead of full scene restarts on every action.
- Enemy attraction state now cleanly releases back to non-attracted behavior when attraction no longer applies.
- Dynamic runtime-safe settings (
playerSpeed,playerJump,playerAirControl,playerPaintDecay,playerPaintDuration,enemyTrailDecay,enemyTrailDuration,debugAI,goalTeam,targetPercent,targetThresholds,wallColorDefault,spikeColorDefault,bombColorDefault) now apply live without full scene restart. - Dynamic runtime-safe settings now also include Arcade tuning values (gravity, projectile cadence/speed/ttl, player acceleration/drag/jump windows, enemy movement/attraction/jump tuning).
- Grid/map resize now clamps and deduplicates entity/obstacle geometry to prevent stale out-of-bounds runtime state.
- Runtime scene bridge resolution now targets
MainSceneby key rather than scene array index. - Phaser runtime dependency is served locally (
/node_modules/phaser/...) to remove external CDN reliance in local sessions. - Main scene base textures are regenerated only when grid-size-relevant signatures change, avoiding repeated texture churn on no-op restarts.
- Strict TS flags enabled (
strict,exactOptionalPropertyTypes,noUncheckedIndexedAccess). - Supported engine/behavior/obstacle literals are centralized as exported typed catalogs in
src/client/types/domain.tsand reused by schema validation. - Imported JSON is normalized defensively through
LevelSchema. - Normalization now clamps map dimensions (
MIN/MAX_MAP_TILES), grid size (MIN/MAX_GRID_SIZE), target percent (0..100), and all object/player/exit coordinates into valid in-bounds grid centers. - Loaded map dimensions are snapped to the active grid size so imported levels always align to full tiles.
- Incremental settings edits are normalized through
normalizeSettingValue, so direct UI updates cannot write invalid runtime settings. - Incremental local-enemy overrides are normalized through shared schema bounds before write, matching global tuning safety.
- Paint persistence settings (
playerPaintDecay,playerPaintDuration,enemyTrailDecay,enemyTrailDuration) are validated and clamped during both import normalization and live updates. - Goal threshold settings now support multi-threshold inputs (
targetThresholds) with clamped/sorted normalization and legacytargetPercentcompatibility. - Goal composition settings now support multi-team unlock rules (
goalComposition, for exampleenemy:20, player:20) with normalization and legacy-field synchronization. - Enemy identity fields (
id,name) are normalized to stable unique values so inventory actions and per-enemy authoring remain deterministic. - Arcade tuning settings and obstacle-size settings are validated/clamped in schema normalization and incremental updates.
- Obstacle color normalization falls back to object default settings (
wallColorDefault,spikeColorDefault,bombColorDefault) so non-overridden instances stay linked to global defaults. - Scene color conversion is now safely validated at call sites (no global Phaser parser patching).
- Enemy sensing/chase tuning now uses canonical width/height fields only (
sightWidth,sightHeight,chaseWidth,chaseHeight).
- Runtime baseline: Node
24.13.1LTS + npm11.8.0pinned across local and CI. - Linting:
npm run lint(ESLint flat config with TypeScript rules) - Formatting:
npm run format/npm run format:check(Prettier) - Full local quality gate:
npm run check - Mutation-testing pilot:
npm run test:mutation(targets critical modules; usenpm run test:mutation -- --dryRunOnlyfor wiring validation) - CI workflow:
.github/workflows/ci.ymlrunstypecheck,lint,test,build, andformat:check
npm run build emits:
dist/client/**/*.jsdist/server/dev-server.js
index.html loads dist/client/main.js.
index.html loads Phaser from /node_modules/phaser/dist/phaser-arcade-physics.min.js.
- Object inspector tabs are context-scoped to the selected object/tool; selecting a bomb exposes bomb defaults in
Globaland bomb overrides inIndividual. - Inspector tab choice is stable (no forced auto-switch); local tab falls back to global only when local overrides are unavailable.
- Global object-default panels now use a consistent
Defaultbadge treatment across enemy/player/exit/wall/spike/bomb contexts. - Global wall/spike/bomb inspector panels each include a visual color picker with the same palette flow as other object color controls.
- Individual override panels use a consistent
Tunningbadge treatment. - New paint persistence controls allow configuring permanent vs timed fade behavior for player and enemy paint.
- Winner-team labels are color-dynamic (derived from current player/enemy palette values), not hardcoded to red/blue text.
- Goal rules support multiple thresholds (for example
15, 30, 45) with live HUD updates. - Goal rules also support composite unlock requirements (for example
enemy:20, player:20) with HUD progress shown for both teams. - Goal composition editing uses structured rows sourced from current environment colors (player/enemy/obstacle), with a swatch-first popover picker, source labels, and hex backup input.
- Individual inspector rows expose consistent reset/apply actions; apply propagates to same-type instances and updates object defaults where applicable (for example bomb defaults).
- Obstacle color edits (global defaults and individual overrides) now apply live without full scene restart, and obstacle textures are neutral so tint changes are visually clear.
- Bomb splash paint now applies only to wall surfaces within radius and lands on the nearest wall face (not tile center); explosions with no nearby wall leave coverage unchanged.
- Sidebar is dock-first: top-level panels start in the sidebar, can be undocked by drag, and support per-panel/global layout reset.
- Docked layout supports sidebar width resize and per-panel height resize; floating panels support width+height resize.
- Top-level sidebar panels remain collapsible in both docked and floating modes (floating collapse is header-only) and support quick-jump navigation.
- Inventory panel lists player/exit/enemies/walls/spikes/bombs grouped by type, with per-instance selection sync to canvas selection.
- Global game settings are organized under one
Game Settingssection with subsections (World,Goals & Win Conditions,Paint & Fade Rules,Physics & Motion). - Color selection now uses an in-app palette popover with an explicit Existing Level Colors list plus custom HSV controls, RGB fields, and hex tools, anchored outside scroll clipping regions.
- Grid/map size and object dimensions are editable in both tiles and pixels (width/height pairs), with synchronized conversions.
- Arcade physics is now exposed as a single highly-configurable profile (no engine swap dropdown).
- Sidebar and header styling were upgraded for clearer hierarchy, stronger focus states, and better visual consistency.
- Editor controls and Goal Mix rows now follow a consistent WCAG-AA-oriented focus/contrast treatment across light and dark themes.
- Layout is responsive for tablet/mobile widths (stacked workbench, adaptive controls area, and sidebar adjustments).
The dev server accepts browser logs at POST /log and writes per-client log files under logs/.
- Payloads are normalized and validated before write (bounded message/type/client-id/data).
- JSON body size is capped (
64kb) to avoid oversized request pressure. - Active in-memory client log-file tracking is pruned by TTL and bounded by max active client count to prevent unbounded cache growth in long sessions.
- Logging concerns are split into focused modules (
LogNormalization,LogCache,LogTypes) for maintainable extension. - Server startup is now explicit (
startDevServer) and supports test imports without side effects. - Endpoint behavior is covered by both unit normalization tests and HTTP integration tests.
- Scene init/create failures now emit structured client logs with correlation IDs, and
/logpayload serialization preservesErrordetails (name,message,stack,cause,code) safely.