@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.
- package/README.md +18 -0
- package/dist/editor.css +3 -4
- package/dist/editor.js +177 -75
- package/dist/lib/cli.js +302 -126
- package/dist/lib/cli.js.map +4 -4
- package/dist/lib/e8/agent.d.ts +35 -0
- package/dist/lib/e8/host.d.ts +22 -0
- package/dist/lib/e8/node.d.ts +66 -0
- package/dist/lib/e8/runtime.d.ts +10 -0
- package/dist/lib/e8/schema.d.ts +3 -0
- package/dist/lib/e8/types.d.ts +80 -0
- package/dist/lib/e8-agent.js +6781 -0
- package/dist/lib/e8-agent.js.map +7 -0
- package/dist/lib/e8-client.js +116 -0
- package/dist/lib/e8-host.js +6929 -0
- package/dist/lib/e8-host.js.map +7 -0
- package/dist/lib/e8-runtime.js +2024 -0
- package/dist/lib/e8-runtime.js.map +7 -0
- package/dist/lib/e8-schema.js +7 -0
- package/dist/lib/e8-schema.js.map +7 -0
- package/dist/lib/editor/api.d.ts +1 -0
- package/dist/lib/editor/embed.d.ts +9 -0
- package/dist/lib/editor/server.d.ts +23 -1
- package/dist/lib/editor/store.d.ts +107 -0
- package/dist/lib/editor/timeline.d.ts +15 -0
- package/dist/lib/editor-entry.d.ts +1 -1
- package/dist/lib/editor-entry.js +303 -126
- package/dist/lib/editor-entry.js.map +4 -4
- package/dist/lib/harness.js +69 -14
- package/dist/lib/interactive-editor.js.map +1 -1
- package/dist/lib/rig-anim.d.ts +2 -0
- package/dist/lib/rig-control-qa.d.ts +27 -0
- package/dist/lib/rig-format.d.ts +989 -0
- package/dist/lib/rig-import-layers.d.ts +2 -0
- package/dist/lib/rig-version.d.ts +12 -0
- package/dist/lib/runtime.js +69 -14
- package/dist/lib/runtime.js.map +3 -3
- package/dist/lib/spine-import.d.ts +4 -0
- package/dist/lib/tool-schema.d.ts +3 -0
- package/dist/lib/tools.d.ts +2 -0
- package/dist/lib/tools.js +257 -73
- package/dist/lib/tools.js.map +4 -4
- package/editor.html +2 -2
- package/package.json +38 -3
- package/skills/e8-golem/SKILL.md +71 -0
- package/skills/golem-symbol-animation/SKILL.md +87 -0
- package/skills/golem-symbol-animation/agents/openai.yaml +4 -0
- package/skills/golem-symbol-animation/references/facial-controls.md +48 -0
- package/skills/golem-symbol-animation/references/game-engine-integration.md +133 -0
- package/skills/golem-symbol-animation/references/golem-authoring.md +138 -0
- package/skills/golem-symbol-animation/references/interactive-editor.md +17 -0
- package/skills/golem-symbol-animation/references/motion-craft.md +113 -0
- package/skills/golem-symbol-animation/references/packed-delivery.md +88 -0
- package/skills/golem-symbol-animation/references/quantitative-qa.md +106 -0
- package/skills/golem-symbol-animation/references/spine-rive-study.md +87 -0
- package/skills/golem-symbol-animation/scripts/atlas_parts.py +87 -0
- package/skills/golem-symbol-animation/scripts/check_package.py +71 -0
- package/skills/golem-symbol-animation/scripts/image_gates.py +143 -0
- package/skills/golem-symbol-cutting/SKILL.md +70 -0
- package/skills/golem-symbol-cutting/agents/openai.yaml +4 -0
- package/skills/golem-symbol-cutting/assets/h1/h1.atlas.png +0 -0
- package/skills/golem-symbol-cutting/assets/h1/h1.png +0 -0
- package/skills/golem-symbol-cutting/references/cutting-workflow.md +92 -0
- package/skills/golem-symbol-cutting/references/facial-layers.md +44 -0
- package/skills/golem-symbol-cutting/references/generation-workflow.md +109 -0
- package/skills/golem-symbol-cutting/references/golem-handoff.md +62 -0
- package/skills/golem-symbol-cutting/references/h1-example.md +46 -0
- package/skills/golem-symbol-cutting/references/hybrid-workflow.md +119 -0
- package/skills/golem-symbol-cutting/references/registration-and-motion.md +89 -0
- package/skills/golem-symbol-cutting/references/spine-rive-construction.md +73 -0
- package/skills/golem-symbol-cutting/scripts/atlas_parts.py +87 -0
- 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())
|