# AGENTS.md

## Overview

`www-v` is a Next.js 16 monorepo. It contains a markdown document/post platform and an embedded Three.js isometric 3D engine with Ammo.js physics. It runs on a shared backend (VMAX) and publishes to VMAX.ai.

## Documentation map: READ the matching doc BEFORE editing a subsystem

The detail lives in per-subsystem docs, not here. Each doc is the **contract** for its area. Do not edit a subsystem without reading its doc first.

The harness auto-applies a directory's `AGENTS.md` when you work inside that folder (the built-in enforcement). These are the docs to open up front. Sub-`AGENTS.md` files nest. Always read the **deepest** one that owns your file.

| Read this | Covers | Before you... |
|---|---|---|
| [`www-v/app/AGENTS.md`](www-v/app/AGENTS.md) | the Next.js App Router web front door: route map, the modern-Next.js conventions this repo holds to, and how a post URL becomes a rendered page | touch any route, page, layout, or the SSR pipeline |
| ↳ [`www-v/app/-/AGENTS.md`](www-v/app/-/AGENTS.md) | the `app/-/` standalone reference and test routes (scenes, components, samples) | add or use a test/debug/reference route |
| ↳ [`www-v/app/-/islands/AGENTS.md`](www-v/app/-/islands/AGENTS.md) (the `/-/islands` catalog) + [`www-v/next-vmax/island-scenes.ts`](www-v/next-vmax/island-scenes.ts) | **The catalog of every scene the engine can build.** One standalone route + one preset row per scene (each worldType, content flag, and showcase). A preset row IS the manifest input. The reference for island builds and the place to verify one in isolation | build, change, or verify any island / structure / scene |
| [`www-v/three-isometric-engine/AGENTS.md`](www-v/three-isometric-engine/AGENTS.md) | the 3D engine: scene architecture, the ocean-ellipsoid placement rules plus cheat-sheet, mount manifest, per-frame loop, shadow scheduling, castle build, shaders, physics | touch anything under `three-isometric-engine/` |
| ↳ [`www-v/three-isometric-engine/world-entities/AGENTS.md`](www-v/three-isometric-engine/world-entities/AGENTS.md) | per-entity contracts (what each publishes/subscribes, its manifest gate, quirks) | edit or add an `Entity` |
| ↳ [`www-v/three-isometric-engine/world-meshes/AGENTS.md`](www-v/three-isometric-engine/world-meshes/AGENTS.md) | per-factory reference (every mesh/geometry factory) | edit or add a mesh factory |
| ↳ [`www-v/three-isometric-engine/agent-simulation/AGENTS.md`](www-v/three-isometric-engine/agent-simulation/AGENTS.md) | scenery/geometry that survived the agent-runtime removal: flag cloth, boat-placement geometry (water-sdf, offshore band), c-space grid | touch flag cloth, boat placement, or wayfinding geometry |
| [`www-v/next-vmax/AGENTS.md`](www-v/next-vmax/AGENTS.md) | per-component invariants of the `next-vmax` library not covered by its skill doc | edit `next-vmax/` |
| [`www-v/next-vmax-tools/AGENTS.md`](www-v/next-vmax-tools/AGENTS.md) | tools-shell invariants not covered by its skill doc | edit `next-vmax-tools/` |
| [`www-v/document-system-common/AGENTS.md`](www-v/document-system-common/AGENTS.md) | shared non-React document layer (constants, utils, shader-context) | edit `document-system-common/` |
| [`www-v/document-system-components/AGENTS.md`](www-v/document-system-components/AGENTS.md) | markdown-post React UI plus the six overlay shaders | edit `document-system-components/` |
| [`www-v/next-slate-markdown/AGENTS.md`](www-v/next-slate-markdown/AGENTS.md) | Slate.js editor, API client, public-page chrome | edit `next-slate-markdown/` |
| [`www-v/public/SKILL.md`](www-v/public/SKILL.md) | VMAX.ai posting API, Slate JSON serialization, common-mistakes checklist | run any posting workflow |
| [`www-v/ALGORITHMS.md`](www-v/ALGORITHMS.md) | catalogue of every algorithm (noise, terrain, SPH/cloth, shaders, and more) with code demos. **Jump to its Table of Contents section; do not read end-to-end** (116 KB) | implement or modify an algorithm |
| [`www-v/skills/`](www-v/skills) `*/SKILL.md` | task workflows (new island/structure/agent, shader and TypeScript perf discipline, LaTeX to VMAX) | invoke that workflow. Read the one skill fully first |

## Top-level directories

| Directory | Purpose |
|---|---|
| `app/` | Next.js App Router pages (home, profiles, posts, admin) plus the `app/-/` reference/test routes. See [`www-v/app/AGENTS.md`](www-v/app/AGENTS.md). |
| `common/` | Repo-wide shared utilities. `derive-world-mode.ts` (the canonical scene-mode plus world-type rules), `theme-colors.ts` (CSS variable to THREE.Color helpers), `attach-theme-observer.ts` (rAF-coalesced body MutationObserver for theme swaps), `scene-modal-bridge.ts` (`requestSceneModal(key)` — the window-event bridge a Three.js scene element fires to open a React modal it cannot reach directly), `position.ts`, `utilities.ts` (`setTheme`, `toggleFontClassName`), `navigation-links.ts`, `home-post-descriptions.ts` |
| `components/` | Top-level React components: page chrome (`DocumentLayout`, `DocumentHeading`, `VmaxAppNavigation`, `VmaxAppDocument`, `VmaxAppIsometricBackdrop`, `VmaxAppPaperPostItem`), dropdowns (`DropdownMenu`, `DropdownMenuTrigger`), the modal system (`ModalContext`, `ModalTrigger`, `OutsideElementEvent`, `ModalRenderer`, `SceneModalBridge` — the last two mounted once in `Providers`; the overlay is click-through so a Three.js scene behind stays interactive, and `SceneModalBridge` opens a registered modal when a scene element fires the `scene-modal-bridge` event, deduped), `LoginModalLauncher` (shows `ModalAuthentication` by default on `/login`), tags (`AvatarSocialTag`, `AffiliationSquare`), `ForestCacheWarmer` (idle-time forest template bake), `PasswordProtectedPost`, `ActionListItem`, `DefaultMetaTags`, `Providers` |
| `next-vmax/` | The application-component library for `app/-/` routes: the app shell (`DefaultLayout`), windows (`ApplicationWindow`, `WebTerminalWindow`), buttons (`GlassButton`, `ButtonAction`, `ButtonActionGlass`) and the `ButtonLoader` spinner (used by the tools template), navigation (`NavigationTab`, `NavigationDropDownButton`), `CodeBlock`/`InlineCode`, `CylinderOdometer`, `Logo`, `Tooltip`, `Icon`, `Checkbox`, `ScrollSpacer`, `FormTypography`, `ModalAuthentication` (the non-dismissable `/login` Google-auth gate), `IslandScene` plus `island-scenes.ts` (the `/-/islands` preset catalog), and `graphs/` (reference data for all 15 graph types). Per-component contracts: [`www-v/skills/create-old-vmax-application-ui/SKILL.md`](www-v/skills/create-old-vmax-application-ui/SKILL.md) (deprecated, but the reference for this library). |
| `next-vmax-tools/` | The `/-/vmax-tools-template` tools shell and its `Form*` body kit (alias `@nextjs-vmax-tools/*`) — a reference app to clone into a CSS-perfect replica for any internal tool. `AppContainer` is the shell; `tools-pages.tsx` is the `TOOLS_PAGES` registry both routes read (the way `island-scenes.ts` is for the island routes). The shell, the registry, the `?columns=N` behaviour, and every `Form*` primitive are documented in [`www-v/skills/create-vmax-tools-page/SKILL.md`](www-v/skills/create-vmax-tools-page/SKILL.md). |
| `document-system-common/` | Shared non-React layer for the document platform. Constants, utilities, `shader-context` (shared WebGL2 manager). See [`www-v/document-system-common/AGENTS.md`](www-v/document-system-common/AGENTS.md). |
| `document-system-components/` | React UI for markdown posts. `Document`, `DocumentLayout`, `LatexBlock`, `MermaidGraph`, `BlockImage`, the six overlay shaders, `Providers`. See [`www-v/document-system-components/AGENTS.md`](www-v/document-system-components/AGENTS.md). |
| `next-slate-markdown/` | Slate.js editor plus API client plus public-page chrome. Posts persist as markdown. See [`www-v/next-slate-markdown/AGENTS.md`](www-v/next-slate-markdown/AGENTS.md). |
| `three-isometric-engine/` | Three.js 3D engine with physics, shaders, scenes (CTF mode, wander, island variants). See [`www-v/three-isometric-engine/AGENTS.md`](www-v/three-isometric-engine/AGENTS.md). See [`www-v/skills/fast-threejs-shaders-check/SKILL.md`](www-v/skills/fast-threejs-shaders-check/SKILL.md) for the GPU and GLSL performance discipline. |
| `modules/` | Tiny shared modules (`image-extensions`, `video-extensions`) |
| `pages/` | Legacy Pages Router (one endpoint remains: `oauth.tsx`) |
| `scripts/` | Build/utility scripts (run via `npm run script`) |
| `public/` | Static assets including `SKILL.md` (the agent posting guide, mirrored at `/skill` and `https://vmax.ai/SKILLS`) |
| `skills/` | Project-local agent skills |

### Cross-file invariants in `common/` and `components/`

- `Providers` seeds `activeConfigs` WITHOUT reading `window.matchMedia` in the `useState` initializer so the server and client produce the same initial render; the mount `useEffect` reconciles the real `prefers-color-scheme`. Do not "fix" the first-paint theme by moving the media query into the initializer.
- `attachThemeObserver` stores its MutationObserver on `mesh.userData.themeObserver` — the key name is a contract with the engine's unmount scan (`three-isometric-engine/common/three/scene-teardown.ts` disconnects observers found under that key); renaming the key leaks observers on teardown.
- `NavigationMenuItem.themeAction` is a serializable stand-in for `onClick`: server components pass menu items across the RSC boundary (`app/login/page.tsx` → `IslandScene`) where a function would not serialize, so theme items carry this token and the client-side dropdown assemblers map it to `Utilities.setTheme` via `resolveNavigationItems`. New action-like items must follow the same token pattern, not a callback.
- `home-post-descriptions.ts` resolves each blurb from `PUBLISHED_POSTS` by HREF, never by list index: `PUBLISHED_POSTS` is ordered newest-first while the engine (`GLComponent`) owns the fixed `post_number` → tag mapping (1 → Populora, 2 → UNIX CTF, 3 → PROPEL), so index-based keying would mismatch Populora and PROPEL. The keys are local post_numbers, not real post UUIDs, so copy edits never touch an id; the home page and `/-/islands/whole-world` both mount the scene from this one source.
- `getCSSVariableColor` (`theme-colors.ts`) resolves a CSS variable through a temporary DOM element and `getComputedStyle(...).color` so oklch, hsl, and rgb variable values all normalise to the `rgb(...)` string form; reading the raw variable text instead would hand `THREE.Color` an oklch string it cannot parse.
- `theme-*` and `font-*` body classes are independent axes: `setTheme` swaps only the theme class (a wholesale `body.className` assignment previously wiped the active font class — a fixed regression), and `toggleFontClassName` keeps `font-*` classes mutually exclusive (clearing any other before applying; off restores the default `--font-family` variables).
- `ForestCacheWarmer` must stay mounted in the root `app/layout.tsx` — that is what warms the forest-template cache on EVERY entry path, including direct external navigation straight to `/[username]/[slug]`, so `createNeutralForest()` skips the bake by the time a grass-world post mounts. The warmup is idempotent (short-circuits when the cache is warm), so double mounts and a second call from `GLComponent` are free.
- Outside-click dismissal works THROUGH the modal system's click-through overlay because `OutsideElementEvent` listens on `document`, not on the overlay element — do not reintroduce an overlay `onClick` (it would never fire under `pointer-events: none`) or re-enable pointer events on the overlay to "fix" dismissal.

## Cross-cutting rules

### Change discipline

- Treat the files you read in the current run as the evidence base. If the evidence does not identify a requirement, state the assumption or ask before changing behavior.
- Preserve existing contracts before chasing a local fix. When behavior changes, update the canonical source, the meaningful assertion or verification path, and any affected agent-facing docs together.
- Expand through the repo's extension points: registries, manifests, entities, shared predicates, helpers, and skills. Do not hard-code one-off branches that future features cannot reuse.

### Node and scripts

Node `>=18` (see `package.json`). Test runner is vitest.

| Command | Purpose |
|---|---|
| `npm run dev` | Dev server on port 10003 |
| `npm run build` | Production build |
| `npm start` | Production server on port 10003 |
| `npm test` | Run tests once |
| `npm run test:watch` | Test watch mode |
| `npm run script` | Run one-off `ts-node` scripts in `scripts/` |

### TypeScript

The codebase is pure TypeScript (`strict: true`, `strictNullChecks: true`, `noUncheckedIndexedAccess: true`, `verbatimModuleSyntax: true`, `target: "es2022"`). The only `.js` files are `three-isometric-engine/vendor/ammo.js` (vendored WASM wrapper with a `.d.ts` stub) and `scripts/strip-glb.js` (the dependency-free GLB scrubber).

- **No `any` at boundaries.** Exported functions, class constructors, and pub/sub channels have typed parameters. Interior logic uses inference.
- **`as const`** on frozen constant objects preserves literal types.
- **`import type`** for type-only imports (enforced by `verbatimModuleSyntax`).
- **Unused parameters** are prefixed with `_` (enforced by `noUnusedParameters`).
- **Private members** use a `__private` prefix with double underscore (`this.__privateHeatField`, not `this._heatField`). Module-scoped private variables use the same convention (`__privateCameraTarget`, not `_cameraTarget`). The `_` prefix is reserved exclusively for unused parameters. See [`www-v/skills/fast-typescript-check/SKILL.md`](www-v/skills/fast-typescript-check/SKILL.md) for the full naming and performance discipline.
- **No `Map` in hot paths.** Use plain objects with string keys and `Record<string, T>` typing. `Map` is permitted only when keys are object references (identity-based lookup).
- **`typeof` checks** against globals (`document`, `window`, `performance`) are hoisted to module-level constants (`const HAS_WINDOW = typeof window !== 'undefined'`).
- **Barrel exports** exist at each engine subsystem root (`agent-simulation/index.ts`, `world-builder/index.ts`, `world-meshes/index.ts`, `world-entities/index.ts`, `physics/index.ts`, `common/index.ts`). Prefer importing from the barrel over deep internal paths.
- **Non-null assertions (`!`)** are used in hot-path engine loops where array indices are bounded by the loop condition. Do not add runtime null guards to these paths.
- **Root `types.d.ts`** globally re-declares Next.js's generated `LayoutProps` (loose `params: Promise<any>` plus a `Record<string, React.ReactNode>` intersection) to work around a Next.js 16 type-generation constraint bug; it looks redundant but the build's generated layout types fail to compile without it — remove only after confirming a Next.js upgrade fixed the generator.

### File naming convention

File names use **hyphen-case** (`shader-context.ts`, `create-palisade-tower.ts`), with two exceptions that use **PascalCase**: React components (`AvatarSocialTag.tsx`, `DefaultLayout.tsx`) and class-per-file modules whose primary export is a single class (`WorldContext.ts`, `GLComponent.ts`, `TeamRegistry.ts`, the `*Entity.ts` files — verified: every PascalCase non-React file in the tree exports a class). camelCase file names are not permitted.

### No comments — knowledge lives in AGENTS.md

**Comments are banned everywhere in this repository.** No `//` line comments, no `/* */` block comments, no `NOTE(@handle)` annotations — in TypeScript, TSX, GLSL, CSS, tests, configs, and scripts alike. Identifier names carry the meaning (see "Identifier naming" below); if code reads unclearly, expand the name or restructure the code — never annotate it. This is permanent policy, not a per-PR preference.

The only permitted exceptions are machine-read directives where technically required: `@ts-expect-error`, `@ts-ignore`, `eslint-disable`, `/// <reference>`. Nothing else survives review.

Knowledge that would once have been a comment — a constraint, a tradeoff, a non-obvious ordering, a workaround, a gotcha — belongs in the deepest `AGENTS.md` that owns the file, as a note that cites the exact function/class/symbol name so a reader can find it from the code (`grep` the symbol, read the doc). If a fact is not worth an AGENTS.md note, it is not worth keeping at all.

The `app/-/islands/AGENTS.md` and `world-entities/AGENTS.md` rewrites are the reference for what a good note looks like: every claim traces to live code, names match the source exactly, and nothing is restated that a reader could infer. Match that bar everywhere.

### Identifier naming: spell names out in full

Names carry the meaning, so they pay for the comment you then do not write. Spell every local variable and parameter out in full — `context` not `ctx`, `deltaX`/`deltaZ` not `dx`/`dz`, `terrainData` not `td`, `event` not `e`, `element` not `el`, `callback` not `cb`, `index` not `i` (and `innerIndex`/a domain name for nested loops). The reader should never decode an abbreviation. The only short names that stay are domain-canonical ones where the full word adds nothing: vector components `x`/`y`/`z` and UV coordinates `u`/`v`.

This rule governs names you own — locals and parameters. It does NOT license churn across boundaries: object/class properties and methods, exported symbols, type and interface fields, JSON manifest keys, pub/sub channel names, React props, THREE `userData` keys, GLSL identifiers (`iTime`, `iFloorBounds`, `uColor`, `vUv`), and CSS variables are contracts — renaming one is an API change, not a readability cleanup, and is out of scope here. Keep the `__private` double-underscore prefix on private members (see TypeScript above).

**Deliberate exceptions (do NOT "fix" these back to longer names).** The codebase was swept to this convention; what stayed short stayed on purpose:
- **Domain-canonical single letters:** `x`/`y`/`z` (coords), `u`/`v` (UV), and `r`/`g`/`b`/`a` ONLY as colour channels. A single letter that is a coordinate, a colour channel, or a generic type parameter (`<T>`) is NOT a loop counter — leave it.
- **`props` and `params`** are kept as canonical React/JS idioms (a maintainer decision), even though they are locals/params.
- **Not variables at all** (so the rule does not apply): `vi.fn()` (the Vitest mock API), CSS `calc(...)` inside style strings, the React/DOM `src` attribute and prop, every other string literal, and any identifier living inside a GLSL/shader template literal (`pos`, `dir`, `mod289`, … inside shader source stay — shaders are excluded wholesale).
- **Type-position contracts:** a function-TYPE signature's parameter name (`decode?: (str: string) => string`) is part of a declared shape, not a binding you own — leave it.

### Brand name casing

Company branding writes the name **Vmax** — capital `V`, lowercase `max`, treated as one word — in prose, UI copy, comments, AND in PascalCase React component names and their file names (`VmaxAppDocument`, `VmaxAppNavigation`, `VmaxAppIsometricBackdrop`, `VmaxAppPaperPostItem`, `Icon.VmaxLogo`). Do NOT write `VMax` (capital `M`) or `VMAX…` inside a mixed-case identifier.

The all-caps **`VMAX`** form survives only where the whole token is uppercase or it is a product/wordmark reference, and stays as-is:

- Product / wordmark / domain in prose and metadata: the `VMAX` backend, `VMAX.ai` / `vmax.ai`, the wordmark SVG.
- `SCREAMING_SNAKE_CASE` constants (`VMAX_SVG_PATHS`, `VMAX_SVG_VIEWBOX`) and data tokens (the `{ VMAX: 'vmax' }` affiliation key) — matching the uppercase-constant convention, not a brand-casing choice.
- The lowercase `next-vmax*` folders and `@nextjs-vmax*` aliases.

### Code correctness > tests passing

**The test suite was deliberately RESET** (every `*.test.ts`/`*.test.tsx` deleted; `vitest.config.ts` carries `passWithNoTests` while it is rebuilt). A "pinned by `X.test.ts`" reference in any doc below therefore names an invariant AWAITING re-pinning in the rebuilt suite, not a file in the tree — treat those references as the rebuild checklist. The rules in this section govern the new suite.

**A green `npm test` is not a signal that a feature works.** It only signals that the asserted contracts hold under the harness. Visual correctness, physics plausibility, emergent behavior, cross-subsystem interaction, and "does this look right on a real browser" are out of scope for vitest.

Before reporting a feature complete:

1. For UI / 3D / shader / physics work: run `npm run dev`, load the scene, **look at the result**. A passing test suite with a broken visual is a false green.
2. Prefer asserting invariants (mass conservation, bounded spread, byte-identical non-mutation) over asserting that a function was called. "Runs without throwing" is not a test.
3. When a spec's test matrix asks for a behavioral assertion (e.g., "lava stays in the crater while h < rim") and you weaken it to an API-shape assertion ("height field is the right length"), say so explicitly. Do not let the weaker test stand in for the stronger one.

**Agents (including Claude) are demonstrably weaker at writing tests than at writing implementation code.** Common failure modes:

- Asserting on intermediate state instead of observable behavior.
- Using synthetic inputs that bypass the real bug class.
- Declaring success based on "tests pass" without running the feature.

When delegating test-writing to an agent, check the tests against the spec's intent, not just the spec's wording. When Claude writes a test that passes on the first run without iteration, treat that as a yellow flag. Real tests usually fail once before being right.

**Do not write tests the type system already proves.** A `strict` compiler with `noUncheckedIndexedAccess` is the first test suite; a green `tsc` already guarantees what a runtime assertion would re-check. Delete (and do not add) tests whose only claim is a tautology or a fact the types enforce:

- Default field values of a freshly constructed object (`new Mode()` has empty arrays) — the field declarations say so.
- Math identities independent of the code under test (`ceil(sqrt(n))² ≥ n`) — that tests the standard library, not us.
- Shape/length/"has property X"/"returns an array" assertions on a typed return — the signature already holds the line.

Keep every test that asserts *behavior the types cannot*: invariants (mass conservation, bounded spread, byte-identical non-mutation), gating and ordering logic, geometry/placement correctness, and regressions. When unsure whether a test is type-covered, keep it — the bar for deletion is "the compiler already fails if this is false."

### Debug visibility rules (apply to every subsystem)

These are three cross-cutting rules. The full contract lives in [`www-v/three-isometric-engine/AGENTS.md`](www-v/three-isometric-engine/AGENTS.md) under "Barrier visibility invariant" and "One-classification-of-truth invariant". This is the summary for contributors working in any directory.

1. **Every barrier renders red.** Hard barriers (physics bodies, planner stamps, predicate gates) AND soft cost-based barriers (RVO penalties, halos, taxes that dominate scoring) MUST draw a red debug primitive. If you cannot draw one, do not stamp it as a barrier. Colour contract:
   - red (`0xff3030`) = physical ground/body barrier
   - blue (`0x3060ff`) = land-agent action/combat circle
   - yellow (`0xfff066`) = wayfinding destination / stake marker
   - green = wayfinding line
2. **Visible matches math.** Every debug overlay's geometry derives from the EXACT constants the runtime predicate uses. No visual-only scale factors, no fudges. The visible IS the gate. Document any clamp / projection in the owning `AGENTS.md`, citing the rendering function.
3. **One classification of truth.** When the same physical concept (sailability, walkability, flammability) is represented in multiple data structures, every representation MUST agree. Multi-class enums NEVER collapse with `> 0` shortcuts. Every consumer lists every class explicitly or imports a canonical predicate next to the source data.

### Path aliases (`tsconfig.json`)

- `@root/*` to `./*`
- `@common/*` to `./common/*`
- `@components/*` to `./components/*`
- `@modules/*` to `./modules/*`
- `@pages/*` to `./pages/*`
- `@engine/*` to `./three-isometric-engine/*`
- `@document-system-common/*` to `./document-system-common/*`
- `@document-system-components/*` to `./document-system-components/*`
- `@nextjs-slate/*` to `./next-slate-markdown/*`
- `@nextjs-vmax/*` to `./next-vmax/*`
- `@nextjs-vmax-tools/*` to `./next-vmax-tools/*`

### Sub-AGENTS files apply hierarchically

The harness auto-applies a sub-AGENTS.md file when an agent is working inside that directory, and they **nest**:

- The four top-level sub-files (`document-system-common`, `document-system-components`, `next-slate-markdown`, `three-isometric-engine`) each own their subsystem in full.
- `three-isometric-engine/` nests further. `three-isometric-engine/world-entities/AGENTS.md` (per-entity contracts) and `three-isometric-engine/world-meshes/AGENTS.md` (per-factory reference) hold the detail that used to bloat the engine root.

**Do not duplicate sub-file content into a parent.** Reference the deepest file that owns the detail.

## Engine data flow

### Entity data-flow diagram

```
Document (Slate.js markdown)
  ↓ deriveWorldMode / deriveWorldType
World Manifest (JSON)
  ↓ mount-world.ts (entity registry + DAG scheduler)
Entities (build → idle bake → update loop)
  ├── OceanBoardEntity → dark + light boards, wake drops
  ├── StarfieldEntity → dark-mode-only full-screen Star Nest sky behind the world
  ├── IslandTerrainEntity → publishLandMass, publishFloorDist
  ├── CastleEntity → publishLandmarks, publishZones
  ├── WalkabilityEntity → publishWalkableOracle
  ├── ForestEntity → publishForestLeaves, onHeatField
  ├── FleetEntity → static offshore rowboat fleet (crews wave in CTF) → publishDockBounds
  ├── GrassBladesEntity → onHeatField, onFloorDist
  ├── VolcanoEntity → publishVolcanoGeometry
  ├── MarbleLavaSystem → publishHeatField
  ├── SmokeFieldEntity → onHeatField, onForestLeaves
  ├── LighthouseFlagEntity → waving flag on each dock lighthouse, claimable by colour
  ├── PalisadeTowerEntity → centre-island watchtower + road; optional coastal dock + recruitment pole
  ├── MotteBaileyCastleEntity → home-page ringwork castle hub; razed by the hwacha
  ├── ShowcaseEntity → mounts one distant-island feature on the centre island for a /-/islands route
  ├── RocketLauncherEntity → "PROPEL" mode: hwacha + village + coastal trebuchet row
  ├── CrystalAgentsEntity → "CRYSTAL" mode: chessboard plinth showcasing the crystal-agent rig
  ├── DistantIslandsEntity → home-page decorative archipelago; every island wraps to the ocean curve; owns iFloorDistB–K
  └── AirshipEntity → propeller galleon (`home` / `showcase` missions): slung block, drop, land
```

Each entity's full contract, and the catalog of scene variants, lives in [`www-v/three-isometric-engine/world-entities/AGENTS.md`](www-v/three-isometric-engine/world-entities/AGENTS.md) and the `/-/islands` presets. The one-liners above are an index, not the spec.

### How mounting and data flow work

- `mount-world.ts` uses an idle DAG scheduler. Entities declare `deps` in the manifest JSON. The scheduler launches all entries whose deps are satisfied in parallel instead of chaining linearly. Entries with no deps start immediately.
- Cross-entity data flows through `WorldContext` publish/subscribe pairs that replay on subscribe.
- Adding a new scene mode is a new JSON manifest plus a new factory entry, with zero edits inside `firstRender`.

### Scene composition is shared

`common/derive-world-mode.ts` is the single source of truth for scene mode plus world type.

- `deriveWorldMode(events)` returns `{ mode: 'ctf' | 'wander' | 'island', isCTF }` from the byline `EVENT` column.
- `deriveWorldType({ imageCount, isDesert })` returns `'ICE' | 'GRASS' | 'DESERT'`.
- `WorldModeResult.isCTF` is a legacy boolean kept so readers can migrate to `mode === 'ctf'` incrementally; new code reads `mode`, not `isCTF`.
- In `deriveWorldMode`, CTF deliberately takes precedence over wander (`!isCTF && WANDER_RE.test(...)`): a post tagged `CTF | Playtest` can contain a stray "wander" in prose, and the agent system must win. Keep the ordering.

SSR preload (`app/[username]/[slug]/(document)/page.tsx`), the document renderer (`document-system-components/Document.tsx`), and `GLComponent` all read from this one file.

## Publishing to VMAX.ai

Before any posting workflow, read [`www-v/public/SKILL.md`](www-v/public/SKILL.md). It contains the full API reference, Slate JSON serialization rules, and a common-mistakes checklist.

- Posts persist as markdown text (`SAVED_AS_MARKDOWN: true`, post type `NEXT_SLATE_WORLD_MARKDOWN`).
- `::isometric(0)` is auto-prepended by the slug page template.
- `source` field is `'vmax.ai'`.
- Presigned-URL upload domain is `vmax.ai`.

## Agent discovery routes

- `/agents` serves this file as `text/markdown`
- `/skill` serves `public/SKILL.md` (API posting guide)
- `/skill/convert` serves `skills/tex-or-pdf-to-vmax-markdown/SKILL.md` (LaTeX/PDF/markdown to VMAX conversion guide)
- `/skill/template` serves `skills/create-vmax-tools-page/SKILL.md` (the tools-page building guide)
- `/llms.txt` is a machine-readable summary
- `/sitemap.xml` includes `/agents`, `/skill`, `/skill/convert`, `/skill/template`, `/llms.txt`
- `/robots.txt` allows all paths except `/api/`

The home page (`/`) and `/admin` link to these routes from the AGENTS.md dropdown / sidebar.
