@energy8platform/golem 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.
Files changed (62) hide show
  1. package/README.md +376 -0
  2. package/bin/golem.js +2 -0
  3. package/dist/editor.css +701 -0
  4. package/dist/editor.js +59202 -0
  5. package/dist/lib/cli.d.ts +1 -0
  6. package/dist/lib/cli.js +5521 -0
  7. package/dist/lib/cli.js.map +7 -0
  8. package/dist/lib/editor/api.d.ts +42 -0
  9. package/dist/lib/editor/app.d.ts +13 -0
  10. package/dist/lib/editor/atlas-detect.d.ts +28 -0
  11. package/dist/lib/editor/atlas-panel.d.ts +1 -0
  12. package/dist/lib/editor/atlas.d.ts +39 -0
  13. package/dist/lib/editor/gizmo-math.d.ts +36 -0
  14. package/dist/lib/editor/gizmos.d.ts +185 -0
  15. package/dist/lib/editor/layers.d.ts +1 -0
  16. package/dist/lib/editor/library.d.ts +3 -0
  17. package/dist/lib/editor/panels.d.ts +26 -0
  18. package/dist/lib/editor/props.d.ts +19 -0
  19. package/dist/lib/editor/server.d.ts +11 -0
  20. package/dist/lib/editor/stage.d.ts +222 -0
  21. package/dist/lib/editor/store.d.ts +1053 -0
  22. package/dist/lib/editor/timeline-layout.d.ts +64 -0
  23. package/dist/lib/editor/timeline.d.ts +45 -0
  24. package/dist/lib/editor-entry.d.ts +2 -0
  25. package/dist/lib/editor-entry.js +5490 -0
  26. package/dist/lib/editor-entry.js.map +7 -0
  27. package/dist/lib/harness.js +51012 -0
  28. package/dist/lib/ktx2.d.ts +15 -0
  29. package/dist/lib/mcp-server.d.ts +2 -0
  30. package/dist/lib/preview/harness.d.ts +16 -0
  31. package/dist/lib/preview/render-preview.d.ts +27 -0
  32. package/dist/lib/preview/viewer.d.ts +1 -0
  33. package/dist/lib/rig-anim.d.ts +109 -0
  34. package/dist/lib/rig-api.d.ts +26 -0
  35. package/dist/lib/rig-atlas.d.ts +20 -0
  36. package/dist/lib/rig-check.d.ts +45 -0
  37. package/dist/lib/rig-constraints.d.ts +59 -0
  38. package/dist/lib/rig-deform.d.ts +27 -0
  39. package/dist/lib/rig-format.d.ts +5036 -0
  40. package/dist/lib/rig-history.d.ts +21 -0
  41. package/dist/lib/rig-import-layers.d.ts +32 -0
  42. package/dist/lib/rig-io.d.ts +9 -0
  43. package/dist/lib/rig-mesh-image.d.ts +2 -0
  44. package/dist/lib/rig-mesh.d.ts +143 -0
  45. package/dist/lib/rig-path.d.ts +101 -0
  46. package/dist/lib/rig-presets.d.ts +101 -0
  47. package/dist/lib/rig-queue.d.ts +2 -0
  48. package/dist/lib/rig-runtime.d.ts +70 -0
  49. package/dist/lib/rig-state.d.ts +64 -0
  50. package/dist/lib/rig-symbol.d.ts +60 -0
  51. package/dist/lib/rig-template-library.d.ts +3 -0
  52. package/dist/lib/rig-templates.d.ts +46 -0
  53. package/dist/lib/rig-tools.d.ts +376 -0
  54. package/dist/lib/runtime.d.ts +9 -0
  55. package/dist/lib/runtime.js +1584 -0
  56. package/dist/lib/runtime.js.map +7 -0
  57. package/dist/lib/spine-import.d.ts +68 -0
  58. package/dist/lib/tools.d.ts +2 -0
  59. package/dist/lib/tools.js +5242 -0
  60. package/dist/lib/tools.js.map +7 -0
  61. package/editor.html +3 -0
  62. package/package.json +96 -0
@@ -0,0 +1,42 @@
1
+ /**
2
+ * editor/api.ts — the only place that talks to editor/server.ts.
3
+ * Every call names the rig by its path relative to the server root; the file on disk is the state.
4
+ */
5
+ import type { RigDocument } from "../rig-format";
6
+ /** `status` is the HTTP status: 409 means a stale `baseVersion` — the tool never ran, so it is not a tool error */
7
+ export interface ToolResult {
8
+ text: string;
9
+ doc?: RigDocument;
10
+ image?: string;
11
+ isError?: boolean;
12
+ version: string;
13
+ status: number;
14
+ }
15
+ export declare function getDoc(rig: string): Promise<{
16
+ doc: RigDocument;
17
+ version: string;
18
+ }>;
19
+ /**
20
+ * `baseVersion` is the version the caller edited: the server refuses with 409 when the file has moved on since.
21
+ * Left out (a read-only tool, and every other client of this route) it is absent from the JSON, and unchecked.
22
+ */
23
+ export declare function callTool(rig: string, name: string, args: Record<string, unknown>, baseVersion?: string): Promise<ToolResult>;
24
+ /** SSE; returns unsubscribe. onState(false) on error, onState(true) on (re)open. EventSource reconnects by itself. */
25
+ export declare function subscribe(rig: string, onChanged: (version: string) => void, onState: (online: boolean) => void): () => void;
26
+ /** URL for an asset `src` that is relative to the rig file's directory */
27
+ export declare const assetUrl: (rigDir: string, src: string) => string;
28
+ export type RigEntry = {
29
+ rig: string;
30
+ name: string;
31
+ mtimeMs: number;
32
+ };
33
+ export type FileEntry = {
34
+ name: string;
35
+ width: number;
36
+ height: number;
37
+ };
38
+ export declare function listRigs(): Promise<RigEntry[]>;
39
+ export declare function createRig(rig: string, name: string, width: number, height: number): Promise<ToolResult>;
40
+ export declare function listFiles(rig: string): Promise<FileEntry[]>;
41
+ /** one PNG into the rig's directory; the server refuses an existing name (409) and anything that is not a PNG */
42
+ export declare function upload(rig: string, file: File): Promise<FileEntry>;
@@ -0,0 +1,13 @@
1
+ import "./editor.css";
2
+ import { Stage } from "./stage";
3
+ import * as store from "./store";
4
+ import { Timeline } from "./timeline";
5
+ declare global {
6
+ interface Window {
7
+ editor: {
8
+ store: typeof store;
9
+ stage: Stage;
10
+ timeline: Timeline;
11
+ };
12
+ }
13
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * editor/atlas-detect.ts — connected islands of opaque pixels on a sheet, as rectangles. Pure: a `Mask` in,
3
+ * `Rect`s out, so it runs in Node for the tests; the browser side (`maskFromImageData`) is one loop.
4
+ */
5
+ export type Rect = [number, number, number, number];
6
+ export interface Mask {
7
+ width: number;
8
+ height: number;
9
+ alpha: Uint8Array;
10
+ }
11
+ export declare function maskFromImageData(img: {
12
+ width: number;
13
+ height: number;
14
+ data: Uint8ClampedArray;
15
+ }): Mask;
16
+ /**
17
+ * Every 4-connected island of pixels with `alpha > threshold`, after growing the mask by `gap` px (a square
18
+ * kernel), so fragments closer than that count as one part. The rectangle is the bbox of the ORIGINAL opaque
19
+ * pixels of the island, not of the grown one, so it stays tight; islands with a bbox area under `minArea` are
20
+ * dropped (specks, stray strokes). Sorted by top, then left.
21
+ */
22
+ export declare function detectIslands(m: Mask, o?: {
23
+ gap?: number;
24
+ minArea?: number;
25
+ threshold?: number;
26
+ }): Rect[];
27
+ /** the bbox of the opaque pixels inside `r` (clipped to the mask), or undefined when there are none */
28
+ export declare function alphaBBox(m: Mask, r: Rect, threshold?: number): Rect | undefined;
@@ -0,0 +1 @@
1
+ export declare function AtlasPanel(): import("preact").JSX.Element;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * editor/atlas.ts — the Stage's atlas mode: the sheet on a checkerboard, its rectangles (named in the accent
3
+ * colour with a label, proposed grey and dashed, the selected ones with grips), and the three gestures —
4
+ * marquee on empty pixels, move from inside, resize by a grip. Rectangles are store state; a finished gesture
5
+ * commits through `setRect` / `addProposed`, so the document changes only for named ones (`rig_set_asset`).
6
+ */
7
+ import { Container, Graphics } from "pixi.js";
8
+ export declare class AtlasView {
9
+ /** the checkerboard and the sheet, with the name labels on top — one container, so the Stage shows and hides it as one */
10
+ readonly view: Container<import("pixi.js").ContainerChild>;
11
+ private labels;
12
+ private gesture?;
13
+ /** the sheet's size once decoded — the Stage fits the camera to it */
14
+ size?: {
15
+ width: number;
16
+ height: number;
17
+ };
18
+ get ready(): boolean;
19
+ /**
20
+ * Decode `url`, show it, and hand its alpha to the store. Every failure (a broken PNG, a sheet over 8192 px a
21
+ * side, a canvas that will not give its pixels) is a status and `atlasFailed`, never a rejection. A view the
22
+ * Stage replaced while the image decoded does nothing on return — the store belongs to the sheet open NOW.
23
+ */
24
+ load(url: string, name: string): Promise<void>;
25
+ /** the overlay for this frame: every rectangle, the selection's grips, the gesture in progress */
26
+ draw(gfx: Graphics, zoom: number): void;
27
+ private label;
28
+ /** the cursor for a hover point: a grip's, `move` inside a selected rectangle, crosshair on empty pixels */
29
+ cursorAt(p: [number, number], zoom: number): string;
30
+ /** a press: a grip or the inside of a SELECTED rectangle starts an edit; inside any other selects it (Shift adds); empty starts a marquee */
31
+ onDown(p: [number, number], zoom: number, shift: boolean): void;
32
+ onMove(p: [number, number]): boolean;
33
+ /** a moved rectangle stays on the sheet (the preview too, so what is dragged is what is committed) */
34
+ private onSheet;
35
+ /** the gesture's result: a moved/resized rectangle (resizes snap), or a new proposed one (snaps; nothing opaque = dropped) */
36
+ onUp(): void;
37
+ /** the sheet's and the checkerboard's GPU textures go with the sprites — nothing else holds them */
38
+ destroy(): void;
39
+ }
@@ -0,0 +1,36 @@
1
+ /** Pure math for the editor gizmos — no DOM, no PixiJS; tested in Node. */
2
+ import { type Affine } from "../rig-anim";
3
+ export interface TransformValues {
4
+ x: number;
5
+ y: number;
6
+ rotation: number;
7
+ scaleX: number;
8
+ scaleY: number;
9
+ shearX: number;
10
+ shearY: number;
11
+ }
12
+ /**
13
+ * Cheap order- and length-sensitive hash of a number array — used where an equality check would be too
14
+ * expensive to run on every doc change (e.g. `structureKeyOf` over a mesh's uvs/triangles, JSON.stringify of
15
+ * which is too slow for rigs with 100+ meshes). Not a checksum: collisions are possible, just unlikely enough
16
+ * for a "does this need a rebuild" gate.
17
+ *
18
+ * Small non-negative integers (triangle/vertex indices) leave a double's low 32-bit word at 0, so folding lo
19
+ * and hi into the running hash as two separate multiply-xor steps (as an earlier version of this function did)
20
+ * left the low-word step contributing nothing beyond the loop index — collapsing most of a 54-element
21
+ * `triangles` permutation's entropy down to the handful of bits `hi` varies over, which collided ~3% of the
22
+ * time on realistic triangle arrays. Instead each element's (lo, hi, index) is combined and run through a full
23
+ * murmur3 avalanche (fmix32) BEFORE folding into `h`, so a coarse difference in `hi` alone still spreads across
24
+ * all 32 bits of that element's contribution; measured at 0 collisions over 5000 permutations of a 54-element,
25
+ * 18-distinct-value array (see the collision-rate test in gizmo-math.test.ts).
26
+ */
27
+ export declare function hashNumbers(arr: readonly number[]): number;
28
+ export declare const deltaDeg: (a: number, b: number) => number;
29
+ export declare const angleDeg: (fx: number, fy: number, tx: number, ty: number) => number;
30
+ /** the resize cursor whose axis is closest to a screen direction (degrees, y down); an axis has no sign, so 180° folds onto 0° */
31
+ export declare function cursorFor(angleDeg: number): string;
32
+ export declare function decompose(m: Affine): TransformValues;
33
+ export declare function localFromWorld(parentWorld: Affine | undefined, world: Affine): TransformValues;
34
+ export declare const toLocal: (m: Affine, x: number, y: number) => [number, number];
35
+ export declare function hitRegion(m: Affine, w: number, h: number, px: number, py: number): boolean;
36
+ export declare function hitMesh(v: number[], tri: number[], px: number, py: number): boolean;
@@ -0,0 +1,185 @@
1
+ /**
2
+ * editor/gizmos.ts — the drag handles on the selected bone / region and the single tool call each gesture commits.
3
+ *
4
+ * A gizmo is a small state machine: `handles()` says where the grips are (document space), `hit()` finds one under
5
+ * the pointer, `begin()` snapshots the document, the skeleton and the values the drag starts from, `drag()` returns
6
+ * a *preview* document rebuilt from that snapshot every move (never from the previous preview, so a gesture never
7
+ * accumulates), and `commit()` names the one tool call that makes the preview real.
8
+ *
9
+ * Everything here is pure except `draw`, which paints into a PixiJS `Graphics` — imported as a type only, so the
10
+ * math below runs in Node.
11
+ */
12
+ import type { Graphics } from "pixi.js";
13
+ import { type Skeleton } from "../rig-anim";
14
+ import type { RigDocument } from "../rig-format";
15
+ import { type TransformValues } from "./gizmo-math";
16
+ export type { TransformValues };
17
+ export interface Mods {
18
+ shift: boolean;
19
+ alt: boolean;
20
+ }
21
+ export interface Handle {
22
+ id: "move" | "rotate" | "scale" | "scaleY";
23
+ x: number;
24
+ y: number;
25
+ cursor: string;
26
+ /** drawn radius in screen px, `GRIP_PX` by default — a path's tangent grips are smaller than their anchor */
27
+ r?: number;
28
+ /** a path TANGENT grip: the one control point it drags (an anchor grip moves the whole selection instead) */
29
+ vertex?: number;
30
+ }
31
+ export interface GizmoContext {
32
+ shown(): RigDocument;
33
+ skel(): Skeleton;
34
+ mode(): "setup" | "animate";
35
+ animId(): string | undefined;
36
+ time(): number;
37
+ }
38
+ export interface ToolCall {
39
+ tool: string;
40
+ args: Record<string, unknown>;
41
+ }
42
+ export interface Gizmo {
43
+ handles(zoom: number): Handle[];
44
+ draw(g: Graphics, zoom: number): void;
45
+ /** nearest handle within 8 screen px, or undefined */
46
+ hit(p: [number, number], zoom: number): Handle | undefined;
47
+ begin(h: Handle, p: [number, number]): void;
48
+ /** returns the draft document for the pointer at p */
49
+ drag(p: [number, number], mods: Mods): RigDocument;
50
+ /** the tool call that commits the gesture (undefined = nothing changed) */
51
+ commit(): ToolCall | undefined;
52
+ }
53
+ /** pure: local transform patch for a bone whose joint should sit at world point p */
54
+ export declare function boneMoveValues(skel: Skeleton, doc: RigDocument, boneId: string, p: [number, number]): {
55
+ x: number;
56
+ y: number;
57
+ };
58
+ /**
59
+ * pure: local rotation so the bone's x axis points from its joint to p (exact under a scaled parent — the direction
60
+ * is measured in the parent's frame, not in world, so non-uniform parent scale does not skew the answer).
61
+ * `affineOf` builds the x axis at `rotation + shearX`, so the shear has to come back out of the measured angle.
62
+ * Assumes `inherit: "normal"`; the other modes replace the parent's linear part and are not editable this way.
63
+ */
64
+ export declare function boneRotationValue(skel: Skeleton, doc: RigDocument, boneId: string, p: [number, number]): number;
65
+ /**
66
+ * pure: the `rig_update_bone` args that move `boneId` under `newParent` while keeping its world matrix — the local
67
+ * transform is its world matrix expressed in the new parent's frame.
68
+ *
69
+ * Throws when the new parent is the bone itself or one of its descendants (that would cut the branch off the tree),
70
+ * and when either bone is unknown. `approximate` warns that the answer only holds for `inherit: "normal"`: the other
71
+ * modes replace the parent's linear part, so no local transform under an arbitrary parent reproduces the old world.
72
+ * The caller passes the REST skeleton — `rig_update_bone` writes setup values, not posed ones.
73
+ */
74
+ export declare function reparentArgs(skel: Skeleton, doc: RigDocument, boneId: string, newParent: string): {
75
+ id: string;
76
+ parent: string;
77
+ transform: TransformValues;
78
+ approximate: boolean;
79
+ };
80
+ export declare class BoneGizmo implements Gizmo {
81
+ private ctx;
82
+ private boneId;
83
+ private start?;
84
+ private patch?;
85
+ constructor(ctx: GizmoContext, boneId: string);
86
+ handles(zoom: number): Handle[];
87
+ draw(g: Graphics, zoom: number): void;
88
+ hit(p: [number, number], zoom: number): Handle | undefined;
89
+ begin(handle: Handle, p: [number, number]): void;
90
+ drag(p: [number, number], mods: Mods): RigDocument;
91
+ /** the preview: setup edits the rest transform, animate writes absolute keys at t (merged into the tracks) */
92
+ private write;
93
+ commit(): ToolCall | undefined;
94
+ }
95
+ export declare class RegionGizmo implements Gizmo {
96
+ private ctx;
97
+ private attachmentId;
98
+ private start?;
99
+ private patch?;
100
+ constructor(ctx: GizmoContext, attachmentId: string);
101
+ private region;
102
+ /** the world transform of the bone the attachment's slot hangs on */
103
+ private boneWorld;
104
+ handles(zoom: number): Handle[];
105
+ draw(g: Graphics, zoom: number): void;
106
+ hit(p: [number, number], zoom: number): Handle | undefined;
107
+ begin(handle: Handle, p: [number, number]): void;
108
+ drag(p: [number, number], mods: Mods): RigDocument;
109
+ commit(): ToolCall | undefined;
110
+ }
111
+ /**
112
+ * The selected vertices of one mesh — or the selected ANCHORS of one path attachment, plus the two bezier
113
+ * tangents of a single selected anchor: a grip on each, and a drag that moves them by one world delta.
114
+ *
115
+ * A mesh and a path are the same problem, and this solves it once: both carry the same `VertexData` (`vertices`
116
+ * in the slot bone's space, or `weights` skinned to bones), and both modes write THE SAME NUMBERS — the drag,
117
+ * mapped into each influence's own bone by `deformOffsetsFor`. **Setup** adds them into the rest shape, so a
118
+ * skinned mesh (or a skinned PATH — five of `ross`'s seven are) keeps its weights and every value the drag did
119
+ * not touch. **Animate** writes them as a `deform.<id>.vertices` key at the current time instead, one key for the
120
+ * whole selection, one tool call. (`ross`'s `tail_path` arrives from Spine with 17 such tracks.) The only
121
+ * difference is the frame: setup maps the drag through the REST matrices, animate through the posed ones.
122
+ *
123
+ * A path's grips sit on its ANCHORS (vertex 3i + 1 of the `[cPrev, p, cNext]` triples), and an anchor drags its
124
+ * whole triple, so the curve keeps its shape around the anchor — which is what a hand expects. The tangents are
125
+ * grips of their own, shown only while exactly ONE anchor is selected (all of them at once would put ten more
126
+ * grips on `ross`'s five-anchor `tail_path`, and a mesh-sized path far more) and dragged one at a time.
127
+ */
128
+ export declare class VertexGizmo implements Gizmo {
129
+ private ctx;
130
+ private attachmentId;
131
+ private indices;
132
+ private start?;
133
+ private delta?;
134
+ private patch?;
135
+ private offsets?;
136
+ constructor(ctx: GizmoContext, attachmentId: string, indices: () => number[]);
137
+ private attachment;
138
+ /** the selected indices that actually exist — a stale selection must not throw mid-frame */
139
+ private selected;
140
+ handles(_zoom: number): Handle[];
141
+ /** a selected anchor and its tangents are one widget, so they get the connecting arms; loose vertex grips are not */
142
+ draw(g: Graphics, zoom: number): void;
143
+ hit(p: [number, number], zoom: number): Handle | undefined;
144
+ begin(handle: Handle, p: [number, number]): void;
145
+ /** which VERTICES a grip drags: a mesh's whole selection, every selected anchor's triple, or the one tangent the grip names */
146
+ private moved;
147
+ /** the deform offsets the animation already has at `t` — undefined when there is no track, or its length is stale */
148
+ private keyAt;
149
+ drag(p: [number, number], _mods: Mods): RigDocument;
150
+ /**
151
+ * The drag as a local offset per influence — the `deform.<id>.vertices` layout — folded over every moved vertex.
152
+ * One key for the whole selection: each list feeds the next call as its base, and the snapshot's base is copied
153
+ * rather than handed over, so no future in-place writer downstream can spoil the next move's baseline.
154
+ * Rebuilt from that base every move, so a gesture never accumulates.
155
+ */
156
+ private localDeltas;
157
+ /**
158
+ * Setup: the same local offsets added into the REST shape, which is the whole of the edit. Every value the drag
159
+ * did not move is passed through untouched — byte for byte, not through a round trip that should be the
160
+ * identity and is not quite (it rounds).
161
+ *
162
+ * Deliberately not `fitVertices`: that re-derives each influence from ONE world point, which is right only for
163
+ * a vertex whose influences already agree on where that vertex is at rest. Spine's do not — 24 of `ross`'s 49
164
+ * skinned meshes carry over a pixel of spread, and `blendInfluences` produces the disagreement on purpose (it
165
+ * is what keeps an inserted anchor's shape in every pose). Fitting throws that away for a position that only
166
+ * matches at rest: the difference contains neither the drag nor the pose, so even a 1 px nudge in setup moves
167
+ * `tail_path`'s anchor ~24 px, and `n_fur`'s vertex 0 ~489 px, once the animation poses the bones. Adding the
168
+ * drag in each bone's own frame moves the vertex by exactly the drag at rest and leaves the spread alone.
169
+ */
170
+ private shift;
171
+ commit(): ToolCall | undefined;
172
+ }
173
+ export type RectHandle = "move" | "n" | "s" | "e" | "w" | "nw" | "ne" | "sw" | "se";
174
+ type Rect = [number, number, number, number];
175
+ /** the eight grips of a rectangle, clockwise from the top-left corner */
176
+ export declare function rectHandles([x, y, w, h]: Rect, _zoom: number): {
177
+ id: RectHandle;
178
+ x: number;
179
+ y: number;
180
+ cursor: string;
181
+ }[];
182
+ /** a grip within 8 screen px, else the nearest edge LINE within that (the whole side resizes, not just its mid grip), else `move` for a press inside the rectangle, else undefined */
183
+ export declare function rectHit(r: Rect, p: [number, number], zoom: number): RectHandle | undefined;
184
+ /** the rectangle after dragging `h` from p0 to p — integers, at least 1×1, and an edge never passes its opposite */
185
+ export declare function rectDrag(h: RectHandle, [x, y, w, h0]: Rect, p0: [number, number], p: [number, number]): Rect;
@@ -0,0 +1 @@
1
+ export declare function LayersList(): import("preact").JSX.Element | null;
@@ -0,0 +1,3 @@
1
+ export declare function FilesList(): import("preact").JSX.Element;
2
+ /** thumbnails of every asset: drag one onto the canvas or a bone row to place it, double-click places it centred */
3
+ export declare function AssetBin(): import("preact").JSX.Element | null;
@@ -0,0 +1,26 @@
1
+ /** editor/panels.tsx — the side panels: the hierarchy tree here, the properties forms in `props.tsx`. */
2
+ import type { ComponentChildren } from "preact";
3
+ export { Properties } from "./props";
4
+ /**
5
+ * Remove the selected node. The Delete key (app.tsx) and the tree's context menu share this, so both ask the same
6
+ * question before a bone takes its children's parent and its keys with it — and before a slot or an attachment
7
+ * takes away animation the user cannot see from the tree.
8
+ */
9
+ export declare function deleteSelection(): void;
10
+ /** `constraints` is the group heading — the one row that stands for no object of its own */
11
+ type MenuAt = {
12
+ kind: "bone" | "slot" | "attachment" | "constraint" | "constraints";
13
+ id: string;
14
+ x: number;
15
+ y: number;
16
+ };
17
+ /** the tree's right-click menu; app.tsx closes it on Escape and on a press outside it */
18
+ export declare const treeMenu: import("@preact/signals-core").Signal<MenuAt | undefined>;
19
+ export declare const closeTreeMenu: () => undefined;
20
+ /** a collapsible block of the left panel; the fold state lives in the store (and localStorage) */
21
+ export declare function Section({ id, title, children }: {
22
+ id: string;
23
+ title: string;
24
+ children: ComponentChildren;
25
+ }): import("preact").JSX.Element;
26
+ export declare function Tree(): import("preact").JSX.Element | null;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * An input whose DOM value follows the document — but never while the user is typing into it, so a ticking
3
+ * playhead cannot rewrite the field under the cursor. Commits on `change` and on Enter (which then blurs);
4
+ * a blur that did not change the text commits nothing.
5
+ */
6
+ export declare function Input({ name, value, type, step, class: cls, placeholder, onCommit }: {
7
+ name: string;
8
+ value: string;
9
+ type?: "text" | "number";
10
+ step?: number;
11
+ class?: string;
12
+ placeholder?: string;
13
+ onCommit: (v: string) => void;
14
+ }): import("preact").JSX.Element;
15
+ /**
16
+ * The right-hand panel: the form for what is selected, then — for the track picked in the timeline, whatever else
17
+ * is selected — its adjustment sliders and the selected key's easing.
18
+ */
19
+ export declare function Properties(): import("preact").JSX.Element;
@@ -0,0 +1,11 @@
1
+ export interface EditorServer {
2
+ port: number;
3
+ url: string;
4
+ close(): Promise<void>;
5
+ }
6
+ /** `root` — where the rigs live (every path in the API is relative to it); `assets` — where editor.html and dist/ are (default: this package). */
7
+ export declare function startEditorServer(opts: {
8
+ root: string;
9
+ port?: number;
10
+ assets?: string;
11
+ }): Promise<EditorServer>;
@@ -0,0 +1,222 @@
1
+ import { type Skeleton } from "../rig-anim";
2
+ import { type Selection } from "./store";
3
+ export declare class Stage {
4
+ private host;
5
+ private app;
6
+ private camera;
7
+ private paper;
8
+ private overlay;
9
+ private gfx;
10
+ private labels;
11
+ private labelPool;
12
+ private player?;
13
+ private textures?;
14
+ private assetKey;
15
+ private structKey;
16
+ private appliedOverlays;
17
+ private t;
18
+ private gen;
19
+ private fitted;
20
+ private destroyed;
21
+ private dispose;
22
+ private gizmo?;
23
+ private atlas?;
24
+ /** the sheet `atlas` was built for, and whether the atlas is what is on screen right now */
25
+ private atlasFor?;
26
+ private atlasOn;
27
+ /** the sheet is decoded and on screen in atlas mode (tests wait on it) */
28
+ get atlasReady(): boolean;
29
+ /**
30
+ * The bone gizmo override: while `from` is selected, the handles (and the drag) belong to `to` — the target bone
31
+ * of the IK that drives `from`. `syncGizmo` consults it and drops it as soon as it stops holding.
32
+ */
33
+ private redirect?;
34
+ /** gizmos read the shown document lazily, so the handles follow the draft while a gesture runs */
35
+ private ctx;
36
+ readonly ready: Promise<void>;
37
+ /** true when the Space held right now was used to pan — the keyboard map then skips the play/pause toggle */
38
+ pannedWithSpace: boolean;
39
+ constructor(host: HTMLElement);
40
+ private init;
41
+ /**
42
+ * A confirmed document: our own commits are by far the commonest, and they only change numbers, so try the
43
+ * cheap swap first (the draft path does the same) and rebuild only when the display objects themselves would
44
+ * differ — new textures, or a slot / attachment added, removed or retyped.
45
+ */
46
+ private applyDoc;
47
+ private rebuild;
48
+ /** a gesture's preview (or the confirmed document again when the gesture is cancelled): same structure, new numbers */
49
+ private applyDraft;
50
+ /** push the shown pose (and slot visibility) into the player, then redraw the overlay */
51
+ private applyPose;
52
+ private reference?;
53
+ private referenceSrc?;
54
+ /** the underlay is on screen — a texture loaded and the sprite visible (tests read it) */
55
+ get referenceShown(): boolean;
56
+ /** `meta.reference` → a sprite between the paper and the rig; re-decoded only when `src` changes */
57
+ private applyReference;
58
+ private startPlayback;
59
+ private tick;
60
+ /** doc px → screen px (canvas client coords); the camera never rotates, so this is exact without a render */
61
+ toScreen(x: number, y: number): [number, number];
62
+ toDoc(sx: number, sy: number): [number, number];
63
+ get zoom(): number;
64
+ /** true once the rig's display objects are on screen (textures loaded) */
65
+ get rendered(): boolean;
66
+ /**
67
+ * The status bar's readout: the pointer in document px and the camera scale. `pointermove` fires far faster
68
+ * than the screen refreshes, so the signals are written at most once per animation frame.
69
+ */
70
+ private at?;
71
+ private viewFrame;
72
+ private publishView;
73
+ /** the document fills the viewport with a margin — in atlas mode the sheet does, once it is decoded */
74
+ fit(): void;
75
+ /**
76
+ * The gizmo for the current selection: a bone always, a region attachment in setup mode, a grip on every
77
+ * selected mesh vertex — a whole mesh gets no transform box, it is edited through its vertices and the panel.
78
+ *
79
+ * The vertex gizmo reads `selectedVertices` lazily (through `.peek()`, so this effect is not re-entered): the
80
+ * list is the truth while several vertices are picked, and `selection.index` covers the moment a fresh pick has
81
+ * only set the primary one.
82
+ *
83
+ * A bone driven by a live IK is the one exception: `redirect` (set by the pointer handler) points the gizmo at
84
+ * the constraint's TARGET bone while the selection stays on the driven bone, so its form keeps showing. The
85
+ * redirection is re-checked here rather than remembered, so a changed selection or a deleted target drops it by
86
+ * itself. A mix faded to 0 drops it too, but only when this effect runs again (a new selection, a changed
87
+ * document) or at the next press: `syncGizmo` does not run on the playhead — that is what `liveGizmo` below is
88
+ * for, and why the redirected handles can outlive the mix that earned them by a few frames of playback.
89
+ */
90
+ private syncGizmo;
91
+ /**
92
+ * The constraint that owns a bone's transform right now, or undefined when the bone is free. Forwards to
93
+ * `driverAt` (rig-constraints.ts) over the editor's own `pose` signal (`poseOf(currentAnim, time)` in animate
94
+ * mode, rest otherwise) — the same sampler that function expects, and the same rule the solvers apply.
95
+ */
96
+ private driverOf;
97
+ /**
98
+ * The gizmo the pointer and the overlay may use: none at all while a live transform or path constraint owns the
99
+ * selected bone, since every drag on it is refused — grips (and their cursors) would promise an edit that cannot
100
+ * happen. Checked here rather than in `syncGizmo` because a keyed mix fades in and out under a still selection,
101
+ * and `syncGizmo` does not run on the playhead.
102
+ */
103
+ private get liveGizmo();
104
+ /**
105
+ * A press on the SELECTED bone when a constraint owns it. Writing such a bone by hand is silently undone by the
106
+ * next evaluation (the stage A review found this), so the gesture is moved or refused instead:
107
+ * - IK with a live mix → the drag is handed to the constraint's target bone (`redirect`), which is what posing
108
+ * an IK limb means; the selection stays put so the panel still shows the bone that was clicked;
109
+ * - transform / path → nothing to hand it to: the press is refused and says where the value comes from.
110
+ *
111
+ * Only a press that grabs the DRIVEN bone's own handles is intercepted — once redirected, the target's grips are
112
+ * dragged like any other, and a press on empty canvas still picks. Under IK both `scale` grips are left alone:
113
+ * `applyIk` never writes `scaleY` and only writes `scaleX` under `stretch`, so scaling a chain bone on either
114
+ * axis is a real edit that must stay reachable (and the `scale` grip wins the tie at the bone's tip). The
115
+ * exemption is IK-SPECIFIC: a transform constraint with `mixScaleX`/`mixScaleY` (and a path one with
116
+ * `rotateMode: chainScale`) does write scale, so there a scale press falls through to the refusal below instead
117
+ * of promising an edit the next evaluation would undo. `alt` drops the redirection altogether (the user really
118
+ * does mean the local value); in `pick` the same modifier means "bones only", and the two agree.
119
+ */
120
+ private grabDriven;
121
+ /**
122
+ * End of a gizmo drag: exactly one tool call (one history snapshot) or nothing at all. The gesture flag is
123
+ * cleared whatever happens, so a throwing gizmo cannot wedge the editor into "a drag is in progress".
124
+ */
125
+ private endGesture;
126
+ /** the stroke in progress: which mesh it paints, and the confirmed weights it started from */
127
+ private stroke?;
128
+ /** the bone gesture in progress: the press (joint) and the live drag point (tip) */
129
+ private boneDraft?;
130
+ /**
131
+ * One brush step. It accumulates on what is already SHOWN (the previous step's draft), not on the confirmed
132
+ * document, so holding the brush over one spot keeps adding — that is what a paint stroke means.
133
+ */
134
+ private paintStep;
135
+ /** End of a stroke: one `rig_set_weights` when the weights actually moved, nothing at all otherwise. */
136
+ private endStroke;
137
+ /** the bone joint within 8 screen px of a document point, nearest wins — the picker's rule, also the brush's */
138
+ private nearestJoint;
139
+ /**
140
+ * Ctrl-click (or Cmd-click) on the SELECTED path's curve: one `rig_path_anchor insert` at the point clicked, or
141
+ * nothing at all when nothing is selected, the click misses the curve, or the split would land on an anchor —
142
+ * `false` tells the caller to fall through to the normal click instead of swallowing it. `hit.t` is
143
+ * `nearestOnPath`'s own parametrization of the curve, which is exactly what `pathAnchorOp` (via `splitCurve`)
144
+ * consumes, so there is no unit conversion between the two.
145
+ *
146
+ * The anchor guard is the same pick radius the grips use, applied to where the new anchor would GO: on an anchor
147
+ * `nearestOnPath` returns the sampled endpoint, so `t` is 0 or 1 and the tool refuses outright with a red error;
148
+ * a third of a pixel off one it returns `t = 0.0005` and the insert succeeds, leaving a second anchor on top of
149
+ * the first. Neither is what the hand meant — within grabbing distance of an anchor, the click meant the anchor,
150
+ * and falling through selects it.
151
+ */
152
+ private tryInsertAnchor;
153
+ /** camera (wheel zoom toward the cursor, middle button / Space+drag pan), gizmo drags, and click-to-select. */
154
+ private bindPointer;
155
+ /**
156
+ * World-space vertices of a mesh attachment (2 per vertex, the shown deform applied) — what the overlay draws,
157
+ * what the picker hits and what the properties panel reads. Throws for anything that is not a mesh.
158
+ */
159
+ meshWorld(id: string): number[];
160
+ /** the path attachment the selection is working on — the attachment itself, or the one an anchor belongs to */
161
+ private selectedPath;
162
+ /**
163
+ * The visible path (any path, selected or not — a path has no fill, so its curve is the only thing about it a
164
+ * click can land on) whose curve passes closest to (x, y), within `r` document px, or undefined. Same slots-loop
165
+ * and de-dup `drawPaths` uses, so this only offers what is actually on screen.
166
+ */
167
+ private nearestPathCurve;
168
+ /**
169
+ * What is under a point in document space: a vertex of the mesh (or an anchor of the path) being edited first,
170
+ * then the closest bone joint within 8 screen px, then that same mesh's own triangles, then the topmost
171
+ * attachment whose shape contains the point, then — last resort — the curve of any visible path, selected or
172
+ * not (a path has no fill, so this is the only way to pick one that is not already selected). `alt` skips
173
+ * vertices, attachments and path curves, and picks bones only.
174
+ *
175
+ * NB: a vertex hit also WRITES `selectedVertices` (that is where Shift-toggling lives) and returns the selection
176
+ * that goes with it — the caller must store what it gets back, or the two would disagree.
177
+ */
178
+ pick(x: number, y: number, alt?: boolean, shift?: boolean): Selection | undefined;
179
+ /** the shown attachments of visible slots, front to back — the order the player draws them, reversed */
180
+ private drawOrder;
181
+ /** current evaluated skeleton for the shown doc and pose — the store computes and memoizes it for everyone */
182
+ get skeleton(): Skeleton;
183
+ private drawOverlay;
184
+ /**
185
+ * The selected constraint: the bones it writes stroked over the normal skeleton in the warning colour (the last
186
+ * one out to its tip, where the chain ends), a dashed line from that tip to what the constraint reaches for, and
187
+ * a diamond on the target bone. A path constraint aims at a SLOT instead, so the dash runs to the nearest anchor
188
+ * of the curve that slot shows and the curve is stroked with it. Drawn only for the ONE selected constraint: a
189
+ * rig with 26 of them costs nothing while none is picked.
190
+ */
191
+ private drawConstraint;
192
+ /** a dashed segment: `Graphics` has no dash pattern, so the dashes are stepped by hand (60 of them at most) */
193
+ private dashed;
194
+ /**
195
+ * Every visible slot that SHOWS a path attachment strokes its curve — a path has no image, so without this the
196
+ * only thing on screen would be the bones it drives. Unselected it is a thin grey line; the selected attachment
197
+ * (or the one an anchor of which is selected) is stroked in the accent colour and grows a dot on every ANCHOR,
198
+ * exactly as a selected mesh does with its vertices. Costs one map lookup per slot when the rig has no path.
199
+ */
200
+ private drawPaths;
201
+ /**
202
+ * A path attachment's curve from its world control points. The vertices are `[cPrev, anchor, cNext]` triples, so
203
+ * curve i runs anchor i → cNext i → cPrev i+1 → anchor i+1 — the layout `samplePath` walks.
204
+ */
205
+ private strokePathCurve;
206
+ /**
207
+ * The selected mesh: its outline, a dot on every vertex (the selected ones accented) and — on demand, or once
208
+ * the zoom makes them readable — every triangle edge. Only the mesh the SELECTION is working on is drawn this
209
+ * way: a rig with a hundred meshes would otherwise spend its frame stroking edges nobody is looking at.
210
+ */
211
+ private drawMesh;
212
+ /**
213
+ * The weight heatmap of the mesh being painted: every triangle filled with the mean weight of the brush's bone
214
+ * over its corners (grey → accent), a dot per vertex in its own weight's colour, and the brush ring.
215
+ *
216
+ * Both the triangles and the dots are BATCHED by quantized weight — one `fill()` per colour level instead of
217
+ * one per shape. A 289-vertex mesh has ~540 triangles, and 540 separate fills is 540 draw calls a frame; 17
218
+ * levels is 17, whatever the mesh's size, and the quantization is invisible at alpha 0.35.
219
+ */
220
+ private drawWeights;
221
+ destroy(): void;
222
+ }