Dungeon venue: grid-first traversable dungeon with cast, loot and encounters
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.
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
// 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'];
|
||||
Reference in New Issue
Block a user