// 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'];