@energy8platform/golem 0.5.0 → 0.7.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 (72) hide show
  1. package/README.md +18 -0
  2. package/dist/editor.css +3 -4
  3. package/dist/editor.js +177 -75
  4. package/dist/lib/cli.js +302 -126
  5. package/dist/lib/cli.js.map +4 -4
  6. package/dist/lib/e8/agent.d.ts +35 -0
  7. package/dist/lib/e8/host.d.ts +22 -0
  8. package/dist/lib/e8/node.d.ts +66 -0
  9. package/dist/lib/e8/runtime.d.ts +10 -0
  10. package/dist/lib/e8/schema.d.ts +3 -0
  11. package/dist/lib/e8/types.d.ts +80 -0
  12. package/dist/lib/e8-agent.js +6781 -0
  13. package/dist/lib/e8-agent.js.map +7 -0
  14. package/dist/lib/e8-client.js +116 -0
  15. package/dist/lib/e8-host.js +6929 -0
  16. package/dist/lib/e8-host.js.map +7 -0
  17. package/dist/lib/e8-runtime.js +2024 -0
  18. package/dist/lib/e8-runtime.js.map +7 -0
  19. package/dist/lib/e8-schema.js +7 -0
  20. package/dist/lib/e8-schema.js.map +7 -0
  21. package/dist/lib/editor/api.d.ts +1 -0
  22. package/dist/lib/editor/embed.d.ts +9 -0
  23. package/dist/lib/editor/server.d.ts +23 -1
  24. package/dist/lib/editor/store.d.ts +107 -0
  25. package/dist/lib/editor/timeline.d.ts +15 -0
  26. package/dist/lib/editor-entry.d.ts +1 -1
  27. package/dist/lib/editor-entry.js +303 -126
  28. package/dist/lib/editor-entry.js.map +4 -4
  29. package/dist/lib/harness.js +69 -14
  30. package/dist/lib/interactive-editor.js.map +1 -1
  31. package/dist/lib/rig-anim.d.ts +2 -0
  32. package/dist/lib/rig-control-qa.d.ts +27 -0
  33. package/dist/lib/rig-format.d.ts +989 -0
  34. package/dist/lib/rig-import-layers.d.ts +2 -0
  35. package/dist/lib/rig-version.d.ts +12 -0
  36. package/dist/lib/runtime.js +69 -14
  37. package/dist/lib/runtime.js.map +3 -3
  38. package/dist/lib/spine-import.d.ts +4 -0
  39. package/dist/lib/tool-schema.d.ts +3 -0
  40. package/dist/lib/tools.d.ts +2 -0
  41. package/dist/lib/tools.js +257 -73
  42. package/dist/lib/tools.js.map +4 -4
  43. package/editor.html +2 -2
  44. package/package.json +38 -3
  45. package/skills/e8-golem/SKILL.md +71 -0
  46. package/skills/golem-symbol-animation/SKILL.md +87 -0
  47. package/skills/golem-symbol-animation/agents/openai.yaml +4 -0
  48. package/skills/golem-symbol-animation/references/facial-controls.md +48 -0
  49. package/skills/golem-symbol-animation/references/game-engine-integration.md +133 -0
  50. package/skills/golem-symbol-animation/references/golem-authoring.md +138 -0
  51. package/skills/golem-symbol-animation/references/interactive-editor.md +17 -0
  52. package/skills/golem-symbol-animation/references/motion-craft.md +113 -0
  53. package/skills/golem-symbol-animation/references/packed-delivery.md +88 -0
  54. package/skills/golem-symbol-animation/references/quantitative-qa.md +106 -0
  55. package/skills/golem-symbol-animation/references/spine-rive-study.md +87 -0
  56. package/skills/golem-symbol-animation/scripts/atlas_parts.py +87 -0
  57. package/skills/golem-symbol-animation/scripts/check_package.py +71 -0
  58. package/skills/golem-symbol-animation/scripts/image_gates.py +143 -0
  59. package/skills/golem-symbol-cutting/SKILL.md +70 -0
  60. package/skills/golem-symbol-cutting/agents/openai.yaml +4 -0
  61. package/skills/golem-symbol-cutting/assets/h1/h1.atlas.png +0 -0
  62. package/skills/golem-symbol-cutting/assets/h1/h1.png +0 -0
  63. package/skills/golem-symbol-cutting/references/cutting-workflow.md +92 -0
  64. package/skills/golem-symbol-cutting/references/facial-layers.md +44 -0
  65. package/skills/golem-symbol-cutting/references/generation-workflow.md +109 -0
  66. package/skills/golem-symbol-cutting/references/golem-handoff.md +62 -0
  67. package/skills/golem-symbol-cutting/references/h1-example.md +46 -0
  68. package/skills/golem-symbol-cutting/references/hybrid-workflow.md +119 -0
  69. package/skills/golem-symbol-cutting/references/registration-and-motion.md +89 -0
  70. package/skills/golem-symbol-cutting/references/spine-rive-construction.md +73 -0
  71. package/skills/golem-symbol-cutting/scripts/atlas_parts.py +87 -0
  72. package/skills/golem-symbol-cutting/scripts/hybrid_parts.py +279 -0
@@ -0,0 +1,113 @@
1
+ # Motion craft for symbols and full characters
2
+
3
+ ## Curves that carry motion through keys
4
+
5
+ A key is not automatically a stop. Golem's legacy `ease_in_out` eases every interval separately,
6
+ so a chain of such keys brakes to zero at every waypoint. Set `interpolation: "pchip"` on a
7
+ numeric track for shared shape-preserving cubic tangents. This works for scalar and numeric
8
+ array channels. Extrema and holds settle; intermediate monotonic keys keep moving. Explicit
9
+ `step` still means a cut. Older runtimes lack this field: verify support before delivery.
10
+
11
+ ```json
12
+ {"target":"bone","id":"hand_target","prop":"x","interpolation":"pchip",
13
+ "keys":[{"t":0,"v":120},{"t":0.3,"v":150},{"t":0.8,"v":220},{"t":1.2,"v":220}]}
14
+ ```
15
+
16
+ PCHIP gives continuous velocity, not continuous acceleration. For a deliberate contact or
17
+ rest-to-rest reach, use `ease: "minimum_jerk"`: `10u³−15u⁴+6u⁵`. Key x/y with the same normalized
18
+ phase to follow a straight path; author a curved spatial path when the action needs it. Put
19
+ minimum-jerk segment boundaries only at real stops, not every sampled waypoint. For longer
20
+ custom trajectories, sample a differentiable function into explicit linear keys at a rate
21
+ that passes acceleration/jerk measurements; do not re-ease each sample.
22
+
23
+ For idle, t=0 is the approved setup pose unless the established brief says otherwise. Author
24
+ both endpoint values and velocities across every channel. PCHIP wraps endpoint tangents only
25
+ when a loop's keys span exactly `[0,duration]` with matching endpoint values. Inspect the
26
+ unwrapped terminal pose too; a looping player seeks duration back to zero.
27
+
28
+ ## Full-body construction and planted feet
29
+
30
+ Build controls in the order dictated by contacts: planted-foot targets, hips/torso, hand and
31
+ prop contacts, then independent cloth/hair. Place IK targets under a stable world/root control
32
+ rather than a body control that sways. A root translating the entire character also translates
33
+ its child targets; use a stationary scene control when world planting must survive root motion.
34
+
35
+ `rig_add_ik_chain` makes a one/two-bone limb by default. Supply `bones` in parent-to-tip order
36
+ for a longer chain; `rig_set_ik` also accepts long chains. One/two-bone solving is analytical;
37
+ three or more bones use iterative CCD with `iterations` and `tolerance`. Check reach error,
38
+ bend direction, extreme poses and playback; an unreachable target cannot be fixed by more
39
+ iterations. For tail/scarf shape control, a path constraint can be easier than tip-only IK.
40
+ The long-chain solver currently rotates the chain; its stretch/compress/softness flags are
41
+ for the analytic short-chain solver. Do not assume stretch on a long chain without checking.
42
+
43
+ Make independently moving hair a separate attachment and control chain. Curl pixels inside
44
+ an otherwise rigid head mesh often receive head-only weights and remain frozen. Cloak hems,
45
+ scarf ends and hair can share the torso's broad motion while adding local lag. Specify which
46
+ bones must show local secondary motion in `rig_check.secondaryBones`.
47
+
48
+ A single 2D limb mesh has a limited useful bend range. Around 40–60 degrees is a practical
49
+ inspection trigger, not an engine limit: topology, silhouette and artwork determine the
50
+ actual boundary. If a fold or shortened limb appears, split upper/lower segments with real
51
+ joint backing, use an alternate drawn pose, or change topology and weights. Do not silently
52
+ shrink the requested action or accept a fold because most triangles remain valid.
53
+
54
+ Use `rig_gradient_weights` for an ordered chain, `rig_smooth_weights` for adjacency smoothing
55
+ with `locked` vertex indices, and `rig_pin_weights` for rigid selections or a document-space
56
+ rectangle. Preserve setup geometry. Review any dropped deform tracks when changing influence
57
+ layout. The editor's weights mode already provides a heat map and brush; preview `weightBone`
58
+ exports the same kind of inspection for a chosen bone.
59
+
60
+ Clipping uses `rig_set_slot {clip: closedPathAttachmentId}`. Create a closed path with
61
+ `rig_set_path_attachment` on a suitable control; its posed/skinned/deformed geometry masks the
62
+ slot's active image. Several slots can share a mask. A mask can contain a pupil or trim hair
63
+ at a collar; it cannot invent missing painted surfaces. Hidden path slots can still define
64
+ masks, since the explicit mask reference is independent of path-slot visibility.
65
+
66
+ ## Contact, grip swaps and constraint order
67
+
68
+ The runtime solves constraints in ascending `order`; equal orders use IK → transform → path
69
+ array order. A follower must read a hand after that hand's IK has run. `rig_check` reports
70
+ `constraint_order`; `rig_order_constraints` computes a stable dependency order and refuses
71
+ cycles. Dependencies include target ancestors, constrained parents and skinned path weights.
72
+ Inspect deliberately competing constraints instead of dismissing warnings globally.
73
+
74
+ For a handoff:
75
+
76
+ 1. Animate the receiving hand to contact while the prop follows its current owner.
77
+ 2. At the actual contact time, call `rig_attach_to_bone {animation,bone,target,t}`. It evaluates
78
+ the current pose, computes offsets in the new target frame, disables previous generated
79
+ followers and keys the new transform constraint after the existing solve order.
80
+ 3. Key the grip/hand attachment swap and relevant front/back ordering at that same instant.
81
+ Match semantic landmarks on both drawings; matching bbox centres is insufficient.
82
+ 4. Inspect before/at/after contact, then moving frames after transfer, including rotation and
83
+ scale. Author successive contacts in time order. For a release into free motion, fade/disable
84
+ the follower and bake or key the prop's world-equivalent pose before continuing.
85
+
86
+ Transform offset x/y/rotation/scale/shear are keyable in the current checkout. Bone local angles
87
+ must respect `inherit`: `noRotationOrReflection` suppresses the parent's rotation; subtracting
88
+ that rotation again produces a wrong world angle. Use actual evaluated matrices.
89
+
90
+ ## Baked secondary motion
91
+
92
+ Drive lag from speed or acceleration, not absolute driver position: a held tilted pose should
93
+ settle rather than hold its scarf bent forever. `rig_bake_secondary` samples the evaluated
94
+ driver rotation and integrates damped oscillators for the supplied bones, with increasing
95
+ lag along the list. `frequency`, `damping`, `gain`, `maxAngle` and `fps` are controls to tune,
96
+ not universal presets. It writes ordinary editable PCHIP rotation tracks. Loop simulations
97
+ settle to a repeated state before baking, then recenter their first pose to setup.
98
+ The driver must already close at the loop boundary. Followers cannot include the driver or
99
+ its ancestors. Recentering is amplitude-limited as a whole, so it cannot exceed `maxAngle`.
100
+
101
+ Test that the driver actually changes and each intended follower has visible local amplitude.
102
+ For translation-led motion use `driverProp: "x"` or `"y"`; the default `"rotation"` uses angular velocity. Tune gain for the driver’s units (pixels or degrees). Inspect loop velocity and acceleration, and
103
+ confirm that local secondary motion still reads at display size. Baked keys require no runtime
104
+ spring state, so engine entry/exit is reproducible.
105
+
106
+ ## Pilot and semantic review
107
+
108
+ For a new full character, first author one representative idle and the hardest contact/gesture.
109
+ Check foot sliding, hem-over-leg order, face visibility, independent hair, grip registration,
110
+ prop rigidity, and flash timing (e.g. a camera flash only while the raised camera points forward).
111
+ Use a brief-specific numerical rule for each claim; a generic geometry check cannot infer a
112
+ camera's narrative readiness. Present the pilot for user review when visual direction or the
113
+ brief requires it, then preserve accepted choices while scaling to the remaining actions/cast.
@@ -0,0 +1,88 @@
1
+ # One rig, one atlas, a clean directory
2
+
3
+ Every delivered symbol revision uses one atlas shared by all its actions, swaps,
4
+ mesh textures and FX. It remains an editable Golem rig. Do not create a second
5
+ runtime/packed rig beneath it or leave loose layers beside it.
6
+
7
+ ```text
8
+ public/assets/rigs/<id>/
9
+ rig.json
10
+ atlas.png
11
+
12
+ art/symbol-work/<id>/ # outside the delivered rig directory
13
+ layers/ # import sidecar and prepared parts
14
+ work/ # staging rig, temporary assets and editor history
15
+ qa/ # comparisons, playback and reports
16
+ ... # source art, prompts, plans and build scripts as needed
17
+ ```
18
+
19
+ Paths are examples. Keep the user's chosen destination and equivalent authoring
20
+ workspace. One atlas means one per delivered symbol rig, not one per action and
21
+ not a requirement to combine unrelated symbols into a game-wide texture.
22
+
23
+ ## Build, pack, verify, then replace
24
+
25
+ 1. Create or continue a working copy outside the destination. Use the current rig
26
+ as the source of accepted changes; an older build script must not overwrite
27
+ later editor edits. Inspect diffs/history before a rebuild. Put generated
28
+ layers, scripts, logs and `.rig-history` beside this working copy only.
29
+ 2. Run authoring and visual checks on that copy. Export through `rig_export` to a
30
+ fresh staging directory outside the delivered rig folder. Inspect the schema:
31
+ `outDir` is absolute or relative to the source rig directory. Use sufficient
32
+ padding and a supported `maxSize`. If packing fails, inspect oversized parts,
33
+ redundant full-head variants and padding; fit/trim with preserved registration
34
+ or choose a supported larger page. Do not silently produce multiple atlases or
35
+ downscale artwork until it changes the accepted appearance.
36
+ 3. Verify the exported pair with `rig_validate`, `rig_check`, and
37
+ `python3 <skill-dir>/scripts/check_package.py <staging-dir>`. This helper uses
38
+ only Python's standard library. All asset `src` values
39
+ must reference the same `atlas.png`; frame rectangles must fit the atlas. This
40
+ directory check supplements Golem validation, it does not replace it.
41
+ 4. Render the working and exported rigs at identical times/scale/background:
42
+ setup, all expressions, active FX, deformation extremes and transitions. Inspect
43
+ meaningful differences. Test actual playback at cell size. For game delivery,
44
+ exercise the adapter and lifecycle using this exported rig.
45
+ 5. Install the verified `rig.json` and `atlas.png` together at the canonical path.
46
+ Preserve the previous pair outside the rig folder until replacement is checked.
47
+ Update game/editor references that used a former nested `packed/rig.json`. Avoid
48
+ exposing a half-updated pair to a live reader; use the project's staging/swap
49
+ mechanism or pause the task-owned reader during the two-file replacement.
50
+ 6. Run the package check on the destination and load that exact path again. Link
51
+ this pair as the final rig and keep previews in the external QA workspace.
52
+
53
+ The current exporter can repack assets with `frame` data, so a packed rig is a
54
+ usable starting point for later edits. Verify version behavior for rotated or
55
+ trimmed imported regions; normalize them in staging when the exporter does not
56
+ support their metadata. Never hand-edit frame coordinates to pretend packing ran.
57
+
58
+ ## Make verification apply to the installed revision
59
+
60
+ Record hashes of the source rig and its textures before staging edits. Before
61
+ replacement, compare the live package to that source snapshot; if it changed,
62
+ preserve and reconcile the new edits instead of installing an older build over it.
63
+ Bind visual review, checker findings and packed-equivalence results to the exact
64
+ rig/texture hashes tested. A timestamped narrative report alone cannot establish
65
+ that the current package was reviewed.
66
+
67
+ Build/install scripts must inspect validation results and checker findings, not
68
+ only tool invocation success. Fail on validation errors and unreviewed warnings.
69
+ If a warning is deliberately accepted, record the exact finding, concrete reason
70
+ and visual evidence for that revision; do not blanket-ignore its category. A
71
+ truncated checker summary is not a complete finding list. Keep the checker active
72
+ and reject newly introduced findings. Require this verification before install.
73
+
74
+ ## History and existing clutter
75
+
76
+ Golem mutations currently write `.rig-history` beside the edited rig. Mutate a
77
+ staging copy to keep the delivery folder clean. A read-only preview does not need
78
+ a history directory. If a user edits the delivered rig in the editor, retain the
79
+ new rig and preserve the resulting history in the external authoring workspace
80
+ when finalizing that revision; keep a complete working package so undo remains
81
+ usable. Do not disable Golem history or discard manual changes.
82
+
83
+ For an existing cluttered rig folder, identify referenced textures and inspect
84
+ game/editor paths first. Move task-owned layers, scripts, QA, duplicate exports
85
+ and history to the external workspace only after the new self-contained pair is
86
+ verified and consumers use its canonical path. Do not recursively delete unknown
87
+ files. If ownership is unclear, report the specific unresolved files rather than
88
+ claiming the clean-directory check passed.
@@ -0,0 +1,106 @@
1
+ # Reproducible gates without false passes
2
+
3
+ Visual playback and measurements answer different questions. For a new action, record its
4
+ expected poses, semantic rules, minimum visible motion, allowed departures and required
5
+ animation IDs before tuning thresholds. A successful tool call is not a passed gate.
6
+
7
+ ## Capture identity and coverage
8
+
9
+ Render explicit times with `rig_render_preview`, using `transparent:true` and a new `framesDir`
10
+ for alpha QA. The directory receives `manifest.json`: the exact input document/file, textures,
11
+ renderer, optional fill masks, sample times and output frames are hashed. Then call
12
+ `rig_verify_render` with the rig, directory, animation and the independently expected times.
13
+ It fails for changed inputs, changed renderer, changed frames, missing/extra frames or the wrong
14
+ animation/time set. A stale file modification timestamp is not a reliable substitute for hashes.
15
+ Keep capture directories outside the delivered package.
16
+ The manifest also records scale, crop, backgrounds and diagnostic overlays. Image gates reject
17
+ bone/weight/onion overlays, an undeclared crop/scale, missing settings or a normal capture
18
+ passed off as a fill-debug capture. Re-capture old frames that lack these settings.
19
+
20
+ For loops sample `[0,D)` at the chosen frame rate and separately inspect the unwrapped endpoint.
21
+ Include extrema and frames immediately before/at/after attachment, grip, flash and draw-order
22
+ changes. Explicit expected samples are the contract; never silently inspect whichever PNGs
23
+ happen to be on disk. A matching manifest establishes provenance, not complete sampling of
24
+ all mathematically possible motion.
25
+
26
+ ## Which measurements support which claims
27
+
28
+ | Claim | Measurement / evidence |
29
+ |---|---|
30
+ | Setup matches approved art | Premultiplied/composited RGB difference on black and white, silhouette/bounds, local worst regions, face landmarks |
31
+ | Hidden repairs remain hidden at rest | Render with `debugFillMasks` (source-file → grayscale-mask file); count visible magenta outside reviewed replacement regions |
32
+ | No holes or seams | True alpha across all expected frames; compare against the approved art's own holes, never discard large holes |
33
+ | Motion reads | Required bone/landmark amplitude at display size; velocity, acceleration and jerk with units and sample interval |
34
+ | No teleports | World positions on adjacent frames and denser samples around a suspect change; isolate sharp outliers from a smooth fast swing |
35
+ | Loop closes | Values and one-sided velocity for every numeric channel; exact attachments, visibility, colour and draw order; true terminal pose |
36
+ | Anatomy stays sound | Landmark ratios, rigid-prop distances, planted-foot drift, mesh folds and silhouette review |
37
+ | Face/hem order | Brief-specific `frontOf` slot pairs plus rendered overlap; do not label intended hair overlap a defect |
38
+ | One drawing per slot | Schema plus active attachment state; simultaneous layers require distinct slots |
39
+ | Secondary exists | Declared `secondaryBones` local motion and visible amplitude; frozen local rotations should not pass because the torso moved |
40
+ | Flash/contact is valid | Sample actual game/animation state around the trigger and verify the required pose/visibility conditions |
41
+ | Swaps are registered | Shared named UV landmarks via `rig_check.landmarks`, including both sides of the switch |
42
+ | Transitions work in game | Drive the real `AnimationState`/`RigPlayer` with actual mix settings and all mapped IDs |
43
+
44
+ `rig_check` adds `pass_key_stop`, `velocity_jump`, `acceleration_jump`, `loop_velocity`,
45
+ `secondary_frozen`, `draw_order_rule` and `attachment_landmark_jump` to existing geometry
46
+ checks. Acceleration findings are informational: PCHIP is C1, not C2. `motion_pop` looks for
47
+ an isolated displacement spike relative to neighbouring frames; a smooth fast gesture alone
48
+ is not evidence of a teleport. Source-atlas bboxes are provenance, not document placement:
49
+ mark `asset.origin.bboxSpace: "source"` so `patch_offset` does not compare unrelated spaces.
50
+
51
+ For image gates run `scripts/image_gates.py config.json report.json` (Pillow + numpy).
52
+ Its `--help` gives the schema. `rest` compares RGB over black/white, worst 16-pixel tile,
53
+ silhouette bounds and optional debug fill
54
+ visibility; `holes` measures added enclosed gaps/partial-alpha cracks of any size. It verifies
55
+ rig, texture, renderer and frame hashes before judging pixels. Supply a reference already
56
+ registered at the capture's exact scale. Use current transparent captures by preference.
57
+ Declare that scale in the gate configuration (`scale:1` by default) and `region` when explicitly
58
+ comparing a crop. An empty art frame fails even though it has no enclosed holes.
59
+ For an older opaque-only renderer, reconstruct alpha from the same pose over two contrasting
60
+ backgrounds and validate that the material uses ordinary alpha compositing; additive/multiply
61
+ content can invalidate that reconstruction.
62
+
63
+ ## Brief-specific motion gates
64
+
65
+ `rig_run_gates` returns explicit pass/fail results with the expected and inspected sample counts.
66
+ Use `motion` rules for minimum amplitude, maximum speed/acceleration/jerk/step and setup drift
67
+ (planted feet); `distances` for rigid-prop proportions or contact tolerances; `conditions` to
68
+ require a visible flash only within a valid camera pose; `requiredAnimations` for the complete
69
+ game contract; and `transitions` to measure real AnimationState playback rather than a substitute
70
+ lerp. Conditions include key-switch times and fail if their supposedly required effect never
71
+ appears. Motion thresholds carry units and depend on the declared fps.
72
+ An empty gate configuration or measurement rule without any threshold is an error. Transition
73
+ steps include bone endpoints and unchanged attachment outlines; swaps use their declared landmarks.
74
+
75
+ ```json
76
+ {"animation":"idle","fps":60,"requiredAnimations":["idle","win"],
77
+ "motion":[{"bone":"hair_tip","channel":"rotation","space":"local","minAmplitude":1},
78
+ {"bone":"foot_left","maxSetupDrift":0.5}],
79
+ "transitions":[{"from":"idle","to":"win","mix":0.18,"maxStep":8}]}
80
+ ```
81
+
82
+ These numbers illustrate the schema, not universal pass limits. Combine semantic gates with
83
+ `rig_check` front-order/landmark rules and current rendered image gates. A numeric “foot” or
84
+ “hair” id does not verify that its artwork is anatomically the correct feature.
85
+
86
+ ## Exclusions are measured, not silent skips
87
+
88
+ Every exception needs a concrete reason, explicit mask and frame indices. The report includes
89
+ excluded pixel fraction and fraction of affected frames. Limits `maxExcludedArea` and
90
+ `maxExcludedFrames` are chosen for the brief and recorded in the configuration; exceeding them
91
+ fails. An all-excluded frame, absent frames, empty reference, stale render or wrong image size
92
+ also fails. Never raise limits solely until a defect passes. A new large hole touching one
93
+ approved curl does not make the entire connected component exempt: baseline gaps are compared
94
+ pixel by pixel, and new exposed area remains measured.
95
+
96
+ A rest-pose negative space may move during animation. Model that legitimate moving opening
97
+ explicitly or register its local reference; do not grow a global exemption rectangle until
98
+ most action frames disappear from QA. Likewise a regenerated prop is reviewed by shape and
99
+ landmarks, not silently zeroed out of the entire setup-difference image.
100
+
101
+ Before relying on a gate, inject representative failures into temporary inputs: remove one
102
+ frame, modify the rig/texture, introduce a large internal gap, freeze the secondary track,
103
+ break a discrete loop endpoint, move one swap landmark, or misorder the IK follower. The gate
104
+ must fail for that reason. Restore original inputs afterward. Report exact checked sample counts,
105
+ accepted exceptions and remaining visual uncertainty; “16 green gates” is not meaningful if
106
+ those gates never observed the current rig or excluded the affected action.
@@ -0,0 +1,87 @@
1
+ # Applying Spine and Rive mechanisms in Golem
2
+
3
+ Read for imported rigs, flexible full characters, combined expression controls or interactive
4
+ motion. Official reference exports and playback were inspected on 2026-10-05. Source
5
+ mechanisms and Golem capability are separate facts: check the current schemas and warnings.
6
+
7
+ ## Map mechanisms to controls
8
+
9
+ | Source mechanism | Golem construction | Verification |
10
+ |---|---|---|
11
+ | Spine region / Rive parented rigid element | Region attachment under a bone | Prop/hand outline remains rigid at maximum rotation |
12
+ | Weighted mesh | Mesh with normalized influence weights; rigid selections pinned | Bind pose, bend in both directions, compression and triangle/silhouette review |
13
+ | IK endpoint + path shape | Stable endpoint targets plus a path-controlled chain | Contacts stay planted while hips move; constraint order and chain spacing remain valid |
14
+ | Attachment timeline / Rive Solo | One slot for mutually exclusive attachments; step keys | Before/at/after switch; landmarks, visibility and local alpha agree |
15
+ | Draw-order keys / Rive draw-order rules | Golem drawOrder track independent of hierarchy | Hands, clothing and props cross correctly at contact |
16
+ | Joystick / blend state | Semantic controls and bindings, or explicit runtime logic | Combined values and transitions; each output property has one intended owner |
17
+ | State-machine layers | Base/main animations and named overlays where semantics match | Interrupt, queue, release, transition and replay, not only direct timeline seeking |
18
+
19
+ The [Spine examples](https://esotericsoftware.com/spine-examples) provide editable/exported
20
+ rigs for inspecting bones, weights and constraints. [Rive bones](https://rive.app/docs/editor/manipulating-shapes/bones),
21
+ [meshes](https://rive.app/docs/editor/manipulating-shapes/meshes) and
22
+ [joysticks](https://rive.app/docs/editor/manipulating-shapes/joysticks) explain the distinction
23
+ between rigid parenting, deformation and reusable pose controls.
24
+
25
+ ## Import fidelity before motion polish
26
+
27
+ Inspect `rig_import_spine` warnings. In updated Golem, `skin` selects a coherent setup and
28
+ attachment lookup, with default-skin fallback. Other skins' attachments remain editable
29
+ variants; this is not a dynamic Spine skin API. Linked meshes lower to independent editable
30
+ mesh attachments with inherited deform keys. Texture lookup follows `path`, then an explicit
31
+ attachment `name`, then the placeholder key. Sequences lower to attachment switches.
32
+ File import resolves all named pages of a multi-page atlas; pack to the established one-atlas
33
+ delivery afterward. A single `image` override applies only to a one-page source.
34
+
35
+ Spine clipping lowers to a closed path with `clipping: {end?: slotId}`. Its active attachment
36
+ starts a clipping span in the current draw order, ending after the named slot. Removing that
37
+ attachment disables the span. Golem's explicit `slot.clip` is a separate authoring mask and
38
+ can use a hidden path. Do not replace animated span semantics with a fixed list of masked
39
+ slots, or make all explicit masks depend on path-slot visibility.
40
+
41
+ Keys may contain `componentEase`, one easing per numeric-array component. Preserve it when
42
+ editing imported RGBA keys: alpha and RGB may have different curves. Independent component
43
+ easing is segment interpolation; do not replace it with PCHIP without intending to change
44
+ the source motion. Equal numeric endpoints do not imply a hold: an absolute Spine Bézier
45
+ curve can leave that value and return. The importer lowers such scalar excursions to its
46
+ ten linear sampling segments.
47
+
48
+ Warnings identify partial import. Spine physics constraints/timelines and dark two-color
49
+ tint are not reproduced by Golem; baking secondary motion is an authoring alternative,
50
+ not a proof that it matches the original physics. `.riv` import/export is not implemented.
51
+ Recreate supported controls from reference evidence without claiming Rive compatibility.
52
+ Treat zero-scale path poses as special fidelity checks: orientation can become ambiguous
53
+ and differ from Spine even when all bone origins match. Inspect the rendered silhouette.
54
+
55
+ ## Build motion through extremes and transitions
56
+
57
+ Use [Spineboy](https://esotericsoftware.com/spine-examples-spineboy) to study IK contacts,
58
+ rigid props, visibility and portal clipping; [Raptor](https://esotericsoftware.com/spine-examples-raptor)
59
+ for continuous weighted surfaces; [Stretchyman](https://esotericsoftware.com/spine-examples-stretchyman)
60
+ for endpoint/shape control; [Mix and Match](https://esotericsoftware.com/spine-examples-mix-and-match)
61
+ for variants. Compare a setup, anticipation, maximum bend, contact, follow-through and
62
+ settled pose rather than copying the entire hierarchy into another character.
63
+
64
+ Put secondary controls under the part that carries them, but contact targets under a stable
65
+ space. Choose overlapping cuts or an alternate view when one weighted image loses its
66
+ silhouette. Bake purposeful lag only after the driver's beats and contacts read clearly.
67
+ Hold a driven pose long enough to verify settling; include reverse travel and loop endpoints.
68
+
69
+ Rive [layers](https://rive.app/docs/editor/state-machine/layers) and
70
+ [transitions](https://rive.app/docs/editor/state-machine/transitions) show why a working clip
71
+ does not establish working interaction. Write the event/input → action mapping. Declare
72
+ ownership of blink, gaze, expression, mouth, head turn and body channels; avoid two overlays
73
+ silently replacing the same property. Exercise interruptions and entry/exit while an overlay
74
+ is active. Joystick scrubbing and time playback are different uses of an authored timeline.
75
+
76
+ ## Evidence for a quality claim
77
+
78
+ Use a matching source runtime for numerical fidelity checks: origins, full matrices, visible
79
+ attachment identity, skinned/deformed vertices, draw order and color/alpha. Enumerate every
80
+ source clip and selected skin, and include samples either side of discrete changes. Float32
81
+ key times can place two runtimes on different sides of an exact boundary; sample a bounded
82
+ epsilon before/after and report it. Do not suppress real jumps with a large tolerance.
83
+
84
+ Numerical agreement does not prove seam-free artwork. Inspect normal-speed playback at the
85
+ intended size, plus joint closeups, masks, pupil edges, swaps and transition extremes. A
86
+ contact sheet is pose evidence, not continuous playback evidence. Preserve source URLs,
87
+ versions, tested coverage and concrete exceptions in QA outside the packed delivery.
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env python3
2
+ """Inspect a PNG and crop parts using an agent-authored manifest; requires Pillow."""
3
+ import argparse
4
+ import hashlib
5
+ import json
6
+ import re
7
+ from pathlib import Path
8
+ from PIL import Image, ImageDraw
9
+
10
+
11
+ def checker(size):
12
+ bg = Image.new('RGBA', size, '#b7bcc4')
13
+ draw = ImageDraw.Draw(bg)
14
+ for y in range(0, size[1], 20):
15
+ for x in range(0, size[0], 20):
16
+ if (x // 20 + y // 20) % 2:
17
+ draw.rectangle((x, y, x + 19, y + 19), fill='#e5e7eb')
18
+ return bg
19
+
20
+
21
+ def run():
22
+ parser = argparse.ArgumentParser(description=__doc__, epilog='Manifest: [{"id":"head","rect":[x,y,width,height]}, ...]. Coordinates are ORIGINAL PNG pixels, manually selected from raster inspection. Output PNG crops preserve all RGBA values, without trimming, rotation or alpha cleanup.')
23
+ parser.add_argument('atlas', type=Path)
24
+ parser.add_argument('out_dir', type=Path)
25
+ parser.add_argument('--manifest', type=Path, help='Agent-authored JSON crop list; omit for inspection only')
26
+ parser.add_argument('--inspection-width', type=int, default=1600)
27
+ parser.add_argument('--overwrite', action='store_true', help='Replace only this invocation\'s named outputs')
28
+ args = parser.parse_args()
29
+ if args.inspection_width < 1:
30
+ parser.error('--inspection-width must be positive')
31
+ with Image.open(args.atlas) as source:
32
+ if source.format != 'PNG':
33
+ parser.error('input must be a PNG')
34
+ image = source.convert('RGBA')
35
+ specs = json.loads(args.manifest.read_text()) if args.manifest else []
36
+ if not isinstance(specs, list):
37
+ parser.error('manifest must be a list of {id, rect} objects')
38
+ ids = set()
39
+ for item in specs:
40
+ if not isinstance(item, dict) or set(item) != {'id', 'rect'}:
41
+ parser.error('each part must contain exactly id and rect')
42
+ name, rect = item['id'], item['rect']
43
+ if not isinstance(name, str) or not re.fullmatch(r'[a-z][a-z0-9_]*', name) or name in ids:
44
+ parser.error('part IDs must be unique snake_case names')
45
+ ids.add(name)
46
+ if not isinstance(rect, list) or len(rect) != 4 or any(type(v) is not int for v in rect):
47
+ parser.error(f'{name}: rect must contain four integers')
48
+ x, y, w, h = rect
49
+ if x < 0 or y < 0 or w < 1 or h < 1 or x + w > image.width or y + h > image.height:
50
+ parser.error(f'{name}: rectangle is outside {image.width}x{image.height}')
51
+ outputs = [args.out_dir / 'inspection.jpg', args.out_dir / 'source-record.json']
52
+ if specs:
53
+ outputs += [args.out_dir / 'parts.jpg'] + [args.out_dir / 'layers' / f'{s["id"]}.png' for s in specs]
54
+ protected = {args.atlas.resolve()}
55
+ if args.manifest:
56
+ protected.add(args.manifest.resolve())
57
+ for output in outputs:
58
+ if output.resolve() in protected:
59
+ parser.error(f'output would overwrite an input: {output}')
60
+ if output.exists() and not args.overwrite:
61
+ parser.error(f'output exists: {output}; choose another directory or --overwrite')
62
+ args.out_dir.mkdir(parents=True, exist_ok=True)
63
+ inspection = checker(image.size)
64
+ inspection.alpha_composite(image)
65
+ inspection.thumbnail((args.inspection_width, image.height))
66
+ inspection.convert('RGB').save(outputs[0], quality=92)
67
+ record = {'source': str(args.atlas.resolve()), 'sha256': hashlib.sha256(args.atlas.read_bytes()).hexdigest(), 'size': list(image.size), 'inspectionSize': list(inspection.size), 'sourcePixelsPerInspectionPixel': [image.width / inspection.width, image.height / inspection.height], 'parts': specs, 'operations': 'RGBA crop only; no trim, alpha cleanup, rotation'}
68
+ if specs:
69
+ (args.out_dir / 'layers').mkdir(exist_ok=True)
70
+ sheet = checker((1000, ((len(specs) + 3) // 4) * 240))
71
+ draw = ImageDraw.Draw(sheet)
72
+ for i, item in enumerate(specs):
73
+ x, y, w, h = item['rect']
74
+ part = image.crop((x, y, x + w, y + h))
75
+ part.save(args.out_dir / 'layers' / f'{item["id"]}.png')
76
+ part.thumbnail((230, 190))
77
+ sx, sy = (i % 4) * 250, (i // 4) * 240
78
+ sheet.alpha_composite(part, (sx + (250 - part.width) // 2, sy + 42))
79
+ draw.text((sx + 7, sy + 5), item['id'], fill='black')
80
+ draw.text((sx + 7, sy + 20), f'{x},{y} {w}x{h}', fill='black')
81
+ sheet.convert('RGB').save(args.out_dir / 'parts.jpg', quality=92)
82
+ outputs[1].write_text(json.dumps(record, indent=2) + '\n')
83
+ print(json.dumps({'out': str(args.out_dir.resolve()), 'size': list(image.size), 'parts': len(specs)}))
84
+
85
+
86
+ if __name__ == '__main__':
87
+ run()
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env python3
2
+ """Check the two-file Golem delivery contract; run rig_validate separately."""
3
+
4
+ import argparse
5
+ import json
6
+ import struct
7
+ from pathlib import Path
8
+
9
+
10
+ def check_package(directory):
11
+ directory = Path(directory)
12
+ if not directory.is_dir():
13
+ return [f"Not a directory: {directory}"]
14
+ errors = []
15
+ expected = {"rig.json", "atlas.png"}
16
+ actual = {p.name for p in directory.iterdir()}
17
+ if actual != expected:
18
+ errors.append(f"Expected only rig.json + atlas.png; missing={sorted(expected - actual)}, extra={sorted(actual - expected)}")
19
+ for name in expected:
20
+ path = directory / name
21
+ if path.is_symlink() or not path.is_file():
22
+ errors.append(f"{name} must be a regular file inside the package")
23
+ if any(not (directory / name).is_file() or (directory / name).is_symlink() for name in expected):
24
+ return errors
25
+ try:
26
+ with (directory / "atlas.png").open("rb") as stream:
27
+ header = stream.read(24)
28
+ if len(header) != 24 or header[:16] != b"\x89PNG\r\n\x1a\n\x00\x00\x00\x0dIHDR":
29
+ raise ValueError("invalid PNG header")
30
+ width, height = struct.unpack(">II", header[16:24])
31
+ if width == 0 or height == 0:
32
+ raise ValueError("empty atlas dimensions")
33
+ doc = json.loads((directory / "rig.json").read_text())
34
+ except (OSError, ValueError) as error:
35
+ return errors + [str(error)]
36
+ assets = doc.get("assets") if isinstance(doc, dict) else None
37
+ if not isinstance(assets, list) or not assets:
38
+ return errors + ["rig.json must contain a nonempty assets array"]
39
+ for index, asset in enumerate(assets):
40
+ if not isinstance(asset, dict):
41
+ errors.append(f"assets[{index}] must be an object")
42
+ continue
43
+ label = asset.get("id", f"assets[{index}]")
44
+ if asset.get("src") != "atlas.png":
45
+ errors.append(f"{label}: src must be atlas.png")
46
+ frame = asset.get("frame")
47
+ if not isinstance(frame, list) or len(frame) != 4 or any(type(v) is not int for v in frame):
48
+ errors.append(f"{label}: expected integer frame [x, y, width, height]")
49
+ continue
50
+ x, y, w, h = frame
51
+ if x < 0 or y < 0 or w <= 0 or h <= 0 or x + w > width or y + h > height:
52
+ errors.append(f"{label}: frame {frame} is outside {width}x{height} atlas")
53
+ return errors
54
+
55
+
56
+ def main():
57
+ parser = argparse.ArgumentParser(description=__doc__)
58
+ parser.add_argument("directory", type=Path)
59
+ args = parser.parse_args()
60
+ errors = check_package(args.directory)
61
+ if errors:
62
+ for error in errors:
63
+ print(f"ERROR: {error}")
64
+ return 1
65
+ print("Package OK: rig.json + one atlas.png; all asset frames are in bounds.")
66
+ print("Run Golem validation and packed playback QA separately.")
67
+ return 0
68
+
69
+
70
+ if __name__ == "__main__":
71
+ raise SystemExit(main())