Epic 5 Phase 0 — actors have bodies; the stage has depth

ActorGenerator is the cast with bodies: generateActor{mpl} takes
{summary,rng,archetype,personality,identity} and returns a serialisable
ActorSpec — same audio-tilts-centre / seed-picks-within rule as
Personality/Identity, forked rng so adding an actor never shifts later
decisions. Five archetypes (monolith/swarm/walker/vehicle/structure),
per-track actor set on look.actors, HUD helper included. Stage C will
grow as a library on this without infra changes.

Mesh twin of Identity.form: actors/meshes.js builds BufferGeometry from
the same assembly (cast SDF → Shape → ExtrudeGeometry, box/capsule/
torus/sphere primitives, symmetry folding radial/mirror/stack). Shared
with the shader impostor path — one character, two projectors.

Renderer depth targets: createDepthTarget / createTarget{depthTexture}
for WebGL DepthTexture plumbing.

Compositor shared rig: one PerspectiveCamera + DepthTexture so a ground
mesh can occlude a subject mesh from another layer. 4/scale dolly,
Personality.camera drift/sway/spin, framing shift — matches particles.js
and shader epilogue behaviour. ModelLayer (kind:model) with
build/update(actorSpec) and sharedCamera injection; createLayer dispatches
on model. Shader contract gains MODEL_PREAMBLE.

LookGenerator now derives actors before scenes; ArcDriver._actorFor +
_layerFor wires ActorSpec into ModelLayer; schema validates kind:model
and actor archetype; lint determinism gate covers actors/.

Gate: lint 107 files clean, 70 shader literals, 68 scenes green; vite
build 294 modules; ActorGenerator determinism + mesh smoke tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Dejvino
2026-08-20 16:26:15 +02:00
co-authored by Claude
parent ef1d24e692
commit 7aa60d7336
11 changed files with 981 additions and 3 deletions
+12
View File
@@ -221,20 +221,32 @@ export class ArcDriver {
return stack[slot] || null;
}
/** Resolve the ActorSpec for a layer, if the module requests one. */
_actorFor(module) {
const actors = this.look.actors;
if (!actors || !module || !module.actor) return null;
return actors[module.actor] || null;
}
/** One Layer per (section, variant, layer slot), built lazily and kept. */
_layerFor(sectionIndex, variant, slot = 0) {
const key = `${sectionIndex}:${variant}:${slot}`;
let layer = this.layerCache.get(key);
if (!layer) {
const spec = this._specFor(sectionIndex, variant, slot);
const actorSpec = this._actorFor(spec.module);
layer = createLayer(spec.module, {
params: spec.params,
seed: spec.seed,
opacity: spec.opacity,
blend: spec.blend,
actorSpec,
});
layer.setPalette(this.look.palette);
layer.setPersonality(this.look.personality);
// ModelLayers can have their actor swapped without being rebuilt — the
// mesh is imposter-free so the geometry can be re-bound live.
if (actorSpec && layer.setActor) layer.setActor(actorSpec);
this.layerCache.set(key, layer);
}
return layer;
+6
View File
@@ -25,6 +25,7 @@ import { derivePaletteArc, describePaletteArc } from './paletteArc.js';
import { deriveFramingStyle, describeFraming } from './framing.js';
import { deriveCamera, describeCamera } from './Camera.js';
import { deriveStory, storyForSection, NEUTRAL_STATE } from './Story.js';
import { generateActorSet, describeActorSet } from '../actors/ActorGenerator.js';
// Which families suit which section kind now comes from the track's DIRECTOR
// (look/directors.js) rather than from a constant here. The coupling it
@@ -816,6 +817,10 @@ export function generateLook(track, {
// toward one camera the way it leans toward one family per kind, and the
// seed decides — see look/Camera.js.
const camera = deriveCamera(director, summary, rng.fork('camera'));
// The cast with bodies — one ActorSpec per archetype, seeded so a later
// library of actors (Stage C) grows without changing the infra. See
// src/actors/ActorGenerator.js — audio tilts the centre, seed picks within.
const actors = generateActorSet(summary, rng.fork('actors'), personality, personality.identity);
const { post, feedback } = derivePost(summary, rng.fork('post'), grain);
// Scenes eligible to be composited OVER a background. Same casting rule as
@@ -887,6 +892,7 @@ export function generateLook(track, {
paletteArc,
framing,
camera,
actors,
grain,
post,
feedback,