hayao 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +29 -12
  2. package/dist/art/autotile.d.ts +77 -0
  3. package/dist/art/bitmapFont.d.ts +113 -0
  4. package/dist/art/font5.d.ts +11 -0
  5. package/dist/art/palette.d.ts +38 -0
  6. package/dist/art/texture.d.ts +78 -0
  7. package/dist/audio/audio.d.ts +7 -0
  8. package/dist/content/dsl.d.ts +61 -0
  9. package/dist/core/dmath.d.ts +20 -0
  10. package/dist/core/math.d.ts +2 -0
  11. package/dist/index.d.ts +37 -1
  12. package/dist/index.js +4549 -69
  13. package/dist/index.js.map +4 -4
  14. package/dist/logic/fsm.d.ts +85 -0
  15. package/dist/logic/graph.d.ts +88 -0
  16. package/dist/logic/history.d.ts +54 -0
  17. package/dist/logic/random.d.ts +32 -0
  18. package/dist/net/browser.d.ts +37 -0
  19. package/dist/net/inputBuffer.d.ts +27 -0
  20. package/dist/net/lockstep.d.ts +79 -0
  21. package/dist/net/players.d.ts +27 -0
  22. package/dist/net/protocol.d.ts +100 -0
  23. package/dist/net/rollback.d.ts +89 -0
  24. package/dist/net/room.d.ts +78 -0
  25. package/dist/net/transport.d.ts +78 -0
  26. package/dist/persist/codec.d.ts +4 -0
  27. package/dist/persist/save.d.ts +32 -0
  28. package/dist/persist/storage.d.ts +46 -0
  29. package/dist/physics/rigidBody.d.ts +104 -0
  30. package/dist/physics/rigidCollide.d.ts +16 -0
  31. package/dist/physics/rigidJoints.d.ts +65 -0
  32. package/dist/physics/rigidQueries.d.ts +15 -0
  33. package/dist/physics/rigidStep.d.ts +14 -0
  34. package/dist/procgen/cave.d.ts +21 -0
  35. package/dist/procgen/grid.d.ts +21 -0
  36. package/dist/procgen/rooms.d.ts +34 -0
  37. package/dist/procgen/scatter.d.ts +32 -0
  38. package/dist/procgen/terrain.d.ts +24 -0
  39. package/dist/render/nineSlice.d.ts +32 -0
  40. package/dist/scene/floatingText.d.ts +51 -0
  41. package/dist/scene/particles.d.ts +64 -0
  42. package/dist/scene/pool.d.ts +16 -0
  43. package/dist/scene/tween.d.ts +26 -0
  44. package/dist/ui/transition.d.ts +107 -0
  45. package/dist/verify/layout.d.ts +39 -0
  46. package/docs/API.md +247 -8
  47. package/package.json +31 -9
@@ -0,0 +1,78 @@
1
+ import type { World } from '../world';
2
+ import type { PlayerId } from './players';
3
+ import { type NetSessionConfig } from './protocol';
4
+ import type { Transport } from './transport';
5
+ import { LockstepSession, type DesyncInfo } from './lockstep';
6
+ import { RollbackSession } from './rollback';
7
+ /** A live networked game, the same shape on host and client. */
8
+ export interface NetGame {
9
+ world: World;
10
+ session: LockstepSession | RollbackSession;
11
+ localPlayer: PlayerId;
12
+ /** Roster at the time the game handle was created. */
13
+ players: PlayerId[];
14
+ /** Drive this from the render loop instead of world.advance(). */
15
+ advance(realMs: number, localActions?: readonly string[]): number;
16
+ dispose(): void;
17
+ }
18
+ export interface RoomCallbacks {
19
+ onDesync?: (info: DesyncInfo) => void;
20
+ onPlayerJoin?: (player: PlayerId, atFrame: number) => void;
21
+ onPlayerLeave?: (player: PlayerId, atFrame: number) => void;
22
+ }
23
+ export interface RoomHostOptions extends RoomCallbacks {
24
+ transport: Transport;
25
+ /** Build the deterministic world every peer will run. */
26
+ makeWorld: (seed: number) => World;
27
+ seed?: number;
28
+ config?: Partial<NetSessionConfig>;
29
+ maxPlayers?: number;
30
+ /** Frames between "a joiner exists" and their first live frame. */
31
+ joinMargin?: number;
32
+ /** Re-attach behaviors after restore (rollback / late-join snapshot). */
33
+ attach?: (world: World) => void;
34
+ }
35
+ export declare class RoomHost {
36
+ readonly localPlayer: PlayerId;
37
+ readonly seed: number;
38
+ readonly config: NetSessionConfig;
39
+ private readonly transport;
40
+ private readonly makeWorld;
41
+ private readonly maxPlayers;
42
+ private readonly joinMargin;
43
+ private readonly attach?;
44
+ private readonly callbacks;
45
+ private players;
46
+ private uidToPlayer;
47
+ private nextPlayerIndex;
48
+ private started;
49
+ private game;
50
+ private pendingSnapshots;
51
+ private unsubscribe;
52
+ constructor(opts: RoomHostOptions);
53
+ /** Players currently in the room (host first). */
54
+ get roster(): PlayerId[];
55
+ get isStarted(): boolean;
56
+ /** Freeze the roster's frame 0 and begin simulating. */
57
+ start(): NetGame;
58
+ /** Announce a player's departure (disconnect handling is the app's call). */
59
+ dropPlayer(player: PlayerId): void;
60
+ dispose(): void;
61
+ private receive;
62
+ private handleHello;
63
+ private serviceSnapshots;
64
+ }
65
+ export interface RoomClientOptions extends RoomCallbacks {
66
+ transport: Transport;
67
+ makeWorld: (seed: number) => World;
68
+ attach?: (world: World) => void;
69
+ name?: string;
70
+ }
71
+ /**
72
+ * Join a room: send hello, wait for welcome (+start or snapshot), come back
73
+ * with a NetGame. Session-layer messages that arrive while the snapshot is in
74
+ * flight are buffered and replayed so no input is ever missed.
75
+ */
76
+ export declare function joinRoom(opts: RoomClientOptions): Promise<NetGame>;
77
+ /** Create a room host over a transport. Call `start()` when the lobby is set. */
78
+ export declare function hostRoom(opts: RoomHostOptions): RoomHost;
@@ -0,0 +1,78 @@
1
+ export interface Transport {
2
+ /** Deliver to every other peer on the bus. */
3
+ send(data: string): void;
4
+ /** Subscribe to incoming data. Returns an unsubscribe function. */
5
+ onMessage(cb: (data: string) => void): () => void;
6
+ /** Fires when the transport goes away (socket close, hub shutdown). */
7
+ onClose(cb: () => void): () => void;
8
+ close(): void;
9
+ readonly isOpen: boolean;
10
+ }
11
+ export interface LoopbackOptions {
12
+ /** Delivery delay in ticks (calls to hub.tick()). Default 0 = next tick. */
13
+ delay?: number;
14
+ /** Drop every Nth message (deterministic loss pattern). 0 = lossless. */
15
+ dropEvery?: number;
16
+ }
17
+ /**
18
+ * An in-memory bus with explicit, deterministic time: nothing is delivered
19
+ * until `tick()` runs, messages arrive in send order, and loss follows a fixed
20
+ * pattern. Network tests stay exactly reproducible — no timers, no races.
21
+ */
22
+ export declare class LoopbackHub {
23
+ private peers;
24
+ private queue;
25
+ private tickCount;
26
+ private seq;
27
+ private sent;
28
+ private readonly delay;
29
+ private readonly dropEvery;
30
+ constructor(opts?: LoopbackOptions);
31
+ /** Create a new endpoint on this bus. */
32
+ connect(): LoopbackTransport;
33
+ /** @internal */
34
+ enqueue(data: string, from: number): void;
35
+ /** Advance one network tick, delivering everything that is due. */
36
+ tick(): void;
37
+ /** Deliver everything in flight (repeated ticks until the queue drains). */
38
+ flush(): void;
39
+ get pending(): number;
40
+ }
41
+ export declare class LoopbackTransport implements Transport {
42
+ private readonly hub;
43
+ readonly peerIndex: number;
44
+ private listeners;
45
+ private closeListeners;
46
+ private open;
47
+ constructor(hub: LoopbackHub, peerIndex: number);
48
+ send(data: string): void;
49
+ onMessage(cb: (data: string) => void): () => void;
50
+ onClose(cb: () => void): () => void;
51
+ close(): void;
52
+ get isOpen(): boolean;
53
+ /** @internal */
54
+ deliver(data: string): void;
55
+ }
56
+ /**
57
+ * A bus over the browser's BroadcastChannel — every tab on the same origin
58
+ * that opens the same room name is a peer. Perfect for local two-tab netplay
59
+ * and demos: no server at all.
60
+ */
61
+ export declare class BroadcastChannelTransport implements Transport {
62
+ private channel;
63
+ private listeners;
64
+ private closeListeners;
65
+ private open;
66
+ constructor(room: string);
67
+ send(data: string): void;
68
+ onMessage(cb: (data: string) => void): () => void;
69
+ onClose(cb: () => void): () => void;
70
+ close(): void;
71
+ get isOpen(): boolean;
72
+ }
73
+ /**
74
+ * A bus over a WebSocket to the hayao relay (`npm run relay`): everyone
75
+ * connected to the same room URL is a peer; the relay forwards each message to
76
+ * the room's other sockets. Resolves once the socket is open.
77
+ */
78
+ export declare function connectWebSocket(url: string): Promise<Transport>;
@@ -0,0 +1,4 @@
1
+ export declare function rleEncode(input: string): string;
2
+ export declare function rleDecode(input: string): string;
3
+ export declare function packVarints(nums: readonly number[]): string;
4
+ export declare function unpackVarints(input: string): number[];
@@ -0,0 +1,32 @@
1
+ import type { World, WorldSnapshot } from '../world';
2
+ import { type StorageAdapter } from './storage';
3
+ /** Bump when the snapshot shape changes in a way old saves can't satisfy. */
4
+ export declare const SAVE_FORMAT_VERSION = 1;
5
+ /** Serialize a snapshot to a compact JSON string with a version envelope. */
6
+ export declare function serializeSnapshot(snap: WorldSnapshot): string;
7
+ /**
8
+ * Parse a serialized snapshot back. Returns `null` on anything malformed —
9
+ * corrupt JSON, wrong version, missing fields — so a bad save is a clean miss,
10
+ * never a thrown exception mid-game. Guards `JSON.parse` per the invariant.
11
+ */
12
+ export declare function parseSnapshot(text: string | null): WorldSnapshot | null;
13
+ /**
14
+ * A tiny save API bound to one World and one storage backend. Slots are named
15
+ * ('auto', 'slot1', …). Everything routes through `world.snapshot()`/`restore()`
16
+ * so the saved and restored states share a hash.
17
+ */
18
+ export declare class SaveManager {
19
+ private readonly world;
20
+ private readonly storage;
21
+ constructor(world: World, storage?: StorageAdapter);
22
+ /** Snapshot the world and persist it under `slot`. */
23
+ save(slot?: string): void;
24
+ /** Restore `slot` into the world. Returns false if absent/corrupt (world untouched). */
25
+ load(slot?: string): boolean;
26
+ /** Is there a well-formed save in `slot`? */
27
+ has(slot?: string): boolean;
28
+ /** Delete a save slot. */
29
+ delete(slot?: string): void;
30
+ /** All slot names currently persisted. */
31
+ slots(): string[];
32
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * A minimal key→string store. Deliberately tiny (get/set/remove/keys) so any
3
+ * backend — `localStorage`, a Map, a file, IndexedDB — satisfies it trivially.
4
+ */
5
+ export interface StorageAdapter {
6
+ get(key: string): string | null;
7
+ set(key: string, value: string): void;
8
+ remove(key: string): void;
9
+ /** All keys currently held, in insertion order where the backend preserves it. */
10
+ keys(): string[];
11
+ }
12
+ /** In-memory backend. Deterministic and self-contained — the headless default. */
13
+ export declare class MemoryStorage implements StorageAdapter {
14
+ private readonly map;
15
+ get(key: string): string | null;
16
+ set(key: string, value: string): void;
17
+ remove(key: string): void;
18
+ keys(): string[];
19
+ }
20
+ /** Swallows everything — reads always miss. For runs that must stay pristine. */
21
+ export declare class NullStorage implements StorageAdapter {
22
+ get(_key: string): string | null;
23
+ set(_key: string, _value: string): void;
24
+ remove(_key: string): void;
25
+ keys(): string[];
26
+ }
27
+ /**
28
+ * `localStorage`-backed, guarded on every call: a disabled/absent/full store
29
+ * (private mode, quota, SSR) degrades to a silent miss instead of throwing, so a
30
+ * save failure never crashes a game. Keys are namespaced by `prefix`.
31
+ */
32
+ export declare class LocalStorageAdapter implements StorageAdapter {
33
+ private readonly prefix;
34
+ constructor(prefix?: string);
35
+ private ls;
36
+ get(key: string): string | null;
37
+ set(key: string, value: string): void;
38
+ remove(key: string): void;
39
+ keys(): string[];
40
+ }
41
+ /**
42
+ * The right adapter for the current environment: `localStorage` when a real one
43
+ * is present (browser), otherwise an in-memory map (headless tests/CI). Pass
44
+ * `NullStorage` explicitly when you want reads to always miss.
45
+ */
46
+ export declare function defaultStorage(prefix?: string): StorageAdapter;
@@ -0,0 +1,104 @@
1
+ import type { Rect } from '../core/math';
2
+ export type RigidShape = {
3
+ kind: 'circle';
4
+ r: number;
5
+ }
6
+ /** Convex polygon, CCW winding, local coords flat [x0,y0,x1,y1,…]. */
7
+ | {
8
+ kind: 'poly';
9
+ points: number[];
10
+ };
11
+ export type BodyKind = 'dynamic' | 'static' | 'kinematic';
12
+ export interface RigidBody {
13
+ id: number;
14
+ kind: BodyKind;
15
+ shape: RigidShape;
16
+ /** Pose: position of the center of mass + rotation. */
17
+ x: number;
18
+ y: number;
19
+ a: number;
20
+ /** Velocity: linear + angular (rad/s). */
21
+ vx: number;
22
+ vy: number;
23
+ w: number;
24
+ /** Mass properties (0 inverse = immovable / non-rotating). */
25
+ m: number;
26
+ invM: number;
27
+ i: number;
28
+ invI: number;
29
+ restitution: number;
30
+ friction: number;
31
+ linDamp: number;
32
+ angDamp: number;
33
+ gravityScale: number;
34
+ /** Swept-circle CCD each step (circles only) — for very fast bodies. */
35
+ bullet: boolean;
36
+ /** Overlap events only, no collision response. */
37
+ sensor: boolean;
38
+ /** Collision filtering: collide iff (a.mask & b.layer) && (b.mask & a.layer). */
39
+ layer: number;
40
+ mask: number;
41
+ canSleep: boolean;
42
+ sleeping: boolean;
43
+ sleepTime: number;
44
+ /** Characteristic radius: converts spin to rim speed for the sleep test. */
45
+ sleepR: number;
46
+ /** Per-step force/torque accumulators (cleared after integration). */
47
+ fx: number;
48
+ fy: number;
49
+ torque: number;
50
+ }
51
+ export interface RigidWorldOpts {
52
+ gravityX?: number;
53
+ gravityY?: number;
54
+ /** Solver velocity iterations (default 16 — stacks need it). */
55
+ iterations?: number;
56
+ /** Broadphase cell size in px (default 96). */
57
+ cellSize?: number;
58
+ }
59
+ export interface RigidWorld {
60
+ gravityX: number;
61
+ gravityY: number;
62
+ iterations: number;
63
+ cellSize: number;
64
+ nextId: number;
65
+ bodies: RigidBody[];
66
+ joints: import('./rigidJoints').Joint[];
67
+ /** Warm-start impulse cache from the previous step, keyed "a:b:feature". */
68
+ warm: Record<string, [number, number]>;
69
+ }
70
+ export declare function createRigidWorld(opts?: RigidWorldOpts): RigidWorld;
71
+ export interface RigidBodyDef {
72
+ kind?: BodyKind;
73
+ shape: RigidShape;
74
+ x?: number;
75
+ y?: number;
76
+ a?: number;
77
+ vx?: number;
78
+ vy?: number;
79
+ w?: number;
80
+ density?: number;
81
+ restitution?: number;
82
+ friction?: number;
83
+ linDamp?: number;
84
+ angDamp?: number;
85
+ gravityScale?: number;
86
+ bullet?: boolean;
87
+ sensor?: boolean;
88
+ layer?: number;
89
+ mask?: number;
90
+ canSleep?: boolean;
91
+ /** Lock rotation (players, elevators): infinite inertia. */
92
+ fixedRotation?: boolean;
93
+ }
94
+ export declare function addBody(rw: RigidWorld, def: RigidBodyDef): number;
95
+ export declare function getBody(rw: RigidWorld, id: number): RigidBody | undefined;
96
+ export declare function removeBody(rw: RigidWorld, id: number): void;
97
+ export declare function wakeBody(b: RigidBody): void;
98
+ /** Apply an impulse at a world point (wakes the body). */
99
+ export declare function applyImpulse(rw: RigidWorld, id: number, ix: number, iy: number, px?: number, py?: number): void;
100
+ /** World-space vertices of a poly body (freshly computed — no caches). */
101
+ export declare function worldPoints(b: RigidBody): number[];
102
+ export declare function bodyAABB(b: RigidBody): Rect;
103
+ /** Centered box as a CCW convex polygon — the workhorse shape helper. */
104
+ export declare function polygonBox(w: number, h: number): RigidShape;
@@ -0,0 +1,16 @@
1
+ import type { RigidBody } from './rigidBody';
2
+ export interface ContactPoint {
3
+ px: number;
4
+ py: number;
5
+ pen: number;
6
+ feature: number;
7
+ }
8
+ export interface Manifold {
9
+ a: RigidBody;
10
+ b: RigidBody;
11
+ nx: number;
12
+ ny: number;
13
+ points: ContactPoint[];
14
+ }
15
+ /** Dispatch on shape kinds. Normal always points from A toward B. */
16
+ export declare function collide(a: RigidBody, b: RigidBody): Manifold | null;
@@ -0,0 +1,65 @@
1
+ import type { RigidBody, RigidWorld } from './rigidBody';
2
+ export interface DistanceJoint {
3
+ kind: 'distance';
4
+ id: number;
5
+ a: number;
6
+ b: number;
7
+ ax: number;
8
+ ay: number;
9
+ bx: number;
10
+ by: number;
11
+ length: number;
12
+ /** Rope mode: only constrain when stretched past `length`. */
13
+ rope: boolean;
14
+ }
15
+ export interface RevoluteJoint {
16
+ kind: 'revolute';
17
+ id: number;
18
+ a: number;
19
+ b: number;
20
+ ax: number;
21
+ ay: number;
22
+ bx: number;
23
+ by: number;
24
+ /** Motor: drive relative angular velocity toward `motorSpeed`. */
25
+ motorSpeed: number;
26
+ maxMotorTorque: number;
27
+ /** Angle limits on (b.a - a.a - refAngle); NaN-free: enabled flag. */
28
+ limitLower: number;
29
+ limitUpper: number;
30
+ limitEnabled: boolean;
31
+ refAngle: number;
32
+ }
33
+ export type Joint = DistanceJoint | RevoluteJoint;
34
+ export interface DistanceJointDef {
35
+ a: number;
36
+ b: number;
37
+ ax?: number;
38
+ ay?: number;
39
+ bx?: number;
40
+ by?: number;
41
+ /** Defaults to the current anchor distance. */
42
+ length?: number;
43
+ rope?: boolean;
44
+ }
45
+ export interface RevoluteJointDef {
46
+ a: number;
47
+ b: number;
48
+ /** World-space pivot; local anchors are derived from current poses. */
49
+ px: number;
50
+ py: number;
51
+ motorSpeed?: number;
52
+ maxMotorTorque?: number;
53
+ limitLower?: number;
54
+ limitUpper?: number;
55
+ }
56
+ export declare function addDistanceJoint(rw: RigidWorld, def: DistanceJointDef): number;
57
+ export declare function addRevoluteJoint(rw: RigidWorld, def: RevoluteJointDef): number;
58
+ export declare function getJoint(rw: RigidWorld, id: number): Joint | undefined;
59
+ export declare function removeJoint(rw: RigidWorld, id: number): void;
60
+ /** Per-step scratch for accumulated-impulse clamping (one per joint). */
61
+ export interface JointScratch {
62
+ motorImpulse: number;
63
+ }
64
+ /** One velocity iteration for a single joint. `bias` uses dt for Baumgarte. */
65
+ export declare function solveJoint(j: Joint, A: RigidBody, B: RigidBody, dt: number, scratch: JointScratch): void;
@@ -0,0 +1,15 @@
1
+ import type { RigidBody, RigidWorld } from './rigidBody';
2
+ export interface RigidRayHit {
3
+ id: number;
4
+ t: number;
5
+ x: number;
6
+ y: number;
7
+ nx: number;
8
+ ny: number;
9
+ }
10
+ /** Is a world point inside this body's shape? */
11
+ export declare function bodyContains(b: RigidBody, x: number, y: number): boolean;
12
+ /** First body containing the point (topmost = last added wins ties via scan order). */
13
+ export declare function pointQuery(rw: RigidWorld, x: number, y: number, mask?: number): RigidBody | undefined;
14
+ /** Closest hit along the segment (x0,y0)→(x1,y1), or null. */
15
+ export declare function rayCastRigid(rw: RigidWorld, x0: number, y0: number, x1: number, y1: number, mask?: number): RigidRayHit | null;
@@ -0,0 +1,14 @@
1
+ import type { RigidWorld } from './rigidBody';
2
+ export interface ContactEvent {
3
+ a: number;
4
+ b: number;
5
+ px: number;
6
+ py: number;
7
+ nx: number;
8
+ ny: number;
9
+ /** Total normal impulse this step — scales with hit violence. */
10
+ impulse: number;
11
+ /** Sensor overlap (no collision response was applied). */
12
+ sensor: boolean;
13
+ }
14
+ export declare function rigidStep(rw: RigidWorld, dt: number): ContactEvent[];
@@ -0,0 +1,21 @@
1
+ import type { Rng } from '../core/rng';
2
+ import { type Grid } from './grid';
3
+ export interface CaveOptions {
4
+ cols: number;
5
+ rows: number;
6
+ /** Initial solid-fill probability (0..1). ~0.45 gives balanced caverns. */
7
+ fill?: number;
8
+ /** Smoothing passes (more = rounder, larger chambers). Default 4. */
9
+ steps?: number;
10
+ /** A cell becomes solid if it has ≥ this many solid neighbours. Default 5. */
11
+ birth?: number;
12
+ /** A solid cell survives if it has ≥ this many solid neighbours. Default 4. */
13
+ survive?: number;
14
+ /** Force the outer ring solid so the cave is sealed. Default true. */
15
+ border?: boolean;
16
+ }
17
+ /**
18
+ * Generate a cave grid (1 = rock, 0 = open) with cellular automata. Deterministic
19
+ * given `rng`'s state + options. Feed the result to `gridToTilemap` for physics.
20
+ */
21
+ export declare function generateCave(rng: Rng, opts: CaveOptions): Grid;
@@ -0,0 +1,21 @@
1
+ import { type TilemapData } from '../physics/tilemap';
2
+ export interface Grid {
3
+ cols: number;
4
+ rows: number;
5
+ /** Row-major cells, length cols*rows. 1 = solid, 0 = open. */
6
+ cells: number[];
7
+ }
8
+ /** Allocate a grid filled with a single value (default open). */
9
+ export declare function makeGrid(cols: number, rows: number, fill?: number): Grid;
10
+ /** Read a cell; out-of-bounds reads as solid (1) so borders seal naturally. */
11
+ export declare function gridAt(g: Grid, x: number, y: number): number;
12
+ /** Write a cell (no-op out of bounds). */
13
+ export declare function gridSet(g: Grid, x: number, y: number, v: number): void;
14
+ /** Count solid cells in the 8-neighbourhood (Moore) around (x,y). */
15
+ export declare function solidNeighbours(g: Grid, x: number, y: number): number;
16
+ /**
17
+ * Project a grid to collision geometry: 1 → SOLID, everything else → EMPTY.
18
+ * The result is a logical, hash-relevant `TilemapData` — feed it straight to
19
+ * the physics tilemap functions.
20
+ */
21
+ export declare function gridToTilemap(g: Grid, tileSize?: number): TilemapData;
@@ -0,0 +1,34 @@
1
+ import type { Rng } from '../core/rng';
2
+ import { type Grid } from './grid';
3
+ export interface Room {
4
+ x: number;
5
+ y: number;
6
+ w: number;
7
+ h: number;
8
+ }
9
+ export interface DungeonOptions {
10
+ cols: number;
11
+ rows: number;
12
+ /** How many placement attempts to make (actual rooms ≤ this). Default 12. */
13
+ attempts?: number;
14
+ /** Room side length range (tiles), inclusive. */
15
+ minSize?: number;
16
+ maxSize?: number;
17
+ /** Keep a 1-tile solid margin around the grid. Default true. */
18
+ border?: boolean;
19
+ }
20
+ export interface Dungeon {
21
+ rooms: Room[];
22
+ grid: Grid;
23
+ }
24
+ /** Center tile of a room (floored). */
25
+ export declare function roomCenter(r: Room): {
26
+ x: number;
27
+ y: number;
28
+ };
29
+ /**
30
+ * Generate a room-and-corridor layout in a grid (1 = wall, 0 = floor).
31
+ * Deterministic given `rng` state + options; consecutive rooms are joined so the
32
+ * whole floor is connected. Feed `.grid` to `gridToTilemap` for physics.
33
+ */
34
+ export declare function generateDungeon(rng: Rng, opts: DungeonOptions): Dungeon;
@@ -0,0 +1,32 @@
1
+ /** 32-bit integer hash of a 2D cell + seed. Well-mixed; avalanche on any bit. */
2
+ export declare function cellHash(x: number, y: number, seed?: number): number;
3
+ /** Deterministic float in [0,1) for a cell — hash normalized. */
4
+ export declare function cellFloat(x: number, y: number, seed?: number): number;
5
+ /** Deterministic integer in [0,n) for a cell (e.g. pick a decoration variant). */
6
+ export declare function cellInt(x: number, y: number, n: number, seed?: number): number;
7
+ /** True with ~`probability` at this cell — the stateless scatter test. */
8
+ export declare function scatter(x: number, y: number, probability: number, seed?: number): boolean;
9
+ /**
10
+ * Enumerate the chosen cells in a rectangle, row-major (ordered). Handy for
11
+ * placing decoration nodes over a visible region without a PRNG. Cosmetic.
12
+ */
13
+ export declare function scatterCells(x0: number, y0: number, cols: number, rows: number, probability: number, seed?: number): {
14
+ x: number;
15
+ y: number;
16
+ }[];
17
+ /**
18
+ * Value noise in [0,1] at a fractional coordinate — smooth bilinear blend of the
19
+ * four surrounding integer-cell hashes. This is the deliberately-small
20
+ * alternative to a simplex lib (see JS13K-MINING anti-recommendations): enough
21
+ * for cosmetic terrain tint, cloud cover, wobble; not a gameplay dependency.
22
+ */
23
+ export declare function valueNoise(x: number, y: number, seed?: number): number;
24
+ export interface FractalOptions {
25
+ octaves?: number;
26
+ /** Frequency multiplier per octave (default 2). */
27
+ lacunarity?: number;
28
+ /** Amplitude multiplier per octave (default 0.5). */
29
+ gain?: number;
30
+ }
31
+ /** Fractal (fBm) value noise — layered octaves, normalized to [0,1]. Cosmetic. */
32
+ export declare function fractalNoise(x: number, y: number, seed?: number, opts?: FractalOptions): number;
@@ -0,0 +1,24 @@
1
+ import { type FractalOptions } from './scatter';
2
+ export interface TerrainOptions {
3
+ /** Baseline ground row (tiles from the top). */
4
+ base: number;
5
+ /** Peak deviation above/below the baseline (tiles). */
6
+ amplitude: number;
7
+ /** Horizontal frequency — smaller = longer, smoother hills. Default 0.08. */
8
+ scale?: number;
9
+ /** Clamp ground into [minRow, maxRow] so it stays on-screen. */
10
+ minRow?: number;
11
+ maxRow?: number;
12
+ seed?: number;
13
+ fractal?: FractalOptions;
14
+ }
15
+ /**
16
+ * Ground row (integer tile Y) for a column. Lower row = higher ground. Sampling
17
+ * the same (col, opts) always returns the same height — the endless-terrain
18
+ * contract.
19
+ */
20
+ export declare function terrainHeight(col: number, opts: TerrainOptions): number;
21
+ /** Ground rows for a contiguous chunk `[startCol, startCol+count)` (ordered). */
22
+ export declare function terrainSlice(startCol: number, count: number, opts: TerrainOptions): number[];
23
+ /** True when a tile is solid ground (at or below the surface row) at (col,row). */
24
+ export declare function isGround(col: number, row: number, opts: TerrainOptions): boolean;
@@ -0,0 +1,32 @@
1
+ import { type Rect, type Transform } from '../core/math';
2
+ import type { DrawCommand } from './commands';
3
+ export interface NineSliceStyle {
4
+ /** Corner size in px — the slice inset that never stretches. */
5
+ border: number;
6
+ /** Center fill (the panel body). */
7
+ fill?: string;
8
+ /** Edge fill (top/bottom/left/right strips). Defaults to `fill`. */
9
+ edge?: string;
10
+ /** Corner fill (the four fixed squares). Defaults to `edge`. */
11
+ corner?: string;
12
+ /** Optional bevel: lighten the top/left, darken the bottom/right. */
13
+ highlight?: string;
14
+ shadow?: string;
15
+ /** Outline stroke drawn around the whole frame. */
16
+ stroke?: string;
17
+ strokeWidth?: number;
18
+ /** Outer corner radius applied to the four corner squares. */
19
+ radius?: number;
20
+ }
21
+ /**
22
+ * Emit the draw commands for a 9-slice panel filling `rect`. Regions are pushed
23
+ * back-to-front (center → edges → corners) at painter key `z`, transformed by
24
+ * `transform` (screen space by default). Border is clamped so it never exceeds
25
+ * half the smaller side, keeping the grid valid at any size.
26
+ */
27
+ export declare function nineSlice(rect: Rect, style: NineSliceStyle, z?: number, transform?: Transform): DrawCommand[];
28
+ /** Ready-made frame looks. */
29
+ export declare const PANEL_PRESETS: {
30
+ readonly parchment: () => NineSliceStyle;
31
+ readonly slate: () => NineSliceStyle;
32
+ };