Arcane is a code-first, test-native, agent-native 2D game engine. Rust core for performance, TypeScript scripting for game logic.
Current status: v0.26.1 — Fix Direction export for V8 module loader; larger asset catalog preview for small sprites. 2418 TS (Node) + 2421 (V8) + 389 Rust tests passing. Phases 0-28 complete.
arcane/
├── README.md — Vision, elevator pitch, quick overview
├── CLAUDE.md — You are here
├── CONTRIBUTING.md — How to contribute (humans and agents)
├── LICENSE — Apache 2.0
├── Cargo.toml — Workspace root (core + cli)
├── .github/workflows/ci.yml — CI: Node tests, Rust tests, V8 tests
├── docs/
│ ├── engineering-philosophy.md — The Three Laws, development principles, code personality
│ ├── api-design.md — LLM-friendly API rules, error design, naming
│ ├── glossary.md — Canonical definitions for all terms
│ ├── architecture.md — Two-layer design (Rust + TypeScript)
│ ├── world-authoring.md — Code-defined scenes, worlds, tilemaps
│ ├── agent-tooling.md — Claude Code agents, skills, MCP tools
│ ├── development-workflow.md — Parallel dev, model selection, worktrees
│ ├── roadmap.md — Phased development plan
│ ├── technical-decisions.md — ADR-style decision log
│ ├── documentation-refresh.md — Checklist for auditing and refreshing all docs
│ ├── publishing-guide.md — crates.io release process and checklist
│ ├── testing-hot-reload.md — Hot-reload implementation details and debugging
│ └── rendering-philosophy.md — Rendering UX design principles and layer conventions
├── core/ — arcane-core lib crate
│ ├── Cargo.toml — Feature-gated: `renderer` (default on)
│ ├── src/
│ │ ├── lib.rs
│ │ ├── scripting/
│ │ │ ├── mod.rs — Public API: TsModuleLoader, ArcaneRuntime, run_test_file
│ │ │ ├── module_loader.rs — TsModuleLoader: TS transpilation via deno_ast
│ │ │ ├── runtime.rs — ArcaneRuntime: V8 + module loader + crypto polyfill
│ │ │ ├── test_runner.rs — V8 test runner with #[op2] result reporting
│ │ │ ├── render_ops.rs — #[op2] ops: sprites, camera, tilemap, lighting, input, audio, font, viewport
│ │ │ ├── replay_ops.rs — #[op2] ops: physics snapshot, recording, replay
│ │ │ ├── geometry_ops.rs — #[op2] ops: op_geo_triangle, op_geo_line → GeoState
│ │ │ ├── particle_ops.rs — #[op2] ops: Rust-native particle simulation
│ │ │ ├── target_ops.rs — #[op2] ops: render-to-texture (stub, future)
│ │ │ └── physics_ops.rs — #[op2] ops: physics world, bodies, constraints, queries (NOT feature-gated)
│ │ ├── physics/ — Homebrew rigid body physics (NOT feature-gated)
│ │ │ ├── mod.rs — Module declarations
│ │ │ ├── types.rs — RigidBody, Shape, Material, Contact, Constraint
│ │ │ ├── integrate.rs — Semi-implicit Euler integration
│ │ │ ├── broadphase.rs — Spatial hash grid
│ │ │ ├── narrowphase.rs — SAT collision detection (all shape pairs)
│ │ │ ├── resolve.rs — Sequential impulse solver
│ │ │ ├── constraints.rs — Distance + revolute joint solving
│ │ │ ├── sleep.rs — Sleep system (velocity threshold + timer)
│ │ │ └── world.rs — PhysicsWorld: fixed timestep, body storage, queries, raycast
│ │ ├── renderer/ — [feature = "renderer"]
│ │ │ ├── mod.rs — Renderer: owns GPU, sprite pipeline, textures, lighting
│ │ │ ├── gpu.rs — GpuContext: wgpu device/surface/pipeline setup
│ │ │ ├── sprite.rs — SpritePipeline: instanced quad rendering + lighting
│ │ │ ├── texture.rs — TextureStore: handle-based texture loading + raw upload
│ │ │ ├── camera.rs — Camera2D: position, zoom, view/proj matrix
│ │ │ ├── tilemap.rs — Tilemap + TilemapStore: tile data, atlas UV, camera culling
│ │ │ ├── lighting.rs — LightingState, PointLight, LightingUniform for GPU
│ │ │ ├── msdf.rs — MSDF font atlas, glyph metrics, SDF shader pipeline
│ │ │ ├── radiance.rs — Radiance Cascades 2D GI compute pipeline
│ │ │ ├── shader.rs — ShaderStore: custom WGSL fragment shaders, 16 vec4 uniforms
│ │ │ ├── postprocess.rs — PostProcessPipeline: offscreen targets, bloom/blur/vignette/CRT
│ │ │ ├── geometry.rs — GeometryBatch: colored geometry GPU pipeline (triangles, lines)
│ │ │ ├── font.rs — CP437 8×8 bitmap font data, generate_builtin_font()
│ │ │ └── shaders/
│ │ │ ├── sprite.wgsl — Instanced sprite shader with lighting (3 bind groups)
│ │ │ ├── radiance.wgsl — GI compute shader (3-pass)
│ │ │ ├── geom.wgsl — Geometry vertex+color shader
│ │ │ └── msdf.wgsl — MSDF distance field text fragment shader
│ │ ├── platform/ — [feature = "renderer"]
│ │ │ ├── mod.rs — Platform public API
│ │ │ ├── window.rs — winit ApplicationHandler + event loop
│ │ │ └── input.rs — Keyboard/mouse state tracking
│ │ └── agent/ — [feature = "renderer"]
│ │ ├── mod.rs — InspectorRequest/Response types, channel types
│ │ └── inspector.rs — tiny_http HTTP server on background thread
│ │ └── audio/ — [feature = "renderer"]
│ │ └── mod.rs — AudioCommand, audio_channel(), start_audio_thread() (rodio)
│ └── tests/ — Rust integration tests
├── catalog/ — Asset catalog data + browser UI
│ ├── catalog.json — Pack metadata (id, tileSize, spacing, tags, downloadUrl)
│ ├── html/ — Browser templates (browse, sheet, gallery, sounds)
│ └── sounds/kenney/ — Sound pack JSON definitions
├── cli/ — arcane-engine bin crate
│ ├── Cargo.toml
│ └── src/
│ ├── main.rs — clap CLI entrypoint
│ └── commands/
│ ├── mod.rs
│ ├── test.rs — `arcane test` — discovers & runs *.test.ts in V8
│ ├── dev.rs — `arcane dev` — window + game loop + hot-reload + inspector + audio
│ ├── describe.rs — `arcane describe` — text description of game state
│ ├── inspect.rs — `arcane inspect` — query specific state paths
│ ├── check.rs — `arcane check` — fast type-check
│ └── catalog.rs — `arcane catalog` — visual asset browser with persistent cart
├── runtime/
│ ├── testing/
│ │ ├── harness.ts — Universal test harness (Node + V8)
│ │ ├── replay.ts — startRecording, stopRecording, replay, diffReplays
│ │ ├── snapshot.ts — World snapshot capture/compare for determinism testing
│ │ ├── property.ts — checkProperty, assertProperty, shrinking, generators
│ │ └── index.ts — Testing barrel export
│ ├── procgen/
│ │ ├── types.ts — TileId, WFCOptions, Constraint, WFCResult
│ │ ├── wfc.ts — Wave Function Collapse algorithm
│ │ ├── constraints.ts — reachability, exactCount, minCount, maxCount, border
│ │ ├── validate.ts — validateLevel, generateAndTest
│ │ └── index.ts — Barrel export
│ ├── state/
│ │ ├── types.ts — EntityId, Vec2, DeepReadonly
│ │ ├── error.ts — ArcaneError, createError()
│ │ ├── prng.ts — PRNGState, seed(), rollDice(), xoshiro128**
│ │ ├── transaction.ts — Mutation, Diff, transaction(), computeDiff()
│ │ ├── query.ts — query(), get(), has(), filter combinators
│ │ ├── observe.ts — ObserverRegistry, path pattern matching
│ │ ├── store.ts — GameStore, createStore()
│ │ └── index.ts — Public API barrel export
│ ├── physics/
│ │ ├── aabb.ts — AABB type, aabbOverlap(), circleAABBOverlap/Resolve()
│ │ ├── types.ts — BodyId, BodyDef, ShapeDef, MaterialDef, BodyState, Contact, RayHit
│ │ ├── world.ts — createPhysicsWorld(), stepPhysics(), destroyPhysicsWorld()
│ │ ├── body.ts — createBody(), destroyBody(), getBodyState(), setBodyVelocity(), applyForce/Impulse()
│ │ ├── constraints.ts — createDistanceJoint(), createRevoluteJoint(), removeConstraint()
│ │ ├── query.ts — queryAABB(), raycast(), getContacts()
│ │ └── index.ts — Barrel export (aabb helpers + physics engine API)
│ ├── rendering/
│ │ ├── types.ts — TextureId, SpriteOptions, CameraState, TilemapId
│ │ ├── atlas.ts — SpriteAtlas: loadAtlasFromDef(), UV normalization, tag queries
│ │ ├── sprites.ts — drawSprite() (unified: color, parallax, tiling), clearSprites()
│ │ ├── camera.ts — setCamera(), getCamera(), followTarget(), followTargetSmooth(), setCameraBounds(), setCameraDeadzone(), zoomTo(), zoomToPoint()
│ │ ├── autotile.ts — 4-bit/8-bit bitmask auto-tiling
│ │ ├── animation-fsm.ts — Animation state machine (states, transitions, blending)
│ │ ├── input.ts — isKeyDown(), isKeyPressed(), getMousePosition()
│ │ ├── tilemap.ts — createTilemap(), setTile(), getTile(), drawTilemap()
│ │ ├── lighting.ts — setAmbientLight(), addPointLight(), clearLights()
│ │ ├── texture.ts — loadTexture(), createSolidTexture(), preloadAssets(), isTextureLoaded(), getLoadingProgress()
│ │ ├── loop.ts — onFrame(), getDeltaTime()
│ │ ├── text.ts — drawText(), measureText(), loadFont(), getDefaultFont(), MSDF font support, wrapText(), drawTextWrapped(), drawTextAligned()
│ │ ├── animation.ts — createAnimation(), updateAnimation(), drawAnimatedSprite()
│ │ ├── audio.ts — loadSound(), playSound(), playMusic(), stopSound(), setVolume(), instance-based playback, spatial audio, bus mixing, crossfade, pooling
│ │ ├── shader.ts — createShaderFromSource(), setShaderParam(), createShader(), setShaderUniform(), getShaderUniformNames()
│ │ ├── effects.ts — 8 effect presets: outline, flash, dissolve, pixelate, hologram, water, glow, grayscale
│ │ └── index.ts — Barrel export
│ ├── ui/
│ │ ├── types.ts — Color, RectOptions, PanelOptions, BarOptions, LabelOptions
│ │ ├── primitives.ts — drawRect(), drawPanel(), drawBar(), drawLabel()
│ │ ├── shapes.ts — drawCircle(), drawEllipse(), drawRing(), drawLine(), drawTriangle(), drawArc(), drawSector(), drawCapsule(), drawPolygon()
│ │ ├── button.ts — createButton(), updateButton(), drawButton()
│ │ ├── toggle.ts — createCheckbox(), createRadioGroup(), updateCheckbox(), updateRadioGroup()
│ │ ├── slider.ts — createSlider(), updateSlider(), drawSlider()
│ │ ├── text-input.ts — createTextInput(), updateTextInput(), drawTextInput()
│ │ ├── layout.ts — verticalStack(), horizontalRow(), anchorTo()
│ │ ├── focus.ts — createFocusManager(), updateFocus(), tab navigation
│ │ └── index.ts — Barrel export
│ ├── pathfinding/
│ │ ├── types.ts — PathGrid, PathOptions, PathResult
│ │ ├── astar.ts — findPath() A* with binary min-heap
│ │ └── index.ts — Barrel export
│ ├── systems/
│ │ ├── types.ts — Rule, SystemDef, RuleResult, ExtendOptions
│ │ ├── system.ts — system(), rule(), applyRule(), extend()
│ │ └── index.ts — Barrel export
│ ├── scenes/
│ │ ├── types.ts — SceneContext, SceneDef, SceneInstance, TransitionConfig
│ │ ├── scene.ts — createScene(), pushScene(), popScene(), replaceScene(), startSceneManager()
│ │ └── index.ts — Barrel export
│ ├── persistence/
│ │ ├── types.ts — SaveMetadata, SaveFile, Migration, StorageBackend
│ │ ├── storage.ts — createMemoryStorage(), createFileStorage()
│ │ ├── save.ts — serialize(), deserialize(), saveGame(), loadGame(), migrations
│ │ ├── autosave.ts — enableAutoSave(), updateAutoSave(), triggerAutoSave()
│ │ └── index.ts — Barrel export
│ ├── agent/
│ │ ├── types.ts — AgentConfig, ActionInfo, DescribeOptions, etc.
│ │ ├── protocol.ts — registerAgent(), AgentProtocol on globalThis
│ │ ├── describe.ts — Default text description renderer (minimal/normal/detailed)
│ │ ├── mcp.ts — MCP tool definitions, request builders
│ │ ├── index.ts — Barrel export
│ │ └── agent.test.ts — Agent protocol tests (~37 tests)
│ └── game/
│ ├── types.ts — Convenience layer type definitions
│ ├── hud.ts — hud.text(), hud.bar(), hud.label() shortcuts
│ ├── widgets.ts — captureInput(), autoUpdate* widget helpers
│ ├── collision.ts — Collision event registry + callbacks
│ ├── entity.ts — Lightweight entity handles (sprite+physics)
│ ├── game.ts — createGame() bootstrap
│ ├── transform.ts — Scene node hierarchy: createNode(), setNodeTransform(), getWorldTransform(), applyToSprite()
│ └── index.ts — Barrel export
├── demos/
│ ├── agent-testing/ — Phase 17 demo: MCP tools, snapshot replay, property testing
│ ├── asteroids/ — Phase 8 demo: rotation, flip, opacity, blend modes, CRT post-processing
│ ├── audio-showcase/ — Phase 20 demo: spatial audio, crossfade, mixer panel
│ ├── breakout/ — Phase 2b demo: real-time arcade (paddle, ball, bricks)
│ ├── card-battler/ — Phase 1 demo: card game
│ ├── character-controller/ — Phase 15 demo: animation state machine + blending
│ ├── gamepad-platformer/ — Phase 24 demo: gamepad/touch input, action mapping
│ ├── hello-world/ — Minimal starter: sprite + text
│ ├── hex-strategy/ — Phase 23 demo: hex grid strategy, terrain, pathfinding
│ ├── isometric-dungeon/ — Phase 22 demo: isometric 2.5D, coordinate transforms, depth sorting
│ ├── juice-showcase/ — Phase 9 demo: tweening, particles, camera shake
│ ├── lighting-showcase/ — Phase 19 demo: point/directional/spot lights, GI, day/night
│ ├── menu-flow/ — Phase 10 demo: scene management, save/load, menu flow
│ ├── msdf-text-showcase/ — MSDF text rendering: outlines, shadows, crisp scaling
│ ├── parallax-scroller/ — Phase 13 demo: parallax scrolling + camera features
│ ├── physics-playground/ — Phase 11 demo: rigid body physics sandbox
│ ├── platformer/ — Phase 4 demo: gravity, platforms, coins, text HUD, UI bars
│ ├── roguelike/ — Phase 2b demo: procedural dungeon, FOV, fog of war
│ ├── shader-showcase/ — Phase 28 demo: effect presets, named uniforms, time-based animation
│ ├── shapes-showcase/ — Phase 25 demo: geometry pipeline shapes, colors, layers
│ ├── sokoban/ — Phase 1 demo: grid puzzle + Phase 2a visual demo
│ ├── sprite-demo/ — Phase 5.5 demo: asset loading validation with sprite sheet + sound
│ ├── tilemap-showcase/ — Phase 14 demo: layers, auto-tiling, animated tiles
│ ├── tower-defense/ — Phase 5 demo: tower placement, enemy waves, pathfinding
│ ├── ui-showcase/ — Phase 16 demo: interactive UI widgets
│ ├── visual-polish/ — Phase 21 demo: transitions, nine-slice, trails, floating text
│ └── wfc-dungeon/ — Phase 18 demo: WFC procedural dungeon generation
├── templates/
│ └── default/ — Scaffolded project template (AGENTS.md, docs/, types/)
├── scripts/
│ ├── generate-declarations.sh — Generates per-module .d.ts files from runtime source JSDoc
│ ├── run-tests.sh — Node TS test runner (all runtime + demo tests)
│ ├── run-coverage.sh — Node TS tests with V8 coverage
│ └── run-coverage-rust.sh — Rust tests with cargo-llvm-cov coverage
- Write clearly and concisely. Prefer examples over explanation.
- Include TypeScript code examples for any API design.
- Use ASCII diagrams, not images.
- Every design claim should be consistent across all documents. If you change the architecture in one doc, update all others.
- ADRs in
technical-decisions.mdfollow the format: Context → Options → Decision → Rationale.
- Link between documents using relative paths:
[Architecture](architecture.md) - When a concept is defined in one doc and referenced in another, link to the definition.
- All TypeScript examples should be valid, readable, and illustrate the actual intended API.
- Use realistic game scenarios (RPG, roguelike, platformer examples preferred).
- Show both the "what" (API usage) and the "why" (what problem it solves).
Read docs/engineering-philosophy.md first. It governs everything else.
The Three Laws (in priority order):
- Is it correct? — Verified by tests, not inspection.
- Is it clear? — Readable by a human or agent six months from now.
- Is it minimal? — The least code/complexity that achieves correctness and clarity.
Definition of Done: It works, it's tested, it's consistent, it's documented, it's reviewed.
Acceptance criteria before implementation: Write what "done" looks like before writing code. Write the tests (or test signatures) before the implementation.
- Code-is-the-scene — No visual editor. Scenes are TypeScript code.
- Game-is-a-database — State is queryable, transactional, observable.
- Testing-first — Game logic runs headless. Tests are instant.
- Agent-native — Built-in protocol for AI agent interaction.
- Explicit over implicit — No hidden state, no singletons, no magic strings.
- Functional core — State in, state out. Pure functions for game logic.
- TypeScript code lives under
runtime/. Rust code undercore/andcli/. - TS runtime has zero external dependencies. Rust crates use deno_core, deno_ast, clap, tokio, anyhow, wgpu, winit, image, bytemuck, notify, tiny_http, rodio.
- All state management functions are pure: state in, state out.
- TS files use
.tsextension imports (no bundler). - Test files import from
runtime/testing/harness.ts(notnode:test/node:assertdirectly). - Tests must pass in both Node (
./scripts/run-tests.sh) and V8 (cargo run -- test). - Pin exact versions of deno_core and deno_ast to avoid API churn.
- Renderer is behind
rendererCargo feature (default on). Headless:--no-default-features. - Rendering API functions are no-ops in headless mode (safe to import anywhere).
arcane dev <entry.ts>opens a window with hot-reload.arcane teststays headless.arcane describe <entry.ts>prints text description.arcane inspect <entry.ts> <path>queries state.- Agent protocol: games call
registerAgent()to installglobalThis.__arcaneAgent. Rust evals TS to interact. - HTTP inspector (
--inspector <port>on dev): channel-based, polls requests in frame callback.
After changing any public API in runtime/, run scripts/generate-declarations.sh and commit the updated templates/default/types/*.d.ts files. These per-module declaration files ship with scaffolded projects and are the primary API reference for LLMs.
After phase completions or significant changes, run the documentation refresh checklist in docs/documentation-refresh.md. It covers auditing docs/, verifying user-facing READMEs, updating the scaffolding template (AGENTS.md, topic guides, per-module types/*.d.ts), and fixing stale test counts, demo lists, and cross-references.
See docs/agent-tooling.md for the full specification of agents, skills, and MCP tools used to develop Arcane. The tooling set evolves with the project — review it at each phase transition.
Arcane supports coordinated multi-session development via agent teams. See docs/development-workflow.md for the full workflow (when to use teams vs subagents vs worktrees, task sizing, model selection). Teammates read this file automatically and should follow all conventions above. File ownership discipline is critical in team mode — each teammate owns distinct files, no two teammates edit the same file simultaneously.