[add] src/geometry/model-instance-collider-generation.ts [add] src/runtime-three/rapier-collision-world.ts [change] AGENTS.md [change] CHAT_CONTEXT.md [change] architecture.md [change] package.json [change] prompts-lite.txt [change] prompts.txt [change] roadmap.md [change] src/assets/model-instances.ts [change] src/document/migrate-scene-document.ts [change] src/document/scene-document-validation.ts [change] src/document/scene-document.ts [change] src/runtime-three/first-person-navigation-controller.ts [change] src/runtime-three/navigation-controller.ts [change] src/runtime-three/runtime-host.ts [change] src/runtime-three/runtime-scene-build.ts [change] src/runtime-three/runtime-scene-validation.ts [change] src/viewport-three/viewport-host.ts [change] testing.md
541 lines
17 KiB
Markdown
541 lines
17 KiB
Markdown
# AGENTS.md
|
||
|
||
## Project identity
|
||
|
||
This repository contains a browser-based brush/solid editor for creating lightweight interactive 3D scenes, plus a built-in browser runner for playing those scenes.
|
||
|
||
The product goal is:
|
||
|
||
- Hammer / TrenchBroom style spatial authoring
|
||
- modern browser delivery
|
||
- glTF asset import, with optional later interchange export
|
||
- fast edit -> run iteration
|
||
- lightweight interactive runtime with spatial audio, navigation modes, and simple entity-driven logic
|
||
|
||
This is not a general-purpose DCC.
|
||
This is not a full game engine.
|
||
This is a focused authoring + runtime tool for browser-delivered 3D spaces.
|
||
|
||
---
|
||
|
||
## Product pillars
|
||
|
||
1. **Brushes are sacred**
|
||
- Layout authoring must remain faster than Blender.
|
||
- Brush editing is a first-class workflow, not a legacy compatibility mode.
|
||
- The editor must feel immediate, precise, and grid-friendly.
|
||
|
||
2. **Imported assets are first-class**
|
||
- glTF / GLB import must feel native.
|
||
- Imported meshes, materials, textures, and animations must coexist cleanly with brush-authored worlds.
|
||
- Imported assets complement brushes; they do not replace them.
|
||
|
||
3. **The runner is built in**
|
||
- Every meaningful authoring step should be testable in-browser.
|
||
- Switching from edit mode to play mode should be nearly instant.
|
||
- The runner is part of the product, not a demo app.
|
||
|
||
4. **Web-native sharing matters**
|
||
- Scenes should be easy to load, embed, preview, and eventually share by URL.
|
||
- The browser is a target platform, not a secondary export target.
|
||
|
||
---
|
||
|
||
## Architectural stance
|
||
|
||
When making design decisions, prefer:
|
||
|
||
- plain three.js over unnecessary abstraction
|
||
- explicit data models over implicit scene graph state
|
||
- deterministic rebuilds over hidden mutations
|
||
- command-based editing over ad hoc state changes
|
||
- typed scene entities over free-form JSON blobs
|
||
- vertical slices over speculative infrastructure
|
||
- boring, maintainable code over cleverness
|
||
|
||
The project uses:
|
||
|
||
- React for application shell and editor UI
|
||
- three.js for viewport and runtime rendering
|
||
- a canonical editor document model independent of three.js
|
||
- command pattern for undo/redo
|
||
- runtime entity systems for navigation, triggers, audio, and interaction
|
||
- glTF / GLB as the main imported 3D asset format
|
||
- JSON as the canonical authoring format
|
||
|
||
Do not collapse editor state into raw three.js objects.
|
||
Do not make the three.js scene the source of truth.
|
||
Do not make glTF the canonical editor save format.
|
||
|
||
---
|
||
|
||
## Early binding decisions
|
||
|
||
These defaults are intentionally fixed for the early slices unless a later slice explicitly changes them.
|
||
|
||
### Coordinate system
|
||
|
||
- world space is right-handed and **Y-up**
|
||
- `+X` is right, `+Y` is up
|
||
- scene units are meter-like and should be used consistently for movement, collision, and audio distances
|
||
|
||
### Early repo shape
|
||
|
||
- start as a single Vite app
|
||
- keep domain folders under `src/`
|
||
- do not introduce `/apps` + `/packages` or a monorepo split until the current code actually needs it
|
||
|
||
### State ownership
|
||
|
||
- do not use the React tree as the canonical state container
|
||
- keep canonical editor state in a thin external editor store/service
|
||
- React renders and dispatches commands; it does not own the document
|
||
|
||
### Persistence
|
||
|
||
- the canonical scene document is versioned from day one
|
||
- M0-M2 may use local draft persistence plus explicit JSON import/export
|
||
- once binary assets matter, user-facing save/load must become a portable project package containing canonical scene JSON plus referenced assets
|
||
- canonical scene JSON remains the source document format, but by itself is no longer a portable project once external assets exist
|
||
- runner/deployment output is a separate downstream package, not the editable project format
|
||
- when binary assets arrive, they must survive reloads via embedded data or project-scoped packaged storage
|
||
- never rely on ephemeral Blob URLs as the only persisted asset reference
|
||
|
||
### Early brush defaults
|
||
|
||
- Slice 1.1 box brushes are axis-aligned only
|
||
- arbitrary brush rotation is explicitly deferred
|
||
- canonical box face IDs are fixed and stable:
|
||
- `posX`
|
||
- `negX`
|
||
- `posY`
|
||
- `negY`
|
||
- `posZ`
|
||
- `negZ`
|
||
- `posY` is the top face and `negY` is the bottom face
|
||
|
||
### Model placement
|
||
|
||
- placed imported models are **model instances**, not typed entities
|
||
- keep model instances in a document collection separate from `entities`
|
||
|
||
### Imported model collision scope
|
||
|
||
- collision authoring for imported models belongs on `modelInstances`, not on asset records
|
||
- the canonical source of truth is authored collision settings, not cooked collider bytes
|
||
- generated collider data may be cached or rebuilt, but it is derived from:
|
||
- imported model asset geometry
|
||
- model instance transform
|
||
- authored collision settings
|
||
- for imported-model collider support beyond simple boxes, prefer integrating a real collision/query library such as Rapier over inventing custom broad-phase/narrow-phase code in-house
|
||
- let the collision/query layer own broad-phase and narrow-phase pruning instead of re-implementing that manually in app code
|
||
- do not turn this slice into a full physics sandbox or general rigidbody architecture rewrite unless the roadmap explicitly asks for that
|
||
- near-term slices may adapt or replace the current handcrafted runner collision path where necessary so brush and imported-model colliders can participate in one coherent collision/query system
|
||
|
||
### Runtime interaction scope
|
||
|
||
- keep trigger/action/target links explicit and typed
|
||
- do not activate actions for systems that do not exist yet
|
||
- add sound and animation actions only when those runtime systems are implemented
|
||
|
||
### Early clipping scope
|
||
|
||
- until a dedicated convex-brush slice exists, clipping must be constrained to results representable by currently supported brush kinds
|
||
- unsupported clip cases must fail clearly instead of inventing new hidden geometry rules
|
||
|
||
---
|
||
|
||
## Non-goals
|
||
|
||
Unless explicitly added to the roadmap, do not turn this project into:
|
||
|
||
- a general CAD package
|
||
- a Blender replacement
|
||
- a multiplayer MMO editor
|
||
- a full node-based visual scripting environment
|
||
- a full physics sandbox
|
||
- a photoreal AAA renderer
|
||
- a React Three Fiber showcase
|
||
- an ECS research project
|
||
|
||
We may add optional scripting, plugins, collaboration, or advanced baking later.
|
||
They are not v1 priorities.
|
||
|
||
---
|
||
|
||
## Core product vocabulary
|
||
|
||
Use these terms consistently:
|
||
|
||
- **Document**: canonical editor state
|
||
- **Brush**: author-authored solid/primitive in canonical brush form
|
||
- **Face**: one editable surface of a brush
|
||
- **Material**: logical authoring material definition
|
||
- **Texture**: image resource backing material channels
|
||
- **Asset**: imported external resource, usually GLB/GLTF or audio and related media
|
||
- **Model Instance**: placed scene instance of an imported asset
|
||
- **Collider**: runtime collision representation derived from brushes or imported models
|
||
- **Prefab**: reusable asset/entity package placeable in scenes
|
||
- **Entity**: typed scene object with runtime/editor semantics
|
||
- **Project Package**: portable editable bundle containing canonical scene JSON plus referenced assets
|
||
- **Runner Package**: deployable playable output for the built-in runner
|
||
- **Runner**: browser runtime that loads and plays scenes
|
||
- **Viewport**: editor rendering surface
|
||
- **Command**: undoable state transition
|
||
- **Tool**: editor interaction mode such as select, create, transform, or face-edit
|
||
- **Build**: deterministic transformation from document -> runtime scene data
|
||
- **Export**: downstream transformation to deployable or interchange deliverables such as runner packages or optional later GLB
|
||
|
||
Avoid vague terms like “object”, “thing”, “item”, or “component” when a more precise domain term exists.
|
||
|
||
---
|
||
|
||
## Repo expectations for agents
|
||
|
||
When working in this repo:
|
||
|
||
1. Read:
|
||
- `AGENTS.md`
|
||
- `CHAT_CONTEXT.md`
|
||
- then inspect the relevant sections of `architecture.md`, `roadmap.md`, and `testing.md` for the active slice
|
||
- if a slice touches persistence, runtime, or testing boundaries in a non-obvious way, read the relevant full doc sections before changing code
|
||
|
||
2. Respect the current vertical slice.
|
||
- Do not “prepare for future flexibility” by adding unnecessary systems.
|
||
- Implement the smallest coherent version that preserves the architecture.
|
||
|
||
3. Preserve layering.
|
||
- `document` owns canonical state.
|
||
- `commands` apply valid state changes.
|
||
- `geometry` owns derived brush and collider generation.
|
||
- `viewport-three` renders editor state.
|
||
- `runtime-three` plays runtime state.
|
||
- `entities` owns typed non-brush scene objects.
|
||
- `assets` adapts external asset/audio/media formats.
|
||
- `serialization` persists canonical state.
|
||
|
||
4. Do not bypass command infrastructure for editor mutations.
|
||
- If the user can do it in the editor, it should usually be represented as a command.
|
||
|
||
5. If persisted schema changes, update compatibility explicitly.
|
||
- bump the document schema version when required
|
||
- add or update migrations
|
||
- add at least one migration or compatibility test
|
||
|
||
6. Prefer explicit typing and explicit invariants.
|
||
- Avoid permissive `any`, loose maps, or magic strings when a discriminated union or typed schema is appropriate.
|
||
|
||
7. Keep systems testable.
|
||
- Geometry generation should be testable outside the browser UI.
|
||
- Serialization should be round-trip tested.
|
||
- Runtime interactions should be testable through deterministic fixtures where possible.
|
||
|
||
8. Keep browser concerns in mind.
|
||
- Gracefully handle pointer lock failure, audio unlock requirements, missing gamepads, and asset load failures.
|
||
- Avoid architecture that assumes native desktop privileges.
|
||
|
||
---
|
||
|
||
## Code quality rules
|
||
|
||
### General
|
||
|
||
- TypeScript only
|
||
- strict typing enabled
|
||
- prefer pure functions for transforms/build steps
|
||
- isolate impure browser/three.js side effects
|
||
- no silent catch-and-ignore
|
||
- no dead feature flags without roadmap justification
|
||
- no hidden singleton globals unless explicitly part of infrastructure
|
||
|
||
### Naming
|
||
|
||
- use descriptive names
|
||
- prefer domain names over generic utility names
|
||
- avoid abbreviations unless they are standard and obvious
|
||
- function names should describe intent, not implementation detail
|
||
|
||
Good:
|
||
- `buildRuntimeSceneFromDocument`
|
||
- `applyMaterialToSelectedFaces`
|
||
- `createBoxBrushCommand`
|
||
|
||
Bad:
|
||
- `handleThing`
|
||
- `updateData`
|
||
- `doBuild`
|
||
|
||
### File organization
|
||
|
||
- small files where it helps clarity
|
||
- split by domain, not by arbitrary technical categories
|
||
- avoid giant “misc” or “utils” dumping grounds
|
||
- every folder should have a clear reason to exist
|
||
|
||
### Comments
|
||
|
||
- explain *why*, not what the code obviously does
|
||
- use comments to document invariants, constraints, and tricky browser behavior
|
||
- remove stale comments promptly
|
||
|
||
### Error handling
|
||
|
||
- fail loudly in development
|
||
- surface usable diagnostics in the editor UI
|
||
- never corrupt the document silently
|
||
- preserve previous valid state on failed builds where possible
|
||
|
||
---
|
||
|
||
## Data model rules
|
||
|
||
The canonical authoring state must remain independent from three.js scene graph objects.
|
||
|
||
### Required separation
|
||
|
||
Maintain these layers:
|
||
|
||
1. **Authoring model**
|
||
- JSON-serializable
|
||
- versioned
|
||
- typed
|
||
- stable across runtime/editor rebuilds
|
||
|
||
2. **Editor view model**
|
||
- three.js meshes, helpers, overlays, gizmos
|
||
- disposable and rebuildable
|
||
|
||
3. **Runtime model**
|
||
- play-mode scene graph
|
||
- controllers, triggers, emitters, colliders, animation mixers
|
||
|
||
Do not let editor-only helpers leak into the canonical document.
|
||
Do not store raw three.js objects inside canonical document state.
|
||
|
||
### Brush rules
|
||
|
||
- brushes are not stored as triangle soup
|
||
- face material assignments are per-face
|
||
- UV transforms are canonical editor data
|
||
- runtime mesh generation is derived data
|
||
- collision generation is derived data
|
||
- early boxes are axis-aligned and use fixed face IDs
|
||
|
||
### Entity rules
|
||
|
||
- entities must be typed
|
||
- entity schemas must be explicit
|
||
- entity defaults must be centralized
|
||
- entity validation must happen at document/build boundaries
|
||
- model instances remain separate from entities
|
||
|
||
### Imported model collision rules
|
||
|
||
- collision settings for imported models live on `modelInstances`
|
||
- supported collision modes must be explicit and typed
|
||
- generated collision geometry is derived data, not the canonical source document
|
||
- collision debug visibility is editor/runtime UI state driven by authored settings, not a hidden renderer-only toggle
|
||
- avoid implicit “always collide with render mesh” behavior
|
||
- if broad-phase/narrow-phase pruning or non-box collider support is needed, prefer Rapier over ad hoc custom collision math
|
||
- collision modes mean:
|
||
- `none` = no collider
|
||
- `terrain` = heightfield collider, static only
|
||
- `static` = triangle mesh collider, fixed only
|
||
- `dynamic` = convex decomposition into compound collider, dynamic/kinematic capable
|
||
- `simple` = one cheap primitive or one convex hull
|
||
|
||
---
|
||
|
||
## Performance rules
|
||
|
||
Performance matters, but premature micro-optimization does not.
|
||
|
||
Priorities:
|
||
|
||
1. editor responsiveness during common operations
|
||
2. deterministic rebuild behavior
|
||
3. predictable memory usage
|
||
4. runtime smoothness for modest scenes
|
||
5. export/build correctness
|
||
|
||
When optimizing:
|
||
|
||
- measure first
|
||
- optimize hotspots, not aesthetics
|
||
- document assumptions
|
||
- prefer algorithmic improvements over clever hacks
|
||
|
||
Expected hotspots:
|
||
|
||
- picking/raycasting
|
||
- brush rebuilds / CSG
|
||
- face highlighting
|
||
- large texture browser lists
|
||
- imported asset previews
|
||
- runtime trigger scanning if implemented naively
|
||
|
||
---
|
||
|
||
## UX rules
|
||
|
||
The editor should feel like a real authoring tool, not a tech demo.
|
||
|
||
Prioritize:
|
||
|
||
- fast selection
|
||
- robust snapping
|
||
- visible grid and transform feedback
|
||
- obvious active tool state
|
||
- low-friction material application
|
||
- quick play testing
|
||
- understandable errors
|
||
|
||
Every new feature should answer:
|
||
- What does the user see?
|
||
- What does the user click/drag/type?
|
||
- How is failure communicated?
|
||
- How is the action undone?
|
||
- How is the result tested?
|
||
|
||
---
|
||
|
||
## Vertical slice policy
|
||
|
||
We build in vertical slices.
|
||
Each slice must deliver a complete, usable capability across all relevant layers.
|
||
|
||
A good slice includes:
|
||
- document changes
|
||
- commands
|
||
- viewport behavior
|
||
- UI panel updates
|
||
- runner behavior if relevant
|
||
- persistence
|
||
- tests
|
||
- manual QA notes
|
||
|
||
A bad slice is “just backend structure” or “just a partial UI”.
|
||
|
||
Do not land architectural scaffolding that has no immediate use in the current slice.
|
||
|
||
If a roadmap item is too large for one pass, split it into smaller end-to-end sub-slices instead of landing half-systems.
|
||
|
||
---
|
||
|
||
## Typical slice shape
|
||
|
||
For each slice, agents should aim to deliver:
|
||
|
||
1. domain model changes
|
||
2. command(s)
|
||
3. viewport interaction/tooling
|
||
4. UI affordance
|
||
5. serialization support
|
||
6. runtime/build support if needed
|
||
7. tests
|
||
8. docs update if behavior changed materially
|
||
|
||
---
|
||
|
||
## Decision heuristics
|
||
|
||
When uncertain:
|
||
|
||
### Prefer plain three.js over extra abstraction
|
||
Unless abstraction clearly simplifies repeated patterns.
|
||
|
||
### Prefer canonical JSON over reusing runtime/export data structures
|
||
The editor’s needs are different from export/runtime needs.
|
||
|
||
### Prefer typed entity schemas over generic script bags
|
||
Especially in early versions.
|
||
|
||
### Prefer constrained capabilities that feel good over flexible capabilities that feel vague
|
||
Example:
|
||
- better to have one excellent box-brush tool than five half-working primitive tools
|
||
|
||
### Prefer immediate usability over speculative extensibility
|
||
But preserve clean seams for future extensions.
|
||
|
||
---
|
||
|
||
## What agents must not do
|
||
|
||
Do not:
|
||
|
||
- rewrite broad project structure without a strong reason
|
||
- introduce new framework dependencies casually
|
||
- add R3F because “we might want it later”
|
||
- introduce ECS because “games use ECS”
|
||
- over-generalize the entity system
|
||
- replace canonical brush data with raw mesh editing
|
||
- implement hidden magic behaviors without schema support
|
||
- remove tests to get green CI
|
||
- make visual changes without noting them in the slice summary
|
||
- ignore browser restrictions around input/audio
|
||
|
||
---
|
||
|
||
## Required deliverables in implementation responses
|
||
|
||
When making meaningful changes, include:
|
||
|
||
1. what changed
|
||
2. why it changed
|
||
3. which files were added/updated
|
||
4. how to run/test it
|
||
5. known limitations
|
||
6. follow-up suggestions only if directly relevant
|
||
|
||
If the environment prevents verification, state exactly what was and was not verified.
|
||
|
||
---
|
||
|
||
## Definition of done for a slice
|
||
|
||
A slice is done when:
|
||
|
||
- the feature can be used end-to-end
|
||
- the feature is represented in canonical document data
|
||
- the feature can be saved and loaded
|
||
- the feature is test-covered appropriately
|
||
- the feature has manual verification notes
|
||
- the feature does not violate the architecture
|
||
- obvious failures produce usable diagnostics
|
||
- undo/redo works if the feature is editor-authored
|
||
|
||
---
|
||
|
||
## Preferred stack unless changed deliberately
|
||
|
||
- TypeScript
|
||
- React
|
||
- Vite
|
||
- three.js
|
||
- Vitest
|
||
- Playwright
|
||
- ESLint
|
||
- Prettier
|
||
- a small state store if needed
|
||
- minimal dependencies overall
|
||
|
||
Add dependencies only when they clearly save time and complexity over building in-house.
|
||
|
||
---
|
||
|
||
## Final instruction to agents
|
||
|
||
Build the smallest coherent thing that feels real.
|
||
|
||
The product should always trend toward:
|
||
- spatial immediacy
|
||
- authoring clarity
|
||
- browser-native practicality
|
||
- fast iteration
|
||
- strong foundations for brushes, assets, entities, and runner behavior
|
||
|
||
If forced to choose, preserve the integrity of:
|
||
1. the canonical document model
|
||
2. the brush workflow
|
||
3. the edit -> run loop
|