# FMVisual framework manual

The illustrated field manual lives at `/framework.html`. Start there for reading
paths, rendering concepts, an executable tutorial, the full world/runtime/settings
reference, troubleshooting and a glossary. The separate `/methods.html` library
explains 25 techniques through illustrations, costs, limits and things to try.

FMVisual is a local ES module framework pinned to Three.js r186. It is not a
published npm package or an independently implemented voxel renderer. The current
WebGPU backend integrates Three.js VXGI, TRAA and optional effects. The WebGL
backend is an explicitly labelled direct-light preview. Original architecture,
procedural surface authoring, runtime coordination, navigation and spatial budget
policies belong to FMVisual.

## Run the complete first-room example

```sh
cd ~/FMVisual
npm ci
make app PORT=4173
# http://127.0.0.1:4173/examples/first-scene.html
```

The complete source is [first-scene.js](../src/examples/first-scene.js) and
[first-app.js](../src/examples/first-app.js). The manual imports those exact files
as text, avoiding a second maintained copy. Both development and production builds
include the example. No models, images, credentials or paid infrastructure are needed.

The example constructs eight boxes from one shared unit geometry. The left wall
is red, two roof pieces leave a sunlit slot, and neutral receivers reveal diffuse
transfer. Compare Direct and Combined at the same camera. On WebGL fallback,
indirect comparisons are disabled and the actual backend is labelled.

Suggested changes, one at a time:

1. Change the red material to blue. Compare shaded receivers with the same camera.
2. Widen the roof opening. Observe direct illumination separately from bounce light.
3. Make a wall thinner relative to voxel spacing. Compare opacity and lighting quality.
4. Use Stop & dispose, then reload. Understand resource ownership before building a larger world.

## The public entry point

Import from `src/framework/index.js`. It exports:

- `createVisualization`, `createSceneRegistry`, `assertWorld`.
- `BACKEND_VERSION`, `assertBackend`, `createDirectBackend`, `createVXGIBackend`.
- `createCameraRail`, `cameraStep`.
- `createAtmosphere`, `createPoolReflection`.
- `createWalkController`, `createWalkMotion`, `createWalkBoundary`.

Spatial helpers `resolveVolumeQuality` and `fitSunShadow` are available from their
own `quality.js` and `shadows.js` modules. `nearestRailPhase` lives in `camera.js`.
The framework entry has no dependency on lab scenes, controls or settings.

## World contract and ownership

`createSceneRegistry({id: factory})` returns a frozen registry with `ids` and
`create(id)`. Unknown IDs and malformed required fields throw. Every factory call
must return fresh owned resources:

| Field | Contract |
| --- | --- |
| `group` | Required Object3D, normally a Group. Contains the scene's meshes. |
| `camera`, `target` | Required finite three-number arrays, distinct eye / look-at positions. |
| `dispose()` | Required. Release owned geometries, materials and textures. Shared resources are released once, not per mesh. |
| `bounds` | Optional Box3. Inferred once from the group if absent. Must be nonempty and cover the world for valid spatial budgets. |
| `title`, `description` | Optional readable strings exposed by the runtime. |
| `cameras` | Optional map of IDs to `{position, target}` triples. Unknown camera IDs fall back to the default eye and target. |
| `tour` | Optional `{positions, targets}` with matching arrays of at least four triples. Closed centripetal curve, position arc-length mapped; inspect full route clearance. |
| `lighting` | Optional `{sun, ambient, sky, background, staticShadows}`. Sun intensity defaults to 5, ambient to .32 and background to #b6c1c1. Sky is opt-in. |
| `reflectivePool` | Optional `{width, length, position}` for one horizontal reflection plane on WebGPU. |
| `voxelReferenceSpan` | Optional positive baseline longest-axis span. Preserve lighting spacing during expansion and opt into fitted sun-shadow coverage. |

Metres are an authoring convention; the framework does not enforce physical units.
The convention is +Y up. Bounds are not automatically recomputed every frame.
`staticShadows: true` is valid only while the relevant geometry stays static.
The host does not yet expose a qualified general dynamic-geometry invalidation API.

The scene owns its geometry/materials/textures. The runtime owns renderer, camera,
controls, sun, ambient, sky, resize observer, loop and optional pool. The backend
owns its passes, voxel resources, temporal history and optional atmosphere/bloom.
Removing meshes from a group does not dispose their GPU resources. Material disposal
does not release textures owned by that material's author.

## Host contract

```js
const app = await createVisualization({
  container, settings, scenes, qualities,
  createBackend, // optional synchronous adapter factory
  onFrame       // optional event callback
});
```

Give `container` nonzero dimensions. Quality map entries contain integer
`resolution` and `cones`. The tutorial supplies a complete settings object.
The reusable runtime does not apply the lab validator. In particular, the lab's
`settings()` rejects custom scene IDs; a custom host must validate its own registry,
quality keys, camera IDs, numeric limits and effect flags.

`app.update(nextSettings)` replaces the full settings object, not a patch:

```js
current = { ...current, exposure: 1.2 };
app.update(current);
```

Returned methods and readable properties:

- `resetCamera()` returns to the authored preset and clears history.
- `dispose()` is idempotent; removes the canvas and releases owned resources.
- `gpu` reflects the actual renderer backend. Custom adapter capabilities still matter.
- `cameraState` exposes position, target, availablePresets, motion, tourAvailable,
  navigation, pointerLocked and dragLookFallback.
- `sceneMetrics` returns a copy of optional scene-provided assembly metrics (blocks, triangles, batches and CPU geometry attribute bytes), or null. It excludes separate distant context and does not measure GPU time or total memory.
- `qualityState` exposes resolved volume and shadow metadata. It is not a GPU memory measurement.
- `features` reports built-in atmosphere/reflection flags and reflection availability.
- `title`, `description` reflect current world metadata.

`onFrame` is an event union, not a fixed record:

- `{fps, frameMs, gpu}` every 30 frames: wall-clock frame intervals, not GPU timestamps.
- `{error}` when synchronous frame rendering fails.
- `{motion: 'still'}` when user interaction takes over from a tour.
- `{navigation: {enabled, locked, dragFallback}}` when navigation state changes.

Initialization can invoke the callback before `await createVisualization(...)`
returns. Do not assume a runtime variable is assigned inside that callback. Inspect
the event's fields rather than expecting FPS on every event.

## Backend contract

A host may provide `createBackend(context)`. Context includes
`renderer, scene, camera, settings, qualities, sun, volumeQuality`.
`volumeQuality(id)` resolves the actual density-preserving grid.

Version 1 requires `render()`, `update(nextSettings)`, `resetHistory()` and
`dispose()`, plus boolean `capabilities.indirect`. Built-in adapters also provide
`name` and `capabilities.temporal`. This is a local lifecycle seam, not a stable
external plugin ABI or six independent GPU algorithm interfaces.

The built-in VXGI pipeline uses depth, packed normals and velocity; upstream
occupancy/injection/directional hierarchy/cone integration; material lighting;
optional shadowed air; optional TRAA; and restrained bloom. Debug views bypass
final temporal composition and use stationary cones. The pool uses a separate
clipped reflection camera. Direct fallback supplies no voxel GI.

Scene changes release the previous backend, reflection and world, then create a
fresh world/backend. Quality changes reconstruct passes. Output and relevant light
changes invalidate history through TRAA's public sizing API. Camera cuts explicitly
reset history. Dedicated ghosting, device-loss, GPU-memory and timestamp
qualification remain tracked work, not implied by passing a build.

## Settings reference

See the HTML manual for the complete readable table. Lab-supported numeric ranges
are sun 0–360 degrees, elevation 8–85 degrees, exposure .3–2.5, indirect 0–3,
aperture 15–80 degrees and haze 0–1. Cached bounces are 0/1/2. Effects use booleans.
Views are combined/direct/gi/ao/radiance/opacity. Motion is still/tour; navigation
is walk/orbit. A host omitting navigation keeps Orbit; the lab selects Walk.

Assigning `study: 'ember'` selects the sun colour but does not apply all numeric
preset values. The lab's study buttons merge the complete authored preset. Tour
needs a world rail; reflection needs a declared pool and WebGPU. The runtime's Walk
supports optional authored planar containment in the hillside sanctuary. Reference
scenes remain free; gravity, floor following and stair stepping are not implemented.

## Growth, troubleshooting and verification

With reference span S₀ and preset R₀, resolve actual resolution as
`ceil(R₀ * max(1, S/S₀))`. Without a reference span it remains R₀. Voxel spacing is
S/R; actual allocated axes include padding and mip alignment. At fixed spacing,
doubling all three dimensions costs approximately eight times the base cells.
Base cells are not total GPU bytes. Longest-axis requests above 256 and fitted
shadow dimensions above 4096 are rejected. These are caps, not universal capacity.

For failures, freeze the camera, disable bloom/air and keep the light fixed. Check
actual backend and startup errors first, then direct shading, opacity/coverage,
radiance, irradiance and Combined. A coarse grid cannot represent every thin wall;
exposure cannot repair missing bounce light; temporal filtering cannot repair an
incorrect transport model. Save the failing camera and settings before changing
several variables.

`make check` runs unit contracts and the production build. `npm run test:browser`
commissions the lab on actual WebGPU and forced fallback. `npm run test:manual`
serves the built output and checks the manual/library navigation and working example
on both paths. Captures and reports live under ignored `artifacts/`.

For a world that feels continuous without empty edges, see
[WORLD-BOUNDARIES.md](WORLD-BOUNDARIES.md). The implementation is tracked as FMV-28;
lighting zones are FMV-25. Existing connected architecture is still one loaded world.

## Bounded worlds and distant context

The Hillside sanctuary adds optional `world.navigation` (areas, obstacles, body
radius and name), `world.backdrop` (separate context Object3D) and `world.viewDistance`.
The runtime includes scenery layer 2 in the camera while the upstream voxel volume
uses layer 0 and explicit nearby bounds. Dispose backdrop resources with the world.
The lab adapter handles both roots. No general streaming system is implied.

`createWalkBoundary` provides `contains`, `project`, `move` and area naming. Areas
have minX/maxX/minZ/maxZ and optional minY/maxY eye-height ranges. Rectangles are
inset by body radius; connected floors need sufficient authored overlap. Obstacles
use x/z/radius or rectangle bounds, with optional height ranges. The controller
accepts `constrainPosition(position, displacement)` to apply swept planar movement.
See [WORLD-BOUNDARIES.md](WORLD-BOUNDARIES.md) for the contract and its limits.

`cameraState` now reports bounded, boundaryName, area and withinBoundary.
`qualityState.transport` exposes actual upstream volume bounds, layers, requested
resolution and spacing after rendering. `qualityState.resources` reports geometry
and texture counts plus rendererTrackedBytes from Three.js allocation accounting;
these counters are not a complete GPU-memory measurement.
