hayao 0.1.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.
- package/LICENSE +21 -0
- package/README.md +98 -0
- package/dist/app/browser.d.ts +19 -0
- package/dist/app/game.d.ts +34 -0
- package/dist/art/palette.d.ts +20 -0
- package/dist/art/shapes.d.ts +12 -0
- package/dist/audio/audio.d.ts +42 -0
- package/dist/core/clock.d.ts +37 -0
- package/dist/core/events.d.ts +23 -0
- package/dist/core/hash.d.ts +6 -0
- package/dist/core/math.d.ts +44 -0
- package/dist/core/rng.d.ts +38 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +2844 -0
- package/dist/index.js.map +7 -0
- package/dist/input/actions.d.ts +40 -0
- package/dist/input/source.d.ts +25 -0
- package/dist/physics/aabb.d.ts +38 -0
- package/dist/physics/platformer.d.ts +112 -0
- package/dist/physics/raycast.d.ts +22 -0
- package/dist/physics/spatialHash.d.ts +15 -0
- package/dist/physics/tilemap.d.ts +38 -0
- package/dist/render/canvas.d.ts +19 -0
- package/dist/render/commands.d.ts +65 -0
- package/dist/render/headless.d.ts +17 -0
- package/dist/render/renderer.d.ts +16 -0
- package/dist/render/svg.d.ts +16 -0
- package/dist/render/svgString.d.ts +5 -0
- package/dist/scene/node.d.ts +100 -0
- package/dist/scene/nodes.d.ts +90 -0
- package/dist/scene/particles.d.ts +78 -0
- package/dist/scene/registry.d.ts +5 -0
- package/dist/scene/tween.d.ts +20 -0
- package/dist/ui/overlay.d.ts +25 -0
- package/dist/ui/settings.d.ts +23 -0
- package/dist/ui/shell.d.ts +19 -0
- package/dist/verify/bot.d.ts +21 -0
- package/dist/verify/capture.d.ts +21 -0
- package/dist/verify/determinism.d.ts +29 -0
- package/dist/verify/driver.d.ts +23 -0
- package/dist/verify/feel.d.ts +25 -0
- package/dist/verify/filmstrip.d.ts +19 -0
- package/dist/verify/playthrough.d.ts +21 -0
- package/dist/verify/solver.d.ts +37 -0
- package/dist/world.d.ts +75 -0
- package/docs/API.md +226 -0
- package/package.json +44 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Node } from './node';
|
|
2
|
+
export type Easing = (t: number) => number;
|
|
3
|
+
export declare const EASINGS: Record<string, Easing>;
|
|
4
|
+
/** A node that runs one or more property tweens and reports when they finish. */
|
|
5
|
+
export declare class AnimationPlayer extends Node {
|
|
6
|
+
readonly type = "AnimationPlayer";
|
|
7
|
+
private tracks;
|
|
8
|
+
/**
|
|
9
|
+
* Animate a value from → to over `duration` seconds via `apply(value)`.
|
|
10
|
+
* Returns this for chaining.
|
|
11
|
+
*/
|
|
12
|
+
to(apply: (v: number) => void, from: number, to: number, duration: number, ease?: keyof typeof EASINGS | Easing, opts?: {
|
|
13
|
+
delay?: number;
|
|
14
|
+
onDone?: () => void;
|
|
15
|
+
}): this;
|
|
16
|
+
get active(): boolean;
|
|
17
|
+
/** Signal emitted when all tracks complete. */
|
|
18
|
+
get finished(): import("..").Signal<void>;
|
|
19
|
+
protected onProcess(dt: number): void;
|
|
20
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface MenuAction {
|
|
2
|
+
label: string;
|
|
3
|
+
onSelect: () => void;
|
|
4
|
+
primary?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface ScreenSpec {
|
|
7
|
+
title?: string;
|
|
8
|
+
/** Subtitle / body text (plain text or HTML string). */
|
|
9
|
+
body?: string;
|
|
10
|
+
/** Menu actions, navigable with up/down + Enter. */
|
|
11
|
+
actions?: MenuAction[];
|
|
12
|
+
/** Dim the game behind the screen. */
|
|
13
|
+
dim?: boolean;
|
|
14
|
+
/** Extra class for theming. */
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ScreenHandle {
|
|
18
|
+
close(): void;
|
|
19
|
+
readonly element: HTMLElement;
|
|
20
|
+
}
|
|
21
|
+
/** Set the element overlays mount into (defaults to document.body). */
|
|
22
|
+
export declare function setOverlayHost(el: HTMLElement): void;
|
|
23
|
+
/** Show a screen (replaces any current one). Returns a handle. */
|
|
24
|
+
export declare function showScreen(spec: ScreenSpec): ScreenHandle;
|
|
25
|
+
export declare function hideScreen(): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface Settings {
|
|
2
|
+
master: number;
|
|
3
|
+
music: number;
|
|
4
|
+
sfx: number;
|
|
5
|
+
muted: boolean;
|
|
6
|
+
colorblind: boolean;
|
|
7
|
+
reducedMotion: boolean;
|
|
8
|
+
}
|
|
9
|
+
type Sub = (s: Settings) => void;
|
|
10
|
+
declare class SettingsStore {
|
|
11
|
+
private state;
|
|
12
|
+
private subs;
|
|
13
|
+
constructor();
|
|
14
|
+
get(): Settings;
|
|
15
|
+
set(patch: Partial<Settings>): void;
|
|
16
|
+
subscribe(fn: Sub): () => void;
|
|
17
|
+
private pushToAudio;
|
|
18
|
+
private load;
|
|
19
|
+
private save;
|
|
20
|
+
}
|
|
21
|
+
export declare const settings: SettingsStore;
|
|
22
|
+
export declare function toggleFullscreen(): void;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface ShellOptions {
|
|
2
|
+
onRestart?: () => void;
|
|
3
|
+
onQuit?: () => void;
|
|
4
|
+
/** Called with true when paused, false when resumed. */
|
|
5
|
+
onPause?: (paused: boolean) => void;
|
|
6
|
+
title?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class Shell {
|
|
9
|
+
private paused;
|
|
10
|
+
private opts;
|
|
11
|
+
private keyHandler;
|
|
12
|
+
constructor(opts?: ShellOptions);
|
|
13
|
+
get isPaused(): boolean;
|
|
14
|
+
toggle(): void;
|
|
15
|
+
pause(): void;
|
|
16
|
+
resume(): void;
|
|
17
|
+
private render;
|
|
18
|
+
dispose(): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface BotCtx {
|
|
2
|
+
/** Frames spent in the current step. */
|
|
3
|
+
frames: number;
|
|
4
|
+
/** Free per-step scratch (cleared on step change). */
|
|
5
|
+
mem: Record<string, unknown>;
|
|
6
|
+
/** Advance to the next plan step. */
|
|
7
|
+
next(): void;
|
|
8
|
+
/** Restart the current step (retry) — clears frames + mem. */
|
|
9
|
+
retry(): void;
|
|
10
|
+
}
|
|
11
|
+
export type StepExec<Step, Probe> = (step: Step, probe: Probe, out: string[], ctx: BotCtx) => void;
|
|
12
|
+
export interface PlanBot<Probe> {
|
|
13
|
+
(probe: Probe): string[];
|
|
14
|
+
stepIndex(): number;
|
|
15
|
+
done(): boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function createPlanBot<Step extends {
|
|
18
|
+
kind: string;
|
|
19
|
+
}, Probe>(plan: Step[], execs: Record<string, StepExec<Step, Probe>>): PlanBot<Probe>;
|
|
20
|
+
/** Shared steering helper: 4/8-way movement toward a point. */
|
|
21
|
+
export declare function steer2D(px: number, py: number, tx: number, ty: number, out: string[], dead?: number): void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { World } from '../world';
|
|
2
|
+
export interface CaptureTarget {
|
|
3
|
+
readonly world: World;
|
|
4
|
+
/** Advance one fixed step with the given actions and render. */
|
|
5
|
+
stepOnce(actions?: string[]): void;
|
|
6
|
+
/** Current frame as an SVG string (a vector screenshot). */
|
|
7
|
+
renderSVG(): string;
|
|
8
|
+
setPaused(paused: boolean): void;
|
|
9
|
+
}
|
|
10
|
+
export interface HayaoCapture {
|
|
11
|
+
pump(frames: number, actions?: string[]): Record<string, unknown>;
|
|
12
|
+
probe(): Record<string, unknown>;
|
|
13
|
+
hash(): string;
|
|
14
|
+
shot(): string;
|
|
15
|
+
save(path: string): Promise<boolean>;
|
|
16
|
+
key(type: 'keydown' | 'keyup', code: string): void;
|
|
17
|
+
readonly world: World;
|
|
18
|
+
}
|
|
19
|
+
export declare function isCaptureMode(): boolean;
|
|
20
|
+
/** Install window.__hayao for scripted browser verification. */
|
|
21
|
+
export declare function installCapture(target: CaptureTarget): HayaoCapture;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { World } from '../world';
|
|
2
|
+
import type { InputLog } from '../input/actions';
|
|
3
|
+
export type WorldFactory = () => World;
|
|
4
|
+
/** Run a world through an input log, returning the final hash and per-frame hashes. */
|
|
5
|
+
export declare function replay(makeWorld: WorldFactory, log: InputLog): {
|
|
6
|
+
finalHash: string;
|
|
7
|
+
hashes: string[];
|
|
8
|
+
};
|
|
9
|
+
export interface DeterminismReport {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
frames: number;
|
|
12
|
+
/** First frame index where the two runs diverged, or -1. */
|
|
13
|
+
divergedAt: number;
|
|
14
|
+
finalHash: string;
|
|
15
|
+
}
|
|
16
|
+
/** Run twice and compare every step's hash. */
|
|
17
|
+
export declare function checkDeterministic(makeWorld: WorldFactory, log: InputLog): DeterminismReport;
|
|
18
|
+
/** Assert determinism (throws with the divergence frame). */
|
|
19
|
+
export declare function assertDeterministic(makeWorld: WorldFactory, log: InputLog): DeterminismReport;
|
|
20
|
+
/**
|
|
21
|
+
* Assert that restoring a snapshot reproduces the exact same continuation —
|
|
22
|
+
* i.e. save/load is lossless. Runs `warmup` steps, snapshots, runs `tail` more
|
|
23
|
+
* on both the original and a restored copy, and compares final hashes.
|
|
24
|
+
*/
|
|
25
|
+
export declare function assertSnapshotStable(makeWorld: WorldFactory, log: InputLog, warmup: number): {
|
|
26
|
+
ok: boolean;
|
|
27
|
+
hashA: string;
|
|
28
|
+
hashB: string;
|
|
29
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { World } from '../world';
|
|
2
|
+
export interface ScriptSegment {
|
|
3
|
+
/** Actions held down for every frame of this segment. */
|
|
4
|
+
hold?: string[];
|
|
5
|
+
/** Actions pressed only on the first frame of this segment (taps). */
|
|
6
|
+
press?: string[];
|
|
7
|
+
/** How many fixed steps this segment lasts (default 1). */
|
|
8
|
+
frames?: number;
|
|
9
|
+
}
|
|
10
|
+
export type InputScript = ScriptSegment[];
|
|
11
|
+
/** Expand a script into per-frame action arrays (an InputLog's frames). */
|
|
12
|
+
export declare function scriptToFrames(script: InputScript): string[][];
|
|
13
|
+
export interface DriveResult {
|
|
14
|
+
frames: number;
|
|
15
|
+
/** True if driving stopped early because `until` matched. */
|
|
16
|
+
matched: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Step a world through a script. Pass `until` to stop as soon as a probe
|
|
20
|
+
* predicate holds (e.g. level solved) — the tail of the script is slack, so
|
|
21
|
+
* playthrough scripts don't need frame-perfect lengths.
|
|
22
|
+
*/
|
|
23
|
+
export declare function drive(world: World, script: InputScript, until?: (probe: Record<string, unknown>) => boolean): DriveResult;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { World } from '../world';
|
|
2
|
+
export type ProbeFrame = Record<string, unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* Step a world through per-frame action arrays, probing after every step.
|
|
5
|
+
* Returns frames.length + 1 probes: index 0 is the pre-step state, index i is
|
|
6
|
+
* the state after frame i. (With the default 60Hz clock, index / 60 = sim
|
|
7
|
+
* seconds.) For a segment-level timeline use scriptedPlaythrough instead.
|
|
8
|
+
*/
|
|
9
|
+
export declare function recordTimeline(world: World, frames: string[][]): ProbeFrame[];
|
|
10
|
+
/** First timeline index where `pred` holds, or -1 if it never does. */
|
|
11
|
+
export declare function firstFrame(timeline: ProbeFrame[], pred: (p: ProbeFrame) => boolean): number;
|
|
12
|
+
/** The value of one probe key across the timeline. */
|
|
13
|
+
export declare function series(timeline: ProbeFrame[], key: string): unknown[];
|
|
14
|
+
/** Timeline indices where `key`'s value differs from the previous frame — an event cadence. */
|
|
15
|
+
export declare function changeFrames(timeline: ProbeFrame[], key: string): number[];
|
|
16
|
+
/** Monotonic within slack: 'up' means every value ≥ previous − slack. */
|
|
17
|
+
export declare function isMonotonic(values: number[], dir: 'up' | 'down', slack?: number): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Fraction of frames carrying at least one action — a crude engagement proxy.
|
|
20
|
+
* Near 0 the player is waiting; near 1 they are mashing; a tuned game usually
|
|
21
|
+
* lives somewhere in between (set the window per genre).
|
|
22
|
+
*/
|
|
23
|
+
export declare function inputDensity(frames: string[][]): number;
|
|
24
|
+
/** Largest gap (in frames) between consecutive events — dead-air detector. */
|
|
25
|
+
export declare function longestLull(eventFrames: number[], totalFrames: number): number;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { World } from '../world';
|
|
2
|
+
export interface FilmstripOptions {
|
|
3
|
+
/** The game's view size (defineGame width/height). */
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
background?: string;
|
|
7
|
+
/** How many panels to sample across the run (default 12, min 2). */
|
|
8
|
+
panels?: number;
|
|
9
|
+
/** Panels per row (default 4). */
|
|
10
|
+
cols?: number;
|
|
11
|
+
/** Rendered width of one panel in px (default 320). */
|
|
12
|
+
panelWidth?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Step `world` through per-frame actions, sampling evenly spaced panels
|
|
16
|
+
* (always including the first and last frame), and return one standalone SVG.
|
|
17
|
+
* Panel labels are frame numbers plus sim seconds at the 60Hz convention.
|
|
18
|
+
*/
|
|
19
|
+
export declare function renderFilmstrip(world: World, frames: string[][], opts: FilmstripOptions): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { World } from '../world';
|
|
2
|
+
export interface Segment {
|
|
3
|
+
/** Actions held down for this segment. */
|
|
4
|
+
actions?: string[];
|
|
5
|
+
/** Number of fixed steps to run. */
|
|
6
|
+
frames: number;
|
|
7
|
+
}
|
|
8
|
+
/** Build a segment: hold `actions` for `frames` steps. */
|
|
9
|
+
export declare function hold(actions: string[], frames: number): Segment;
|
|
10
|
+
/** Build an idle segment (no input) for `frames` steps. */
|
|
11
|
+
export declare function wait(frames: number): Segment;
|
|
12
|
+
export interface PlaythroughResult {
|
|
13
|
+
totalFrames: number;
|
|
14
|
+
finalHash: string;
|
|
15
|
+
/** Probe snapshot after each segment. */
|
|
16
|
+
probes: Record<string, unknown>[];
|
|
17
|
+
}
|
|
18
|
+
/** Run a scripted playthrough; returns a probe after each segment. */
|
|
19
|
+
export declare function scriptedPlaythrough(world: World, script: Segment[]): PlaythroughResult;
|
|
20
|
+
/** Convenience: advance a world N frames with a fixed input set. */
|
|
21
|
+
export declare function pump(world: World, frames: number, actions?: string[]): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface Puzzle<State, Move> {
|
|
2
|
+
/** Starting state (optionally per level index). */
|
|
3
|
+
initial(level?: number): State;
|
|
4
|
+
/** Legal moves from a state, in deterministic order. */
|
|
5
|
+
moves(state: State): Move[];
|
|
6
|
+
/** Apply a player move (may fold in a deterministic opponent response). */
|
|
7
|
+
apply(state: State, move: Move): State;
|
|
8
|
+
/** Win test. */
|
|
9
|
+
isWin(state: State): boolean;
|
|
10
|
+
/** Optional dead/lost test — pruned from the search. */
|
|
11
|
+
isDead?(state: State): boolean;
|
|
12
|
+
/** Canonical key for de-duplication (visited set). */
|
|
13
|
+
key(state: State): string;
|
|
14
|
+
}
|
|
15
|
+
export interface SolveResult<Move> {
|
|
16
|
+
solvable: boolean;
|
|
17
|
+
/** Shortest winning move sequence, if found. */
|
|
18
|
+
path?: Move[];
|
|
19
|
+
/** Depth of the shortest solution. */
|
|
20
|
+
depth?: number;
|
|
21
|
+
/** Nodes expanded (a difficulty/complexity proxy). */
|
|
22
|
+
nodes: number;
|
|
23
|
+
/** True if the search hit a cap before proving/disproving. */
|
|
24
|
+
exhausted: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface SolveOptions {
|
|
27
|
+
level?: number;
|
|
28
|
+
maxDepth?: number;
|
|
29
|
+
nodeCap?: number;
|
|
30
|
+
}
|
|
31
|
+
/** Breadth-first search → the shortest solution and a real winnability proof. */
|
|
32
|
+
export declare function solve<State, Move>(puzzle: Puzzle<State, Move>, options?: SolveOptions): SolveResult<Move>;
|
|
33
|
+
/**
|
|
34
|
+
* Assert a puzzle level is winnable (throws with detail if not). Use in tests as
|
|
35
|
+
* the content-balance gate.
|
|
36
|
+
*/
|
|
37
|
+
export declare function assertSolvable<State, Move>(puzzle: Puzzle<State, Move>, options?: SolveOptions): SolveResult<Move>;
|
package/dist/world.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Clock, type ClockConfig } from './core/clock';
|
|
2
|
+
import { EventBus } from './core/events';
|
|
3
|
+
import { Rng } from './core/rng';
|
|
4
|
+
import { InputState } from './input/actions';
|
|
5
|
+
import { Node, type WorldContext } from './scene/node';
|
|
6
|
+
import type { Camera2D } from './scene/nodes';
|
|
7
|
+
import { type Transform } from './core/math';
|
|
8
|
+
import type { DrawCommand } from './render/commands';
|
|
9
|
+
export interface WorldConfig {
|
|
10
|
+
seed?: number;
|
|
11
|
+
clock?: ClockConfig;
|
|
12
|
+
/** Design-space dimensions (default 1280×720). */
|
|
13
|
+
width?: number;
|
|
14
|
+
height?: number;
|
|
15
|
+
}
|
|
16
|
+
/** Default engine event map; games extend it with their own keys. */
|
|
17
|
+
export interface CoreEvents {
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
export declare class World implements WorldContext {
|
|
21
|
+
readonly rng: Rng;
|
|
22
|
+
readonly clock: Clock;
|
|
23
|
+
readonly input: InputState;
|
|
24
|
+
readonly events: EventBus<CoreEvents>;
|
|
25
|
+
readonly resources: Map<string, unknown>;
|
|
26
|
+
/**
|
|
27
|
+
* Canonical game state that lives OUTSIDE the scene tree (pure-sim structs,
|
|
28
|
+
* controllers). Must be plain JSON-serializable data: it is included in
|
|
29
|
+
* `hash()` and `snapshot()`, so hidden state here cannot escape determinism
|
|
30
|
+
* checks the way ad-hoc module variables can.
|
|
31
|
+
*/
|
|
32
|
+
state: Record<string, unknown>;
|
|
33
|
+
readonly width: number;
|
|
34
|
+
readonly height: number;
|
|
35
|
+
root: Node;
|
|
36
|
+
activeCamera: Camera2D | null;
|
|
37
|
+
private seed;
|
|
38
|
+
private freeQueue;
|
|
39
|
+
private started;
|
|
40
|
+
constructor(config?: WorldConfig);
|
|
41
|
+
get time(): number;
|
|
42
|
+
get frame(): number;
|
|
43
|
+
/** Replace the scene root, entering the tree. */
|
|
44
|
+
setRoot(node: Node): void;
|
|
45
|
+
requestFree(node: Node): void;
|
|
46
|
+
private ensureStarted;
|
|
47
|
+
/**
|
|
48
|
+
* Advance exactly one fixed step with the given actions held down.
|
|
49
|
+
* This is THE deterministic transition — call it from Node or the browser loop.
|
|
50
|
+
*/
|
|
51
|
+
step(actionsDown?: Iterable<string>): void;
|
|
52
|
+
/** Feed real elapsed ms; runs 0+ fixed steps. Returns steps run. */
|
|
53
|
+
advance(realMs: number, actionsDown?: Iterable<string>): number;
|
|
54
|
+
private flushFree;
|
|
55
|
+
/** The view transform (inverse of the active camera), mapping world → screen. */
|
|
56
|
+
viewTransform(): Transform;
|
|
57
|
+
/** Project the whole scene to a display list (already camera-applied). */
|
|
58
|
+
render(): DrawCommand[];
|
|
59
|
+
/** Deterministic structural hash of the whole sim state. */
|
|
60
|
+
hash(): string;
|
|
61
|
+
/** Compact snapshot for undo/time-travel and saves. */
|
|
62
|
+
snapshot(): WorldSnapshot;
|
|
63
|
+
/** Restore a snapshot. Rebuilds the tree from data (behaviors are re-attached by scene code). */
|
|
64
|
+
restore(snap: WorldSnapshot): void;
|
|
65
|
+
/** A compact probe snapshot for the verification harness (override-friendly). */
|
|
66
|
+
probe(): Record<string, unknown>;
|
|
67
|
+
}
|
|
68
|
+
export interface WorldSnapshot {
|
|
69
|
+
seed: number;
|
|
70
|
+
rng: ReturnType<Rng['getState']>;
|
|
71
|
+
clock: ReturnType<Clock['getState']>;
|
|
72
|
+
input: ReturnType<InputState['getState']>;
|
|
73
|
+
state: Record<string, unknown>;
|
|
74
|
+
tree: ReturnType<Node['serialize']>;
|
|
75
|
+
}
|
package/docs/API.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# hayao.js — Public API digest
|
|
2
|
+
|
|
3
|
+
> Auto-generated by `npm run api`. Everything importable from `@hayao`.
|
|
4
|
+
> Grep here for the real surface — never guess an API from memory.
|
|
5
|
+
|
|
6
|
+
**201 exports.**
|
|
7
|
+
|
|
8
|
+
## classs (22)
|
|
9
|
+
|
|
10
|
+
- `AnimationPlayer`
|
|
11
|
+
- `AudioBus`
|
|
12
|
+
- `Camera2D`
|
|
13
|
+
- `Canvas2DRenderer`
|
|
14
|
+
- `Clock`
|
|
15
|
+
- `EventBus`
|
|
16
|
+
- `HeadlessRenderer`
|
|
17
|
+
- `InputRecorder`
|
|
18
|
+
- `InputState`
|
|
19
|
+
- `KeyboardSource`
|
|
20
|
+
- `Node`
|
|
21
|
+
- `Particles`
|
|
22
|
+
- `Rng`
|
|
23
|
+
- `Shaker`
|
|
24
|
+
- `Shell`
|
|
25
|
+
- `Signal`
|
|
26
|
+
- `SpatialHash`
|
|
27
|
+
- `Sprite`
|
|
28
|
+
- `SvgRenderer`
|
|
29
|
+
- `Text`
|
|
30
|
+
- `Timer`
|
|
31
|
+
- `World`
|
|
32
|
+
|
|
33
|
+
## functions (73)
|
|
34
|
+
|
|
35
|
+
- `applyTransform`: (m: Transform, p: Vec2) => Vec2
|
|
36
|
+
- `asciiEntities`: (rowsAscii: string[], tileSize?: number, chars?: Record<string, TileId>) => { char: string; tx: number; ty: number; x: number; y: number; }[]
|
|
37
|
+
- `assertDeterministic`: (makeWorld: WorldFactory, log: InputLog) => DeterminismReport
|
|
38
|
+
- `assertSnapshotStable`: (makeWorld: WorldFactory, log: InputLog, warmup: number) => { ok: boolean; hashA: string; hashB: string; }
|
|
39
|
+
- `assertSolvable`: <State, Move>(puzzle: Puzzle<State, Move>, options?: SolveOptions) => SolveResult<Move>
|
|
40
|
+
- `blobPath`: (rng: Rng, radius: number, wobble?: number, lobes?: number) => string
|
|
41
|
+
- `changeFrames`: (timeline: ProbeFrame[], key: string) => number[]
|
|
42
|
+
- `checkDeterministic`: (makeWorld: WorldFactory, log: InputLog) => DeterminismReport
|
|
43
|
+
- `commandsToSVGInner`: (commands: DrawCommand[]) => string
|
|
44
|
+
- `composeTransform`: (m: Transform, n: Transform) => Transform
|
|
45
|
+
- `createPlanBot`: <Step extends { kind: string; }, Probe>(plan: Step[], execs: Record<string, StepExec<Step, Probe>>) => PlanBot<Probe>
|
|
46
|
+
- `createPlatformerState`: (x: number, y: number) => PlatformerState
|
|
47
|
+
- `createWorld`: (def: GameDefinition, seedOverride?: number | undefined) => World
|
|
48
|
+
- `dashJumpDistance`: (cfg?: PlatformerConfig) => number
|
|
49
|
+
- `defineGame`: (def: GameDefinition) => Required<Pick<GameDefinition, "width" | "height" | "seed" | "inputMap" | "background">> & GameDefinition
|
|
50
|
+
- `deserializeNode`: (data: SerializedNode) => Node
|
|
51
|
+
- `drive`: (world: World, script: InputScript, until?: ((probe: Record<string, unknown>) => boolean) | undefined) => DriveResult
|
|
52
|
+
- `firstFrame`: (timeline: ProbeFrame[], pred: (p: ProbeFrame) => boolean) => number
|
|
53
|
+
- `frameActions`: (log: InputLog, i: number) => string[]
|
|
54
|
+
- `gameInputMap`: (def: GameDefinition) => InputMap
|
|
55
|
+
- `groundAt`: (map: TilemapData, x: number, y: number, w: number, h: number, solids?: SolidRect[], dropThrough?: boolean) => { grounded: boolean; solid: number; }
|
|
56
|
+
- `hashString`: (str: string) => number
|
|
57
|
+
- `hashValue`: (value: unknown) => string
|
|
58
|
+
- `hideScreen`: () => void
|
|
59
|
+
- `hold`: (actions: string[], frames: number) => Segment
|
|
60
|
+
- `inputDensity`: (frames: string[][]) => number
|
|
61
|
+
- `installCapture`: (target: CaptureTarget) => HayaoCapture
|
|
62
|
+
- `invertTransform`: (m: Transform) => Transform
|
|
63
|
+
- `inVisionCone`: (map: TilemapData, ex: number, ey: number, faceX: number, faceY: number, fov: number, range: number, tx: number, ty: number) => boolean
|
|
64
|
+
- `isCaptureMode`: () => boolean
|
|
65
|
+
- `isMonotonic`: (values: number[], dir: "up" | "down", slack?: number) => boolean
|
|
66
|
+
- `jumpAirtime`: (cfg?: PlatformerConfig) => number
|
|
67
|
+
- `jumpDistance`: (cfg?: PlatformerConfig) => number
|
|
68
|
+
- `jumpHeight`: (cfg?: PlatformerConfig) => number
|
|
69
|
+
- `keysToActions`: (map: InputMap, keysDown: Set<string>) => string[]
|
|
70
|
+
- `lineOfSight`: (map: TilemapData, ax: number, ay: number, bx: number, by: number) => boolean
|
|
71
|
+
- `longestLull`: (eventFrames: number[], totalFrames: number) => number
|
|
72
|
+
- `makeTransform`: (pos: Vec2, rotation: number, scale: Vec2) => Transform
|
|
73
|
+
- `mix`: (a: string, b: string, t: number) => string
|
|
74
|
+
- `moveRect`: (map: TilemapData, rect: Rect, dx: number, dy: number, opts?: MoveOpts) => MoveResult
|
|
75
|
+
- `pump`: (world: World, frames: number, actions?: string[]) => void
|
|
76
|
+
- `raycastTiles`: (map: TilemapData, x0: number, y0: number, x1: number, y1: number) => RayHit
|
|
77
|
+
- `recordTimeline`: (world: World, frames: string[][]) => ProbeFrame[]
|
|
78
|
+
- `rectBlocked`: (map: TilemapData, x: number, y: number, w: number, h: number, solids?: SolidRect[]) => boolean
|
|
79
|
+
- `rectContains`: (r: Rect, p: Vec2) => boolean
|
|
80
|
+
- `rectsOverlap`: (a: Rect, b: Rect) => boolean
|
|
81
|
+
- `registerNode`: (type: string, factory: NodeFactory) => void
|
|
82
|
+
- `regularPolygon`: (sides: number, radius: number, rotation?: number) => number[]
|
|
83
|
+
- `renderFilmstrip`: (world: World, frames: string[][], opts: FilmstripOptions) => string
|
|
84
|
+
- `renderToSVGString`: (commands: DrawCommand[], width: number, height: number, background?: string) => string
|
|
85
|
+
- `replay`: (makeWorld: WorldFactory, log: InputLog) => { finalHash: string; hashes: string[]; }
|
|
86
|
+
- `resetNodeIds`: (start?: number) => void
|
|
87
|
+
- `runBrowser`: (def: GameDefinition, mount: HTMLElement, opts?: RunOptions) => GameHandle
|
|
88
|
+
- `runHeadless`: (def: GameDefinition, inputLog?: InputLog | undefined) => HeadlessResult
|
|
89
|
+
- `scriptedPlaythrough`: (world: World, script: Segment[]) => PlaythroughResult
|
|
90
|
+
- `scriptToFrames`: (script: InputScript) => string[][]
|
|
91
|
+
- `series`: (timeline: ProbeFrame[], key: string) => unknown[]
|
|
92
|
+
- `setOverlayHost`: (el: HTMLElement) => void
|
|
93
|
+
- `showScreen`: (spec: ScreenSpec) => ScreenHandle
|
|
94
|
+
- `smoothClosedPath`: (points: Vec2[], tension?: number) => string
|
|
95
|
+
- `smoothOpenPath`: (points: Vec2[], tension?: number) => string
|
|
96
|
+
- `solve`: <State, Move>(puzzle: Puzzle<State, Move>, options?: SolveOptions) => SolveResult<Move>
|
|
97
|
+
- `sortCommands`: (cmds: DrawCommand[]) => DrawCommand[]
|
|
98
|
+
- `star`: (points: number, outer: number, inner: number, rotation?: number) => number[]
|
|
99
|
+
- `steer2D`: (px: number, py: number, tx: number, ty: number, out: string[], dead?: number) => void
|
|
100
|
+
- `stepPlatformer`: (s: PlatformerState, input: PadInput, dt: number, map: TilemapData, cfg?: PlatformerConfig, platforms?: Platform[]) => PlatformerEvents
|
|
101
|
+
- `tileAt`: (map: TilemapData, tx: number, ty: number) => number
|
|
102
|
+
- `tileAtPoint`: (map: TilemapData, x: number, y: number) => number
|
|
103
|
+
- `tilemapFromAscii`: (rowsAscii: string[], tileSize?: number, chars?: Record<string, TileId>) => TilemapData
|
|
104
|
+
- `toggleFullscreen`: () => void
|
|
105
|
+
- `vnorm`: (a: Vec2) => Vec2
|
|
106
|
+
- `wait`: (frames: number) => Segment
|
|
107
|
+
- `withAlpha`: (hex: string, alpha: number) => string
|
|
108
|
+
|
|
109
|
+
## interfaces (62)
|
|
110
|
+
|
|
111
|
+
- `Behavior`
|
|
112
|
+
- `BotCtx`
|
|
113
|
+
- `CameraConfig`
|
|
114
|
+
- `CaptureTarget`
|
|
115
|
+
- `CircleCommand`
|
|
116
|
+
- `ClockConfig`
|
|
117
|
+
- `CoreEvents`
|
|
118
|
+
- `DeterminismReport`
|
|
119
|
+
- `DriveResult`
|
|
120
|
+
- `FilmstripOptions`
|
|
121
|
+
- `GameDefinition`
|
|
122
|
+
- `GameHandle`
|
|
123
|
+
- `HayaoCapture`
|
|
124
|
+
- `HeadlessResult`
|
|
125
|
+
- `ImageCommand`
|
|
126
|
+
- `InputLog`
|
|
127
|
+
- `MenuAction`
|
|
128
|
+
- `MoveOpts`
|
|
129
|
+
- `MoveResult`
|
|
130
|
+
- `NodeConfig`
|
|
131
|
+
- `PadInput`
|
|
132
|
+
- `Paint`
|
|
133
|
+
- `Palette`
|
|
134
|
+
- `ParticleStyle`
|
|
135
|
+
- `PathCommand`
|
|
136
|
+
- `PlanBot`
|
|
137
|
+
- `Platform`
|
|
138
|
+
- `PlatformerConfig`
|
|
139
|
+
- `PlatformerEvents`
|
|
140
|
+
- `PlatformerState`
|
|
141
|
+
- `PlaythroughResult`
|
|
142
|
+
- `PolyCommand`
|
|
143
|
+
- `Puzzle`
|
|
144
|
+
- `RayHit`
|
|
145
|
+
- `Rect`
|
|
146
|
+
- `RectCommand`
|
|
147
|
+
- `Renderer`
|
|
148
|
+
- `RendererConfig`
|
|
149
|
+
- `RngState`
|
|
150
|
+
- `RunOptions`
|
|
151
|
+
- `ScreenHandle`
|
|
152
|
+
- `ScreenSpec`
|
|
153
|
+
- `ScriptSegment`
|
|
154
|
+
- `Segment`
|
|
155
|
+
- `SerializedNode`
|
|
156
|
+
- `Settings`
|
|
157
|
+
- `ShellOptions`
|
|
158
|
+
- `SolidRect`
|
|
159
|
+
- `SolveOptions`
|
|
160
|
+
- `SolveResult`
|
|
161
|
+
- `SpriteConfig`
|
|
162
|
+
- `TextCommand`
|
|
163
|
+
- `TextConfig`
|
|
164
|
+
- `TilemapData`
|
|
165
|
+
- `TimerConfig`
|
|
166
|
+
- `Tone`
|
|
167
|
+
- `Transform`
|
|
168
|
+
- `Vec2`
|
|
169
|
+
- `Volumes`
|
|
170
|
+
- `WorldConfig`
|
|
171
|
+
- `WorldContext`
|
|
172
|
+
- `WorldSnapshot`
|
|
173
|
+
|
|
174
|
+
## types (12)
|
|
175
|
+
|
|
176
|
+
- `DrawCommand`
|
|
177
|
+
- `Easing`
|
|
178
|
+
- `InputMap`
|
|
179
|
+
- `InputScript`
|
|
180
|
+
- `Listener`
|
|
181
|
+
- `NodeFactory`
|
|
182
|
+
- `ProbeFrame`
|
|
183
|
+
- `Shape`
|
|
184
|
+
- `StepExec`
|
|
185
|
+
- `TextAlign`
|
|
186
|
+
- `TileId`
|
|
187
|
+
- `WorldFactory`
|
|
188
|
+
|
|
189
|
+
## consts (31)
|
|
190
|
+
|
|
191
|
+
- `audio`: AudioBus
|
|
192
|
+
- `clamp`: (v: number, lo: number, hi: number) => number
|
|
193
|
+
- `DEFAULT_INPUT_MAP`: InputMap
|
|
194
|
+
- `DEFAULT_PLATFORMER`: PlatformerConfig
|
|
195
|
+
- `DEFAULT_TILE_CHARS`: Record<string, TileId>
|
|
196
|
+
- `deg2rad`: (d: number) => number
|
|
197
|
+
- `DUSK`: Palette
|
|
198
|
+
- `EASINGS`: Record<string, Easing>
|
|
199
|
+
- `IDENTITY`: Transform
|
|
200
|
+
- `invLerp`: (a: number, b: number, v: number) => number
|
|
201
|
+
- `lerp`: (a: number, b: number, t: number) => number
|
|
202
|
+
- `mapHeight`: (map: TilemapData) => number
|
|
203
|
+
- `mapWidth`: (map: TilemapData) => number
|
|
204
|
+
- `MEADOW`: Palette
|
|
205
|
+
- `PAD_NEUTRAL`: PadInput
|
|
206
|
+
- `PALETTES`: Record<string, Palette>
|
|
207
|
+
- `PAPER`: Palette
|
|
208
|
+
- `PARTICLE_PRESETS`: { readonly dust: (colors?: string[]) => ParticleStyle; readonly burst: (colors?: string[]) => ParticleStyle; readonly hit: (colors?: string[]) => ParticleSty…
|
|
209
|
+
- `rad2deg`: (r: number) => number
|
|
210
|
+
- `remap`: (v: number, a0: number, a1: number, b0: number, b1: number) => number
|
|
211
|
+
- `settings`: SettingsStore
|
|
212
|
+
- `TAU`: number
|
|
213
|
+
- `TILE`: { readonly EMPTY: 0; readonly SOLID: 1; readonly ONEWAY: 2; readonly HAZARD: 3; }
|
|
214
|
+
- `vadd`: (a: Vec2, b: Vec2) => Vec2
|
|
215
|
+
- `vdist`: (a: Vec2, b: Vec2) => number
|
|
216
|
+
- `vdot`: (a: Vec2, b: Vec2) => number
|
|
217
|
+
- `vec2`: (x?: number, y?: number) => Vec2
|
|
218
|
+
- `VERSION`: "0.1.0"
|
|
219
|
+
- `vlen`: (a: Vec2) => number
|
|
220
|
+
- `vscale`: (a: Vec2, s: number) => Vec2
|
|
221
|
+
- `vsub`: (a: Vec2, b: Vec2) => Vec2
|
|
222
|
+
|
|
223
|
+
## exports (1)
|
|
224
|
+
|
|
225
|
+
- `Node2D`
|
|
226
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hayao",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "An AI-first game engine: a deterministic, headless-native simulation kernel with a Godot-style scene tree, pluggable renderers (SVG/Canvas/headless), and a built-in verification harness — designed so an LLM can author, test, and prove correct a whole game without ever opening a browser.",
|
|
6
|
+
"keywords": ["game-engine", "ai-first", "deterministic", "typescript", "svg", "godot-like", "headless", "simulation"],
|
|
7
|
+
"author": "hellojanpacan",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": { "type": "git", "url": "git+https://github.com/hellojanpacan/hayao-js.git" },
|
|
10
|
+
"homepage": "https://github.com/hellojanpacan/hayao-js#readme",
|
|
11
|
+
"bugs": { "url": "https://github.com/hellojanpacan/hayao-js/issues" },
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"module": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": ["dist", "docs/API.md"],
|
|
22
|
+
"engines": { "node": ">=18" },
|
|
23
|
+
"publishConfig": { "access": "public" },
|
|
24
|
+
"scripts": {
|
|
25
|
+
"dev": "vite",
|
|
26
|
+
"build": "tsc --noEmit && vite build",
|
|
27
|
+
"build:lib": "rm -rf dist && esbuild src/index.ts --bundle --format=esm --platform=neutral --target=es2022 --outfile=dist/index.js --sourcemap && tsc -p tsconfig.build.json",
|
|
28
|
+
"check": "tsc --noEmit",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"test:watch": "vitest",
|
|
31
|
+
"api": "tsx scripts/api-digest.ts",
|
|
32
|
+
"invariants": "tsx scripts/invariants.ts",
|
|
33
|
+
"verify": "tsx scripts/invariants.ts && tsx scripts/verify.ts",
|
|
34
|
+
"prepublishOnly": "npm run check && npm test && npm run build:lib"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.10.0",
|
|
38
|
+
"esbuild": "^0.28.0",
|
|
39
|
+
"tsx": "^4.19.0",
|
|
40
|
+
"typescript": "^5.7.0",
|
|
41
|
+
"vite": "^6.0.0",
|
|
42
|
+
"vitest": "^2.1.0"
|
|
43
|
+
}
|
|
44
|
+
}
|