hayao 0.2.0 → 0.4.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 (83) hide show
  1. package/README.md +123 -24
  2. package/bin/create-hayao.mjs +380 -0
  3. package/bin/hayao-mcp-cli.mjs +11 -0
  4. package/dist/app/browser.d.ts +65 -4
  5. package/dist/app/game.d.ts +59 -10
  6. package/dist/app/tuning.d.ts +68 -0
  7. package/dist/art/palette.d.ts +41 -0
  8. package/dist/audio/adaptive.d.ts +58 -0
  9. package/dist/audio/album.d.ts +16 -0
  10. package/dist/audio/analysis.d.ts +59 -0
  11. package/dist/audio/audio.d.ts +39 -1
  12. package/dist/audio/chord.d.ts +17 -0
  13. package/dist/audio/genres.d.ts +11 -0
  14. package/dist/audio/lint.d.ts +29 -0
  15. package/dist/audio/match.d.ts +38 -0
  16. package/dist/audio/music.d.ts +88 -0
  17. package/dist/audio/pcm.d.ts +54 -0
  18. package/dist/audio/quality.d.ts +28 -0
  19. package/dist/audio/reverb.d.ts +15 -0
  20. package/dist/audio/synth.d.ts +63 -0
  21. package/dist/audio/theory.d.ts +64 -0
  22. package/dist/audio/zzfx.d.ts +14 -0
  23. package/dist/content/campaign.d.ts +69 -0
  24. package/dist/content/generate.d.ts +78 -0
  25. package/dist/content/level.d.ts +93 -0
  26. package/dist/content/worldgraph.d.ts +73 -0
  27. package/dist/core/clock.d.ts +1 -1
  28. package/dist/core/dmath.d.ts +2 -0
  29. package/dist/core/projection.d.ts +36 -0
  30. package/dist/core/rng.d.ts +9 -0
  31. package/dist/hayao.global.js +200 -0
  32. package/dist/index.d.ts +39 -1
  33. package/dist/index.js +6558 -686
  34. package/dist/index.js.map +4 -4
  35. package/dist/index.min.js +200 -0
  36. package/dist/input/actions.d.ts +60 -6
  37. package/dist/input/gamepad.d.ts +101 -0
  38. package/dist/input/source.d.ts +133 -1
  39. package/dist/logic/coroutine.d.ts +68 -0
  40. package/dist/mcp.js +31225 -0
  41. package/dist/rasterize-worker-lite.mjs +13 -0
  42. package/dist/render/canvas.d.ts +6 -5
  43. package/dist/render/canvas2d-core.d.ts +9 -0
  44. package/dist/render/commands.d.ts +82 -2
  45. package/dist/render/paint.d.ts +41 -0
  46. package/dist/render/renderer.d.ts +47 -0
  47. package/dist/render/svg.d.ts +5 -1
  48. package/dist/render/svgString.d.ts +6 -2
  49. package/dist/render/webgl.d.ts +176 -0
  50. package/dist/scene/cameraController.d.ts +42 -0
  51. package/dist/scene/iso.d.ts +73 -0
  52. package/dist/scene/node.d.ts +98 -6
  53. package/dist/scene/nodes.d.ts +48 -0
  54. package/dist/scene/parallax.d.ts +15 -0
  55. package/dist/scene/particles.d.ts +19 -0
  56. package/dist/scene/verletChain.d.ts +76 -0
  57. package/dist/studio/mcpMain.d.ts +1 -0
  58. package/dist/studio/mcpServer.d.ts +2 -0
  59. package/dist/studio/record.d.ts +54 -0
  60. package/dist/studio/run.d.ts +78 -0
  61. package/dist/studio/session.d.ts +80 -0
  62. package/dist/studio/timeline.d.ts +35 -0
  63. package/dist/studio/vitePlugin.d.ts +6 -0
  64. package/dist/studio-plugin.js +228 -0
  65. package/dist/ui/overlay.d.ts +6 -0
  66. package/dist/ui/touch.d.ts +51 -0
  67. package/dist/verify/audioFilmstrip.d.ts +39 -0
  68. package/dist/verify/dom.d.ts +26 -0
  69. package/dist/verify/ethnography.d.ts +67 -0
  70. package/dist/verify/gates.d.ts +160 -0
  71. package/dist/verify/layout.d.ts +30 -3
  72. package/dist/verify/ramp.d.ts +40 -0
  73. package/dist/world.d.ts +109 -8
  74. package/dist-studio/assets/index-C7tty_Wo.js +109 -0
  75. package/dist-studio/assets/index-CM3tjRQo.css +1 -0
  76. package/dist-studio/index.html +18 -0
  77. package/docs/API.md +300 -23
  78. package/docs/CONVENTIONS.md +511 -0
  79. package/docs/EMBED.md +90 -0
  80. package/docs/QUICKSTART.md +141 -0
  81. package/docs/STUDIO.md +97 -0
  82. package/docs/VERIFICATION.md +256 -0
  83. package/package.json +40 -6
@@ -0,0 +1,13 @@
1
+ // Lite rasterize worker shipped in the npm package (dist/): SVG file → PNG in
2
+ // an isolated process, no repo-only helpers (no sanitize pass, system fonts).
3
+ // @resvg/resvg-js resolves from the CONSUMER's node_modules — install it as a
4
+ // devDependency to enable inspect_moment PNGs; without it this exits non-zero
5
+ // and the MCP server degrades to probe + hash.
6
+ // node rasterize-worker-lite.mjs <in.svg> <width> <out.png>
7
+ import { readFileSync, writeFileSync } from 'node:fs';
8
+
9
+ const [, , svgPath, width, outPath] = process.argv;
10
+ const { Resvg } = await import('@resvg/resvg-js');
11
+ const svg = readFileSync(svgPath, 'utf8');
12
+ const png = new Resvg(svg, { fitTo: { mode: 'width', value: Number(width) }, font: { loadSystemFonts: true } }).render().asPng();
13
+ writeFileSync(outPath, png);
@@ -1,5 +1,6 @@
1
- import { type DrawCommand } from './commands';
2
- import type { Renderer, RendererConfig } from './renderer';
1
+ import type { DrawCommand } from './commands';
2
+ import { type Renderer, type RendererConfig, type Viewport } from './renderer';
3
+ import type { Vec2 } from '../core/math';
3
4
  export declare class Canvas2DRenderer implements Renderer {
4
5
  readonly width: number;
5
6
  readonly height: number;
@@ -11,9 +12,9 @@ export declare class Canvas2DRenderer implements Renderer {
11
12
  mount(parent: HTMLElement): void;
12
13
  private resize;
13
14
  draw(commands: DrawCommand[]): void;
14
- private stroke;
15
- private paint;
16
- private roundRect;
17
15
  get element(): HTMLCanvasElement;
16
+ /** Map a pointer event's clientX/Y into design space (undoes the letterbox). */
17
+ toDesign(clientX: number, clientY: number): Vec2;
18
+ viewport(): Viewport;
18
19
  dispose(): void;
19
20
  }
@@ -0,0 +1,9 @@
1
+ import { type DrawCommand } from './commands';
2
+ type Ctx2D = CanvasRenderingContext2D;
3
+ /**
4
+ * Paint a sorted display list onto an existing Canvas2D context at `scale`
5
+ * (device pixel ratio). The caller is responsible for sizing the canvas to
6
+ * `width * scale` × `height * scale` before calling.
7
+ */
8
+ export declare function drawToCanvas2D(ctx: Ctx2D, commands: DrawCommand[], width: number, height: number, background: string, scale: number): void;
9
+ export {};
@@ -1,4 +1,39 @@
1
1
  import type { Transform } from '../core/math';
2
+ /** One color stop of a gradient. `offset` is 0..1 along the gradient axis. */
3
+ export interface GradientStop {
4
+ offset: number;
5
+ color: string;
6
+ }
7
+ /**
8
+ * A gradient fill in OBJECT-BOUNDING-BOX space: all coordinates are 0..1
9
+ * relative to the painted shape's bounds, so the same gradient reads correctly
10
+ * on a shape of any size or position (matches SVG `gradientUnits`).
11
+ */
12
+ export interface LinearGradient {
13
+ type: 'linear';
14
+ x1: number;
15
+ y1: number;
16
+ x2: number;
17
+ y2: number;
18
+ stops: GradientStop[];
19
+ }
20
+ export interface RadialGradient {
21
+ type: 'radial';
22
+ /** Center + radius, all 0..1 in object-bounding-box space. */
23
+ cx: number;
24
+ cy: number;
25
+ r: number;
26
+ stops: GradientStop[];
27
+ }
28
+ export type Gradient = LinearGradient | RadialGradient;
29
+ /** A soft shadow / outer glow. `dx=dy=0` is a symmetric glow; offset = drop. */
30
+ export interface Shadow {
31
+ color: string;
32
+ /** Blur radius in local px. */
33
+ blur: number;
34
+ dx?: number;
35
+ dy?: number;
36
+ }
2
37
  export interface Paint {
3
38
  fill?: string;
4
39
  stroke?: string;
@@ -6,6 +41,15 @@ export interface Paint {
6
41
  opacity?: number;
7
42
  /** Round line joins/caps for organic shapes. */
8
43
  round?: boolean;
44
+ /** A gradient fill; when present it overrides `fill` for the shape body. */
45
+ gradient?: Gradient;
46
+ /** A soft outer glow / drop shadow applied to the shape. */
47
+ shadow?: Shadow;
48
+ /**
49
+ * Dash pattern for strokes, in local px (`[dash, gap, …]` — same as
50
+ * Canvas2D `setLineDash` / SVG `stroke-dasharray`). Omit for solid lines.
51
+ */
52
+ lineDash?: number[];
9
53
  }
10
54
  export type TextAlign = 'left' | 'center' | 'right';
11
55
  interface Base extends Paint {
@@ -13,6 +57,20 @@ interface Base extends Paint {
13
57
  transform: Transform;
14
58
  /** Painter's-order key; ties broken by tree order. Default 0. */
15
59
  z: number;
60
+ /**
61
+ * Render layer: commands sort by layer FIRST, then z, then tree order.
62
+ * Layer 0 (default) is the world; layer 1 is the screen-space overlay pass
63
+ * (HUD, transitions) — set automatically for `Node.screenSpace` subtrees, so
64
+ * overlays always paint above world content regardless of z values.
65
+ */
66
+ layer?: number;
67
+ /**
68
+ * Transient view chrome — a drifting popup, particle, or tween that lives for
69
+ * a moment and is never something the player reads for meaning. Layout lints
70
+ * (see verify/layout) skip these by default: a "+10" floating over a HUD label
71
+ * is motion, not a collision. Set by cosmetic emitters (FloatingText/Particles).
72
+ */
73
+ transient?: boolean;
16
74
  }
17
75
  export interface RectCommand extends Base {
18
76
  kind: 'rect';
@@ -29,6 +87,28 @@ export interface CircleCommand extends Base {
29
87
  cy: number;
30
88
  radius: number;
31
89
  }
90
+ export interface EllipseCommand extends Base {
91
+ kind: 'ellipse';
92
+ cx: number;
93
+ cy: number;
94
+ rx: number;
95
+ ry: number;
96
+ }
97
+ /**
98
+ * A circular arc (open stroke) or sector/pie (closed to the center).
99
+ * Angles are radians, clockwise from +x (screen convention, y-down);
100
+ * the arc runs from `start` to `end` in the clockwise direction.
101
+ */
102
+ export interface ArcCommand extends Base {
103
+ kind: 'arc';
104
+ cx: number;
105
+ cy: number;
106
+ radius: number;
107
+ start: number;
108
+ end: number;
109
+ /** When true, close through the center (a pie slice) so `fill` reads as a sector. */
110
+ sector?: boolean;
111
+ }
32
112
  export interface PolyCommand extends Base {
33
113
  kind: 'poly';
34
114
  /** Flat [x0,y0,x1,y1,…] in local space. */
@@ -59,7 +139,7 @@ export interface ImageCommand extends Base {
59
139
  w: number;
60
140
  h: number;
61
141
  }
62
- export type DrawCommand = RectCommand | CircleCommand | PolyCommand | PathCommand | TextCommand | ImageCommand;
63
- /** Stable painter's sort: by z, then original index (tree order) as tiebreak. */
142
+ export type DrawCommand = RectCommand | CircleCommand | EllipseCommand | ArcCommand | PolyCommand | PathCommand | TextCommand | ImageCommand;
143
+ /** Stable painter's sort: by layer, then z, then original index (tree order) as tiebreak. */
64
144
  export declare function sortCommands(cmds: DrawCommand[]): DrawCommand[];
65
145
  export {};
@@ -0,0 +1,41 @@
1
+ import type { DrawCommand, Gradient, GradientStop, LinearGradient, RadialGradient, Shadow } from './commands';
2
+ type StopInput = string | GradientStop;
3
+ /**
4
+ * A linear gradient by angle in degrees (0 = left→right, 90 = top→bottom).
5
+ * Stops are colors (auto-spaced) or explicit `{offset, color}`.
6
+ */
7
+ export declare function linearGradient(stops: readonly StopInput[], angleDeg?: number): LinearGradient;
8
+ /** A radial gradient centered in the shape by default (inner stop first). */
9
+ export declare function radialGradient(stops: readonly StopInput[], opts?: {
10
+ cx?: number;
11
+ cy?: number;
12
+ r?: number;
13
+ }): RadialGradient;
14
+ /** A symmetric soft outer glow. */
15
+ export declare function glow(color: string, blur: number): Shadow;
16
+ /** A soft drop shadow, offset by (dx,dy). */
17
+ export declare function dropShadow(color: string, blur: number, dx?: number, dy?: number): Shadow;
18
+ export declare function gradientDef(g: Gradient, id: string): string;
19
+ export declare function shadowDef(s: Shadow, id: string): string;
20
+ export interface BBox {
21
+ x: number;
22
+ y: number;
23
+ w: number;
24
+ h: number;
25
+ }
26
+ /** Local-space bounding box of a shape command (null for paths/text/image). */
27
+ export declare function shapeBBox(c: DrawCommand): BBox | null;
28
+ /** Why a command's geometry is unpaintable, or null when it's fine. */
29
+ export declare function invalidCommandReason(c: DrawCommand): string | null;
30
+ /**
31
+ * console.warn once per (kind + reason) — a bad value animating every frame
32
+ * logs a single line, not sixty a second. `detail` (the offending command or
33
+ * error) is passed through so the console shows the actual value.
34
+ */
35
+ export declare function warnCommandOnce(kind: string, reason: string, detail?: unknown): void;
36
+ /** Build a Canvas gradient mapped from object-bounding-box space into `bbox`. */
37
+ export declare function canvasGradient(ctx: {
38
+ createLinearGradient(x0: number, y0: number, x1: number, y1: number): CanvasGradient;
39
+ createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): CanvasGradient;
40
+ }, g: Gradient, bbox: BBox): CanvasGradient;
41
+ export {};
@@ -1,4 +1,5 @@
1
1
  import type { DrawCommand } from './commands';
2
+ import type { Vec2 } from '../core/math';
2
3
  /** A backend that paints a display list into a host (DOM, canvas, or nothing). */
3
4
  export interface Renderer {
4
5
  readonly width: number;
@@ -7,10 +8,56 @@ export interface Renderer {
7
8
  draw(commands: DrawCommand[]): void;
8
9
  /** Attach to a DOM parent (browser backends). */
9
10
  mount?(parent: HTMLElement): void;
11
+ /** The mounted DOM node (canvas or svg), for attaching pointer listeners. */
12
+ readonly element?: HTMLElement | SVGElement;
13
+ /**
14
+ * Invert the display transform: a browser pointer event's clientX/clientY →
15
+ * design-space coordinates. The backend owns the letterbox (`object-fit:
16
+ * contain` / `preserveAspectRatio`) and DPR, so it's the only place that can
17
+ * undo them correctly. Returns design coords even outside the letterbox
18
+ * (values fall outside 0..width/height there).
19
+ */
20
+ toDesign?(clientX: number, clientY: number): Vec2;
21
+ /**
22
+ * The drawn (letterboxed) design area within the mount, in mount-local px:
23
+ * `{ x, y, width, height, scale }`. Host-drawn UI (floating touch controls,
24
+ * DOM HUD) anchors to this rect instead of re-deriving the `object-fit:
25
+ * contain` math the backend already does for `toDesign`. Undefined for
26
+ * headless backends.
27
+ */
28
+ viewport?(): Viewport;
10
29
  dispose?(): void;
11
30
  }
31
+ /** The drawn area within the mount: offset + size in host px, plus design→px scale. */
32
+ export interface Viewport {
33
+ x: number;
34
+ y: number;
35
+ width: number;
36
+ height: number;
37
+ scale: number;
38
+ }
12
39
  export interface RendererConfig {
13
40
  width: number;
14
41
  height: number;
15
42
  background?: string;
16
43
  }
44
+ /**
45
+ * Undo a centered uniform-fit (`object-fit: contain` / SVG `xMidYMid meet`):
46
+ * map a client pixel inside `rect` back to the design box `width×height`. Shared
47
+ * by every DOM backend so the letterbox math lives in exactly one place.
48
+ */
49
+ export declare function clientToDesign(rect: {
50
+ left: number;
51
+ top: number;
52
+ width: number;
53
+ height: number;
54
+ }, width: number, height: number, clientX: number, clientY: number): Vec2;
55
+ /**
56
+ * The centered uniform-fit rect for a design box inside an element of size
57
+ * `rect` — the inverse framing of `clientToDesign`, in element-local px. Shared
58
+ * by every DOM backend's `viewport()`.
59
+ */
60
+ export declare function fitViewport(rect: {
61
+ width: number;
62
+ height: number;
63
+ }, width: number, height: number): Viewport;
@@ -1,5 +1,6 @@
1
1
  import type { DrawCommand } from './commands';
2
- import type { Renderer, RendererConfig } from './renderer';
2
+ import { type Renderer, type RendererConfig, type Viewport } from './renderer';
3
+ import type { Vec2 } from '../core/math';
3
4
  export declare class SvgRenderer implements Renderer {
4
5
  readonly width: number;
5
6
  readonly height: number;
@@ -12,5 +13,8 @@ export declare class SvgRenderer implements Renderer {
12
13
  draw(commands: DrawCommand[]): void;
13
14
  setBackground(color: string): void;
14
15
  get element(): SVGSVGElement;
16
+ /** Map a pointer event's clientX/Y into design space (undoes the letterbox). */
17
+ toDesign(clientX: number, clientY: number): Vec2;
18
+ viewport(): Viewport;
15
19
  dispose(): void;
16
20
  }
@@ -1,5 +1,9 @@
1
1
  import { type DrawCommand } from './commands';
2
- /** Inner SVG markup for a display list (no wrapping <svg>). */
3
- export declare function commandsToSVGInner(commands: DrawCommand[]): string;
2
+ /**
3
+ * Inner SVG markup for a display list (no wrapping <svg>). `idPrefix` salts the
4
+ * ids of any gradient/shadow <defs> so several inner markups can share one SVG
5
+ * document (e.g. a filmstrip) without their `url(#…)` references colliding.
6
+ */
7
+ export declare function commandsToSVGInner(commands: DrawCommand[], idPrefix?: string): string;
4
8
  /** A complete, standalone SVG document string for a frame — a headless screenshot. */
5
9
  export declare function renderToSVGString(commands: DrawCommand[], width: number, height: number, background?: string): string;
@@ -0,0 +1,176 @@
1
+ import type { DrawCommand } from './commands';
2
+ import { type Renderer, type RendererConfig, type Viewport } from './renderer';
3
+ import type { Vec2 } from '../core/math';
4
+ /** Value for a GLSL uniform: float, vec2, vec3, or vec4. */
5
+ export type UniformValue = number | [number, number] | [number, number, number] | [number, number, number, number];
6
+ /**
7
+ * One pass in a post-processing pipeline. Each pass is a GLSL fragment shader
8
+ * that receives:
9
+ * uniform sampler2D u_scene — the original Canvas2D rasterized frame
10
+ * uniform sampler2D u_prev — output of the previous pass (= u_scene for pass 0)
11
+ * uniform float u_time — wall-clock seconds (cosmetic only, never hashed)
12
+ * uniform vec2 u_resolution — display canvas size in pixels
13
+ * in vec2 v_uv — 0..1 UV with (0,0) at top-left
14
+ * out vec4 fragColor — output pixel
15
+ *
16
+ * Plus any custom uniforms added via setUniform() or the pass's own `uniforms` map.
17
+ */
18
+ export interface PostProcessPass {
19
+ /** GLSL #version 300 es fragment shader source. */
20
+ shader: string;
21
+ /** Per-pass custom uniforms (merged with the renderer-level ones, pass overrides renderer). */
22
+ uniforms?: Record<string, UniformValue>;
23
+ }
24
+ /**
25
+ * Ready-made GLSL fragment shaders for common post-processing effects.
26
+ * Use in setPostProcess() or as passes in setPipeline().
27
+ *
28
+ * Each effect references u_scene / u_prev / u_time / u_resolution as needed.
29
+ * Custom tuning: pass a custom uniform via setUniform() — names are noted below.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * import { WebGL2Renderer, WEBGL_EFFECTS } from '@hayao';
34
+ * const renderer = new WebGL2Renderer({ width, height, postProcess: WEBGL_EFFECTS.vignette });
35
+ * // Tune: renderer.setUniform('u_vignette', 0.5);
36
+ * ```
37
+ */
38
+ export declare const WEBGL_EFFECTS: {
39
+ /** Passthrough — no effect. */
40
+ readonly passthrough: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_scene;\nuniform sampler2D u_prev;\nout vec4 fragColor;\nvoid main() { fragColor = texture(u_prev, v_uv); }";
41
+ /** Pixelate: blockSize in pixels (default 4). setUniform('u_block', N) */
42
+ readonly pixelate: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_prev;\nuniform vec2 u_resolution;\nuniform float u_block;\nout vec4 fragColor;\nvoid main() {\n float block = max(1.0, u_block > 0.0 ? u_block : 4.0);\n vec2 px = floor(v_uv * u_resolution / block) * block / u_resolution;\n fragColor = texture(u_prev, px);\n}";
43
+ /** Vignette: darkens edges. setUniform('u_vignette', 0.0..1.0, default 0.4) */
44
+ readonly vignette: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_prev;\nuniform float u_vignette;\nout vec4 fragColor;\nvoid main() {\n vec4 col = texture(u_prev, v_uv);\n vec2 uv = v_uv - 0.5;\n float d = dot(uv, uv);\n float strength = u_vignette > 0.0 ? u_vignette : 0.4;\n fragColor = col * (1.0 - d * strength * 4.0);\n}";
45
+ /** CRT scanlines + slight barrel distortion. u_time drives the scan roll. */
46
+ readonly crt: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_prev;\nuniform vec2 u_resolution;\nuniform float u_time;\nuniform float u_crt_lines; // scanline density, default 600\nuniform float u_crt_warp; // barrel strength, default 0.08\nout vec4 fragColor;\nvoid main() {\n float lines = u_crt_lines > 0.0 ? u_crt_lines : 600.0;\n float warp = u_crt_warp > 0.0 ? u_crt_warp : 0.08;\n // barrel distortion\n vec2 uv = v_uv - 0.5;\n float r2 = dot(uv, uv);\n uv *= 1.0 + warp * r2;\n uv += 0.5;\n if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {\n fragColor = vec4(0.0, 0.0, 0.0, 1.0);\n return;\n }\n vec4 col = texture(u_prev, uv);\n // rolling scanlines\n float scan = sin((uv.y * lines + u_time * 40.0) * 3.14159) * 0.5 + 0.5;\n col.rgb *= 0.75 + 0.25 * scan;\n // slight green phosphor tint\n col.rgb = mix(col.rgb, col.rgb * vec3(0.9, 1.05, 0.85), 0.3);\n fragColor = col;\n}";
47
+ /** Chromatic aberration / RGB fringe. setUniform('u_aberration', 0..0.01, default 0.004) */
48
+ readonly chromaticAberration: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_prev;\nuniform float u_aberration;\nout vec4 fragColor;\nvoid main() {\n float amt = u_aberration > 0.0 ? u_aberration : 0.004;\n vec2 dir = v_uv - 0.5;\n float r = texture(u_prev, v_uv + dir * amt * 2.0).r;\n float g = texture(u_prev, v_uv).g;\n float b = texture(u_prev, v_uv - dir * amt * 2.0).b;\n fragColor = vec4(r, g, b, 1.0);\n}";
49
+ /** Palette-shift / hue rotation. u_hue_shift in radians, driven by u_time for animation. */
50
+ readonly hueRotate: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_prev;\nuniform float u_hue_shift; // radians; if 0 uses u_time\nuniform float u_time;\nout vec4 fragColor;\nvoid main() {\n float angle = u_hue_shift != 0.0 ? u_hue_shift : u_time * 0.5;\n vec4 col = texture(u_prev, v_uv);\n // Hue rotate via Rodrigues in YIQ-ish space\n float c = cos(angle), s = sin(angle);\n mat3 m = mat3(\n 0.299+0.701*c+0.168*s, 0.587-0.587*c+0.330*s, 0.114-0.114*c-0.497*s,\n 0.299-0.299*c-0.328*s, 0.587+0.413*c+0.035*s, 0.114-0.114*c+0.292*s,\n 0.299-0.300*c+1.250*s, 0.587-0.588*c-1.050*s, 0.114+0.886*c-0.203*s\n );\n fragColor = vec4(m * col.rgb, col.a);\n}";
51
+ /** Wave distortion (heat haze, underwater). setUniform('u_wave_amp', default 0.003) */
52
+ readonly wave: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_prev;\nuniform float u_time;\nuniform float u_wave_amp;\nuniform float u_wave_freq;\nout vec4 fragColor;\nvoid main() {\n float amp = u_wave_amp > 0.0 ? u_wave_amp : 0.003;\n float freq = u_wave_freq > 0.0 ? u_wave_freq : 8.0;\n vec2 uv = v_uv;\n uv.x += sin(uv.y * freq + u_time * 2.5) * amp;\n uv.y += cos(uv.x * freq + u_time * 2.5) * amp;\n fragColor = texture(u_prev, clamp(uv, 0.0, 1.0));\n}";
53
+ /** Bloom pass 1/4: extract bright pixels above threshold (default 0.6). */
54
+ readonly bloomBrightpass: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_prev;\nuniform float u_bloom_threshold;\nout vec4 fragColor;\nvoid main() {\n vec4 col = texture(u_prev, v_uv);\n float lum = dot(col.rgb, vec3(0.2126, 0.7152, 0.0722));\n float thresh = u_bloom_threshold > 0.0 ? u_bloom_threshold : 0.55;\n float weight = smoothstep(thresh - 0.05, thresh + 0.05, lum);\n fragColor = col * weight;\n}";
55
+ /** Bloom pass 2/4: horizontal gaussian blur (13-tap). */
56
+ readonly bloomHBlur: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_prev;\nuniform vec2 u_resolution;\nuniform float u_bloom_spread;\nout vec4 fragColor;\nvoid main() {\n float spread = u_bloom_spread > 0.0 ? u_bloom_spread : 1.0;\n vec2 texel = vec2(spread / u_resolution.x, 0.0);\n const float W[7] = float[](0.0625, 0.125, 0.1875, 0.25, 0.1875, 0.125, 0.0625);\n vec4 sum = vec4(0.0);\n for (int i = 0; i < 7; i++)\n sum += texture(u_prev, clamp(v_uv + float(i-3) * texel, 0.0, 1.0)) * W[i];\n fragColor = sum;\n}";
57
+ /** Bloom pass 3/4: vertical gaussian blur (13-tap). */
58
+ readonly bloomVBlur: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_prev;\nuniform vec2 u_resolution;\nuniform float u_bloom_spread;\nout vec4 fragColor;\nvoid main() {\n float spread = u_bloom_spread > 0.0 ? u_bloom_spread : 1.0;\n vec2 texel = vec2(0.0, spread / u_resolution.y);\n const float W[7] = float[](0.0625, 0.125, 0.1875, 0.25, 0.1875, 0.125, 0.0625);\n vec4 sum = vec4(0.0);\n for (int i = 0; i < 7; i++)\n sum += texture(u_prev, clamp(v_uv + float(i-3) * texel, 0.0, 1.0)) * W[i];\n fragColor = sum;\n}";
59
+ /** Bloom pass 4/4: composite original + blurred bloom. u_bloom_intensity controls strength. */
60
+ readonly bloomComposite: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_scene;\nuniform sampler2D u_prev;\nuniform float u_bloom_intensity;\nout vec4 fragColor;\nvoid main() {\n vec4 scene = texture(u_scene, v_uv);\n vec4 bloom = texture(u_prev, v_uv);\n float intensity = u_bloom_intensity > 0.0 ? u_bloom_intensity : 0.8;\n fragColor = vec4(scene.rgb + bloom.rgb * intensity, scene.a);\n}";
61
+ };
62
+ export declare const BLOOM_PIPELINE: PostProcessPass[];
63
+ export interface WebGLRendererConfig extends RendererConfig {
64
+ /**
65
+ * Initial post-processing shader or pipeline. Pass a GLSL string for a
66
+ * single-pass effect, or use setPipeline() for multi-pass. Defaults to
67
+ * passthrough.
68
+ */
69
+ postProcess?: string;
70
+ }
71
+ /**
72
+ * GPU-accelerated renderer with a programmable GLSL post-processing pipeline.
73
+ *
74
+ * ## Quick start
75
+ * ```ts
76
+ * import { WebGL2Renderer, WEBGL_EFFECTS, BLOOM_PIPELINE } from '@hayao';
77
+ *
78
+ * // Single-pass:
79
+ * const renderer = new WebGL2Renderer({ width, height,
80
+ * postProcess: WEBGL_EFFECTS.vignette });
81
+ * renderer.setUniform('u_vignette', 0.5);
82
+ *
83
+ * // Multi-pass bloom:
84
+ * const renderer = new WebGL2Renderer({ width, height });
85
+ * renderer.setPipeline(BLOOM_PIPELINE);
86
+ * renderer.setUniform('u_bloom_intensity', 1.2);
87
+ *
88
+ * // Runtime effect swap:
89
+ * renderer.setPostProcess(WEBGL_EFFECTS.crt);
90
+ * renderer.clearPostProcess(); // back to passthrough
91
+ * ```
92
+ *
93
+ * ## Auto-uniforms (available in every shader, every pass)
94
+ * - `uniform float u_time` — wall-clock seconds (cosmetic, never hashed)
95
+ * - `uniform vec2 u_resolution` — display canvas size in px (w, h)
96
+ * - `uniform sampler2D u_scene` — original Canvas2D rasterized frame
97
+ * - `uniform sampler2D u_prev` — output of the previous pass (= u_scene for pass 0)
98
+ *
99
+ * ## Determinism
100
+ * `u_time` is wall-clock and NOT part of `world.hash()`. The renderer is a
101
+ * pure display-list consumer — it never writes state back into the engine.
102
+ * All verify passes run against `HeadlessRenderer` / `SvgRenderer` and see
103
+ * the same DrawCommand[] regardless of post-processing.
104
+ */
105
+ export declare class WebGL2Renderer implements Renderer {
106
+ readonly width: number;
107
+ readonly height: number;
108
+ private background;
109
+ private glCanvas;
110
+ private gl;
111
+ private offscreen;
112
+ private ctx2d;
113
+ private dpr;
114
+ /** Original rasterized scene texture. */
115
+ private sceneTex;
116
+ /** Intermediate pass framebuffers (one per non-final pass). */
117
+ private fbos;
118
+ /** Compiled programs, one per pass. */
119
+ private programs;
120
+ /** Per-pass uniform maps (merged with global uniforms). */
121
+ private passUniforms;
122
+ /** Empty VAO for gl_VertexID-only draws. */
123
+ private vao;
124
+ /** Frames drawn, driving u_time at a nominal 60 fps (cosmetic, but deterministic). */
125
+ private framesDrawn;
126
+ /** Global custom uniforms applied to every pass. */
127
+ private uniforms;
128
+ constructor(config: WebGLRendererConfig);
129
+ mount(parent: HTMLElement): void;
130
+ private resize;
131
+ private buildPipeline;
132
+ draw(commands: DrawCommand[]): void;
133
+ /**
134
+ * Set a custom uniform applied to every pass. Useful for knob-driven effect
135
+ * parameters (intensity, scale, colour) without recompiling the shader.
136
+ * The value is set on every draw() until cleared or overridden.
137
+ *
138
+ * ```ts
139
+ * renderer.setUniform('u_vignette', 0.5);
140
+ * renderer.setUniform('u_bloom_intensity', [1.0, 0.8]);
141
+ * ```
142
+ */
143
+ setUniform(name: string, value: UniformValue): this;
144
+ /** Remove a custom uniform. */
145
+ clearUniform(name: string): this;
146
+ /**
147
+ * Replace the post-processing pipeline. Accepts an array of passes; each
148
+ * pass reads `u_prev` (previous output) and `u_scene` (original frame).
149
+ * Throws on shader compile error — previous pipeline stays active.
150
+ *
151
+ * For single-pass effects, prefer `setPostProcess(shader)`. For the standard
152
+ * bloom setup, use `setPipeline(BLOOM_PIPELINE)`.
153
+ *
154
+ * ```ts
155
+ * renderer.setPipeline([
156
+ * { shader: WEBGL_EFFECTS.bloomBrightpass },
157
+ * { shader: WEBGL_EFFECTS.bloomHBlur },
158
+ * { shader: WEBGL_EFFECTS.bloomVBlur },
159
+ * { shader: WEBGL_EFFECTS.bloomComposite },
160
+ * ]);
161
+ * renderer.setUniform('u_bloom_intensity', 1.0);
162
+ * ```
163
+ */
164
+ setPipeline(passes: PostProcessPass[]): this;
165
+ /**
166
+ * Convenience: set a single-pass post-processing shader.
167
+ * Throws on compile error — previous effect stays active.
168
+ */
169
+ setPostProcess(fragmentShader: string): this;
170
+ /** Restore passthrough (no post-processing). */
171
+ clearPostProcess(): this;
172
+ get element(): HTMLCanvasElement;
173
+ toDesign(clientX: number, clientY: number): Vec2;
174
+ viewport(): Viewport;
175
+ dispose(): void;
176
+ }
@@ -0,0 +1,42 @@
1
+ import { Node, type NodeConfig } from './node';
2
+ import { type Vec2 } from '../core/math';
3
+ import type { Camera2D } from './nodes';
4
+ /** World-space rectangle the camera CENTER is kept inside (before viewport inset). */
5
+ export interface CameraBounds {
6
+ minX: number;
7
+ minY: number;
8
+ maxX: number;
9
+ maxY: number;
10
+ }
11
+ export interface CameraControllerConfig extends NodeConfig {
12
+ /** Node to follow. May be set later via `follow()`. */
13
+ target?: Node;
14
+ /** Camera to drive. Defaults to the world's active camera each step. */
15
+ camera?: Camera2D;
16
+ /** Half-extents of a slack box the target may roam before the camera chases. */
17
+ deadzone?: number | Vec2;
18
+ /** Per-step chase factor in [0,1]; 1 snaps, lower trails. Default 0.15. */
19
+ smooth?: number;
20
+ /** Clamp the view to a world rectangle (the level bounds) so no void shows. */
21
+ bounds?: CameraBounds;
22
+ }
23
+ export declare class CameraController extends Node {
24
+ readonly type = "CameraController";
25
+ cosmetic: boolean;
26
+ target: Node | null;
27
+ private cameraRef;
28
+ deadzone: Vec2;
29
+ smooth: number;
30
+ bounds: CameraBounds | null;
31
+ constructor(config?: CameraControllerConfig);
32
+ /** Point the controller at a node to follow. */
33
+ follow(target: Node): this;
34
+ private host;
35
+ private camera;
36
+ /** Where the camera wants to sit: the target position, clamped to bounds. */
37
+ private desired;
38
+ /** Jump the camera straight to its desired position (no smoothing). */
39
+ snap(): void;
40
+ protected onReady(): void;
41
+ protected onProcess(): void;
42
+ }
@@ -0,0 +1,73 @@
1
+ import type { Transform } from '../core/math';
2
+ import type { DrawCommand, Paint } from '../render/commands';
3
+ import { Node, type NodeConfig } from './node';
4
+ /**
5
+ * A raised isometric tile = a cube: a top diamond + a front-left and front-right
6
+ * parallelogram. `pos` is the GROUND cell centre (where an elev-0 diamond would
7
+ * sit); the block rises `height` screen-px toward −y. Give it a base `fill` and
8
+ * the two side faces auto-shade (left darkest, right mid) — or override any face
9
+ * with `top` / `left` / `right`. Emits the three faces in back-to-front order so
10
+ * the top always paints over the sides within the prism.
11
+ */
12
+ export interface IsoPrismConfig extends NodeConfig {
13
+ /** Footprint diamond width (horizontal diagonal) in screen px. */
14
+ tileW: number;
15
+ /** Footprint diamond height (vertical diagonal) in screen px. */
16
+ tileH: number;
17
+ /** Block height in screen px (0 = a flat tile: only the top diamond). */
18
+ height: number;
19
+ /** Base colour; side faces derive from it unless overridden. */
20
+ fill: string;
21
+ /** Explicit face paints (override the auto-shade). */
22
+ top?: Paint;
23
+ left?: Paint;
24
+ right?: Paint;
25
+ /** Shared stroke for all faces (seams between tiles). */
26
+ stroke?: string;
27
+ strokeWidth?: number;
28
+ opacity?: number;
29
+ }
30
+ /** Multiply a #rgb / #rrggbb colour's channels by `f` (clamped) for face shading. */
31
+ export declare function shadeHex(hex: string, f: number): string;
32
+ export declare class IsoPrism extends Node {
33
+ readonly type = "IsoPrism";
34
+ tileW: number;
35
+ tileH: number;
36
+ height: number;
37
+ fill: string;
38
+ topPaint?: Paint;
39
+ leftPaint?: Paint;
40
+ rightPaint?: Paint;
41
+ stroke?: string;
42
+ strokeWidth?: number;
43
+ opacity?: number;
44
+ constructor(config: IsoPrismConfig);
45
+ protected draw(out: DrawCommand[], world: Transform): void;
46
+ protected serializeProps(): Record<string, unknown>;
47
+ applyProps(props: Record<string, unknown>): void;
48
+ }
49
+ /**
50
+ * A container that auto-assigns each child's painter `z` from a depth accessor,
51
+ * so overlapping sprites stack correctly without every mover re-setting `z` in
52
+ * its own update. The key is read at DRAW time (positions are final for the
53
+ * frame), integrating these children into the same global z-order as the rest of
54
+ * the scene. Godot's `YSort`, generalised to any axis:
55
+ * new DepthSort({ key: (n) => n.pos.y }) // top-down overlap
56
+ * new DepthSort({ key: (n) => n.userDepth }) // iso gx+gy
57
+ * See docs/CONVENTIONS.md §Depth for the z-from-depth convention.
58
+ */
59
+ export interface DepthSortConfig extends NodeConfig {
60
+ /** Depth of a child → its `z`. Larger = drawn on top (painter's order). */
61
+ key: (n: Node) => number;
62
+ /** Multiplier applied to the key before it becomes `z` (default 1). */
63
+ depthScale?: number;
64
+ }
65
+ export declare class DepthSort extends Node {
66
+ readonly type = "DepthSort";
67
+ key: (n: Node) => number;
68
+ depthScale: number;
69
+ /** DepthSort is a pure ordering container — its z assignments are view-only. */
70
+ cosmetic: boolean;
71
+ constructor(config: DepthSortConfig);
72
+ collectDraw(out: DrawCommand[], parentWorld?: Transform): void;
73
+ }