New VenueGenerator 'dungeon' archetype. The floor plan is generated first on a 44x44 grid (rejection-sampled rooms, Kruskal MST + loop edges, L-shaped corridors, BFS-verified connectivity); walls are merged runs built exactly where open meets solid, so the shell has no holes, and the walk route is a closed travelling tour through every room. The camera either walks first-person (hero hidden to avoid clipping) or plants on a per-room watch cell with line-of-sight to the route and tracks the actor. Enemies (walker/structure/swarm ActorSpecs from the track's own summary) lurk, stand sentry, dart and drift; loot caches pause the analytic odometer, which can hold or advance but never retreat. Includes dungeon-debug.html (2D map + timeline stills), 14 map tests plus facade camera tests, and the portcullis-holds-for-the-whole-party regression test.
59 lines
2.0 KiB
JavaScript
59 lines
2.0 KiB
JavaScript
// Shared helpers for the dungeon venue submodules (layout / zones / props /
|
|
// hazards). Pure, no THREE, no wall-clock, no Math.random — seeded Rng only,
|
|
// all motion analytic f(t, seed) so seek === playback.
|
|
//
|
|
// A submodule owns ONE portion of the image and the 3D space its objects live
|
|
// in. The facade (../dungeon.js) owns the single walk clock and the path;
|
|
// every submodule reads (t, plan, pathAt) and never integrates.
|
|
|
|
import { Rng } from '../../engine/rng.js';
|
|
|
|
/** Independent seeded stream that never perturbs sibling streams. */
|
|
export function forkDungeon(seed, label) {
|
|
return new Rng(hashSeed(seed) ^ 0x9e3779b9).fork(String(label));
|
|
}
|
|
|
|
function hashSeed(seed) {
|
|
return (seed >>> 0) || 1;
|
|
}
|
|
|
|
/** Analytic wrap: position of a base offset walking at rate dz after time t. */
|
|
export function streamWrap(s0, t, dz, span) {
|
|
const s = s0 + t * dz;
|
|
return s0 + ((((s - s0) % span) + span) % span);
|
|
}
|
|
|
|
/** Smoothstep 0..1. */
|
|
export function smooth01(x) {
|
|
const t = Math.max(0, Math.min(1, x));
|
|
return t * t * (3 - 2 * t);
|
|
}
|
|
|
|
/**
|
|
* Crew-style visibility fade: how many of a population with `count` members
|
|
* show at a continuous level in [0, ~1.1]. Never toggles — pure f(level,index).
|
|
* Same pattern as ride.js crew fade (DESIGN §4: furniture never hard-toggles
|
|
* on interpolated state).
|
|
*/
|
|
export function fadeFor(level, index, count, soft = 0.12) {
|
|
const showIndex = index / Math.max(1, count);
|
|
return Math.max(0, Math.min(1, (level - showIndex) / soft));
|
|
}
|
|
|
|
/** Segment kinds the layout planner may emit. */
|
|
export const SEGMENT_KINDS = [
|
|
'hall',
|
|
'room',
|
|
'forest',
|
|
'wallwalk',
|
|
];
|
|
|
|
/** Zone names the zone planner may emit (one per section). */
|
|
export const ZONE_NAMES = ['hall', 'room', 'forest', 'wallwalk'];
|
|
|
|
/** Prop types in the prop library (primitives only, no glTF). */
|
|
export const PROP_TYPES = ['rubble', 'furniture', 'trees', 'rampart', 'torch', 'banner', 'stalagmite'];
|
|
|
|
/** Hazard types (visual only — the walk sidesteps them). */
|
|
export const HAZARD_TYPES = ['gate', 'gap', 'patrol'];
|