The training dummies are KayKit skeletons, each ragdoll part drawing the bones it carries #26

Merged
agra merged 8 commits from task/3935-the-training-dummies-are-skeletons-a-cc0 into master 2026-10-30 18:31:49 +02:00
Owner

Tracker: #3935

Every training dummy, hung or free, now draws as KayKit's CC0 Skeleton Minion instead of 12 bare capsules. Each ragdoll part draws the share of the skeleton it carries, at that part's pose every frame. The ragdoll's capsules and joints are fitted to the skeleton.

The asset

  • Source. KayKit "Character Pack: Skeletons" by Kay Lousberg, https://kaylousberg.itch.io/kaykit-skeletons. The page's free file, downloaded on 2026-10-30, is KayKit_Skeletons_1.1_FREE.zip (8177445 bytes, SHA-256 21fbad59…d3e2). The prefab comes from its characters/gltf/Skeleton_Minion.glb.
  • Version and licence. The page now serves version 1.1, not the 1.0 the task names. The archive's License.txt says CC0 1.0.
  • Which skeleton. The Minion is the pack's plainest skeleton: a scarf, a belt and short trousers, with no helmet or hood. No better CC0 skeleton turned up: Quaternius's packs now carry their own licence.
  • The import. assets/prefabs/kaykit.py writes assets/prefabs/skeleton_minion.blend: the rig and its nine skinned meshes (5288 triangles) with their weights.
    • It scales the skeleton so the top of its head stands 1.81 m up, the dummy's height, and turns it to face the template's way.
    • It poses the skeleton as a hanging dummy: upper arms 65° down from the T-pose, feet pointed 60° down. The rest pose stays the T-pose, so the Mobs stream can reuse the rig.
    • Each bone names the ragdoll part it belongs to in its custom property hulken_part.
    • Materials keep only their Base Color and image, as kenney.py's do. The eyes' glowing material takes its glow's yellow as its Base Color, since hulken draws no glow.
    • The texture becomes assets/textures/skeleton_texture.png, identical texel for texel to the archive's. kenney.py gained an if __name__ == "__main__" guard so kaykit.py can reuse its PNG writer.
    • It saves the file on the Standard view transform, as the bake requires, so Blender displays the skeleton's colours as hulken draws them.
    • Rerunning the script reproduces the prefab and the texture exactly.
  • Each new file has a .md beside it with its source, URL, version, file and licence.

The bake

  • tools/bake/dummy.py renders the prefab with the scene bake's render engine and takes the skeleton as Blender's render draws it, in its saved pose: each mesh as the render depsgraph evaluates it, and the bones of the rig that depsgraph evaluates. Each triangle goes to the part whose bones weigh its corners most, judged by the weights summed over all three corners; a polygon whose material index is past the last slot draws in the last slot, as in Blender. The stage (stage dummy) puts each triangle in its part's own space and writes baked/dummy.hbm: textures plus 12 parts, in the template's order.
  • What the bake refuses:
    • more than one rig, or anything the render draws besides the meshes the rig skins, such as a light or an instance of the model;
    • a mesh of the model the render leaves out, whether hidden itself, through its collection or by its view layer;
    • a mesh with any modifier besides the one Armature, or whose Armature names another rig or none, is off in the render or deforms it by no vertex group;
    • a mesh object, the view layer or the scene set otherwise than a new one where that changes the render, such as a mesh the camera does not see, a held-out collection or a compositor with no Render Layers node, and a view layer off for rendering;
    • a view transform other than Standard or a working space other than Linear Rec.709. A scene's bake refuses the same, and both now call one check, check_colours in extract.py. Under Linear Rec.2020 the eyes' yellow would bake as another colour while Blender displays it unchanged;
    • a hulken_part that is not a name, or one the template does not have;
    • a vertex weighted by no bone with a part;
    • an empty material slot, or a mesh that draws an image with no UVs;
    • anything a scene's materials are refused for.
  • The fit check. The bake also fails unless the template fits the skeleton, within 1 mm:
    • every bone a part carries lies inside that part's capsule;
    • every bone starting from another part's bone starts at the template's joint between those two parts;
    • every template joint sits at such a bone;
    • every part carries a triangle;
    • the skeleton's highest point is the top of the head a hung dummy hangs from.

The ragdoll

  • Shape. The template keeps its 12 parts, 11 joints, masses (75.5 kg), limits and damping. Its capsules and joint points are refitted to this big-headed, short-legged skeleton:
    • the head is a 0.37 m capsule whose top is the hang point;
    • each shin carries its foot;
    • the shoulders and elbows turn with the upper arm, so an elbow still bends forward;
    • parts no joint joins start at least 1.6 cm apart, and sim_dummy now checks this.
  • The hung dummy (sim_dummy):
    • It sleeps after 0.98 s; master's sleeps after 0.53 s. Pointing the feet down keeps each shin's weight from swinging its knee: with the feet level, it took 7.2 s and failed the 5 s bound.
    • Walking into it swings its pelvis 0.57 m (master 1.08 m; the bound is 0.2 m). The body now hangs lower against the player's capsule.
    • It settles again after 9.95 s (master 13.7 s).
    • It stays within its limits plus 0.03°.
  • The bench scene. One outer-row dummy now leans 18°, down from 25°: at 25° its new head started 4 cm inside its neighbour's. With that change, every car still meets a dummy, every dummy meets another, and no dummy touches the frame. Limits are passed by at most 5.5° (the bound is 20°).
  • Bench timing. Run interleaved under taskpolicy -b, five rounds at the merge head 51235f4 and master 5af4834, three of them with the pre-merge head 629ec49 too: worst tick 0.512–0.522 ms at the merge head, 0.517–0.523 ms at 629ec49 and 0.516–0.530 ms at master; median 0.425–0.427 ms at the merge head and 0.425–0.429 ms at 629ec49, against master's 0.378–0.380 ms. Gate run 972's own bench read a worst tick of 0.514 ms and a median of 0.333 ms. The budget is 2 ms.

Drawing and tests

  • src/render/dummies.sx loads baked/dummy.hbm and gives every dummy part its share of the skeleton. No capsule draws.
  • tests/dummy_draws.sx's Metal golden shows a hung skeleton and a free one fallen beside it. With master's capsule drawing put back, it fails: 13136 of 144000 pixels differ, by up to 213.
  • tests/dummy_model.sx checks each stage rule against a skeleton built from the template. tests/bake_dummy.py checks each Blender rule, in one Blender session.
  • Mutation runs, at 629ec49 (the merge changes none of the code they mutate or the tests that kill them): all 31 mutants of the stage, the Blender extract and the drawing fail a test, and so do all 18 mutants of the extract's render rules, among them taking the model from the viewport's depsgraph, reading the bones or the meshes' places from the unevaluated objects, dropping the render, object, view layer or scene checks, dropping the Armature's vertex-group or render clause, dropping the material-slot clamp, and giving a triangle to the part weighing one corner most.
  • Mutation runs at 014d8e3, each run against bake_dummy and bake_extract. All five of the colour and rig rules' mutants fail a test:
    • the dummy's bake without the colour check (its AgX and Linear Rec.2020 cases);
    • skinned() without its rig clause (its case "a mesh whose Armature names no rig");
    • the colour check without its working space, or without its view transform (the matching case in both tests);
    • the scene bake without the colour check (bake_extract's AgX and Linear Rec.2020 cases).

Checks

  • The merge of master. 51235f4 merges master 5af4834 (#3934, barrels as Cylinder bodies). The one conflict, assets/scenes/first_map.blend.md, keeps master's Props bullet (the barrels are Cylinder bodies) and this branch's Dummies bullet. first_map.blend is master's, unchanged. Against 629ec49 the merge changes exactly what master changed against the merge base, file by file.
  • sak-gate full: gate run 961 passed at 5a879eb, gate run 962 at 64c323d (a README-only change) and gate run 970 at 629ec49, 43 tests each, gate run 972 at the merge head 51235f4, 44 tests (with master's barrels), and gate run 975 at 014d8e3, 44 tests.
  • The prefab on Standard. kaykit.py was rerun at 014d8e3 over a fresh download of the archive, whose SHA-256 matches. The prefab's whole RNA dump, every scene, object, mesh, armature, material, image and collection (114951 lines), differs from 51235f4's only in view_settings.view_transform, AgX against Standard. The texture is byte-identical. tools/bake.sh at 014d8e3 and at 51235f4 gives byte-identical dummy.hbm, test_scene.hbs/.hbw and first_map.hbs/.hbw. 51235f4's prefab, saved on AgX, is now refused at view_settings.view_transform.
  • The dummy's figures after the merge. Verbose copies of sim_dummy and physics_bench_scene print the same figures at 51235f4 as at 629ec49: the hung dummy sleeps after 0.98 s, the walk-in swings its pelvis 0.572 m, it settles again after 9.95 s, it stays within its limits plus 0.027°, and the bench passes a limit by at most 5.503°. At master 5af4834 they read 0.53 s, 1.078 m, 13.73 s and 7.428°.
  • Bakes. Before the merge, the render-depsgraph extract wrote an intermediate byte-identical to 64c323d's, and tools/bake.sh in arms of 64c323d and 629ec49 gave byte-identical baked/dummy.hbm, test_scene.hbs/.hbw and first_map.hbs/.hbw. At the merge head, first_map.hbs/.hbw and test_scene.hbs/.hbw are byte-identical to master 5af4834's, and dummy.hbm to 629ec49's.
  • In the app: at the merge head, run from an empty directory, it opens its window, loads the model from its bundle (it exits 1 when it cannot), and exits 0 on SIGTERM. Before the merge, an offscreen render of the first map showed both skeletons hanging from the frame by their ropes.
  • For Agra: in the app, the dummies are skeletons, and they hang and swing as before.
Tracker: [#3935](https://sak.swipelab.com/tasks/3935) Every training dummy, hung or free, now draws as KayKit's CC0 Skeleton Minion instead of 12 bare capsules. Each ragdoll part draws the share of the skeleton it carries, at that part's pose every frame. The ragdoll's capsules and joints are fitted to the skeleton. ## The asset - **Source.** KayKit "Character Pack: Skeletons" by Kay Lousberg, https://kaylousberg.itch.io/kaykit-skeletons. The page's free file, downloaded on 2026-10-30, is `KayKit_Skeletons_1.1_FREE.zip` (8177445 bytes, SHA-256 21fbad59…d3e2). The prefab comes from its `characters/gltf/Skeleton_Minion.glb`. - **Version and licence.** The page now serves version 1.1, not the 1.0 the task names. The archive's `License.txt` says CC0 1.0. - **Which skeleton.** The Minion is the pack's plainest skeleton: a scarf, a belt and short trousers, with no helmet or hood. No better CC0 skeleton turned up: Quaternius's packs now carry their own licence. - **The import.** `assets/prefabs/kaykit.py` writes `assets/prefabs/skeleton_minion.blend`: the rig and its nine skinned meshes (5288 triangles) with their weights. - It scales the skeleton so the top of its head stands 1.81 m up, the dummy's height, and turns it to face the template's way. - It poses the skeleton as a hanging dummy: upper arms 65° down from the T-pose, feet pointed 60° down. The rest pose stays the T-pose, so the Mobs stream can reuse the rig. - Each bone names the ragdoll part it belongs to in its custom property `hulken_part`. - Materials keep only their Base Color and image, as `kenney.py`'s do. The eyes' glowing material takes its glow's yellow as its Base Color, since hulken draws no glow. - The texture becomes `assets/textures/skeleton_texture.png`, identical texel for texel to the archive's. `kenney.py` gained an `if __name__ == "__main__"` guard so `kaykit.py` can reuse its PNG writer. - It saves the file on the Standard view transform, as the bake requires, so Blender displays the skeleton's colours as hulken draws them. - Rerunning the script reproduces the prefab and the texture exactly. - Each new file has a `.md` beside it with its source, URL, version, file and licence. ## The bake - `tools/bake/dummy.py` renders the prefab with the scene bake's render engine and takes the skeleton as Blender's render draws it, in its saved pose: each mesh as the render depsgraph evaluates it, and the bones of the rig that depsgraph evaluates. Each triangle goes to the part whose bones weigh its corners most, judged by the weights summed over all three corners; a polygon whose material index is past the last slot draws in the last slot, as in Blender. The stage (`stage dummy`) puts each triangle in its part's own space and writes `baked/dummy.hbm`: textures plus 12 parts, in the template's order. - **What the bake refuses:** - more than one rig, or anything the render draws besides the meshes the rig skins, such as a light or an instance of the model; - a mesh of the model the render leaves out, whether hidden itself, through its collection or by its view layer; - a mesh with any modifier besides the one Armature, or whose Armature names another rig or none, is off in the render or deforms it by no vertex group; - a mesh object, the view layer or the scene set otherwise than a new one where that changes the render, such as a mesh the camera does not see, a held-out collection or a compositor with no Render Layers node, and a view layer off for rendering; - a view transform other than Standard or a working space other than Linear Rec.709. A scene's bake refuses the same, and both now call one check, `check_colours` in `extract.py`. Under Linear Rec.2020 the eyes' yellow would bake as another colour while Blender displays it unchanged; - a `hulken_part` that is not a name, or one the template does not have; - a vertex weighted by no bone with a part; - an empty material slot, or a mesh that draws an image with no UVs; - anything a scene's materials are refused for. - **The fit check.** The bake also fails unless the template fits the skeleton, within 1 mm: - every bone a part carries lies inside that part's capsule; - every bone starting from another part's bone starts at the template's joint between those two parts; - every template joint sits at such a bone; - every part carries a triangle; - the skeleton's highest point is the top of the head a hung dummy hangs from. ## The ragdoll - **Shape.** The template keeps its 12 parts, 11 joints, masses (75.5 kg), limits and damping. Its capsules and joint points are refitted to this big-headed, short-legged skeleton: - the head is a 0.37 m capsule whose top is the hang point; - each shin carries its foot; - the shoulders and elbows turn with the upper arm, so an elbow still bends forward; - parts no joint joins start at least 1.6 cm apart, and `sim_dummy` now checks this. - **The hung dummy (`sim_dummy`):** - It sleeps after 0.98 s; master's sleeps after 0.53 s. Pointing the feet down keeps each shin's weight from swinging its knee: with the feet level, it took 7.2 s and failed the 5 s bound. - Walking into it swings its pelvis 0.57 m (master 1.08 m; the bound is 0.2 m). The body now hangs lower against the player's capsule. - It settles again after 9.95 s (master 13.7 s). - It stays within its limits plus 0.03°. - **The bench scene.** One outer-row dummy now leans 18°, down from 25°: at 25° its new head started 4 cm inside its neighbour's. With that change, every car still meets a dummy, every dummy meets another, and no dummy touches the frame. Limits are passed by at most 5.5° (the bound is 20°). - **Bench timing.** Run interleaved under `taskpolicy -b`, five rounds at the merge head 51235f4 and master 5af4834, three of them with the pre-merge head 629ec49 too: worst tick 0.512–0.522 ms at the merge head, 0.517–0.523 ms at 629ec49 and 0.516–0.530 ms at master; median 0.425–0.427 ms at the merge head and 0.425–0.429 ms at 629ec49, against master's 0.378–0.380 ms. Gate run 972's own bench read a worst tick of 0.514 ms and a median of 0.333 ms. The budget is 2 ms. ## Drawing and tests - `src/render/dummies.sx` loads `baked/dummy.hbm` and gives every dummy part its share of the skeleton. No capsule draws. - `tests/dummy_draws.sx`'s Metal golden shows a hung skeleton and a free one fallen beside it. With master's capsule drawing put back, it fails: 13136 of 144000 pixels differ, by up to 213. - `tests/dummy_model.sx` checks each stage rule against a skeleton built from the template. `tests/bake_dummy.py` checks each Blender rule, in one Blender session. - Mutation runs, at 629ec49 (the merge changes none of the code they mutate or the tests that kill them): all 31 mutants of the stage, the Blender extract and the drawing fail a test, and so do all 18 mutants of the extract's render rules, among them taking the model from the viewport's depsgraph, reading the bones or the meshes' places from the unevaluated objects, dropping the render, object, view layer or scene checks, dropping the Armature's vertex-group or render clause, dropping the material-slot clamp, and giving a triangle to the part weighing one corner most. - Mutation runs at 014d8e3, each run against `bake_dummy` and `bake_extract`. All five of the colour and rig rules' mutants fail a test: - the dummy's bake without the colour check (its AgX and Linear Rec.2020 cases); - `skinned()` without its rig clause (its case "a mesh whose Armature names no rig"); - the colour check without its working space, or without its view transform (the matching case in both tests); - the scene bake without the colour check (`bake_extract`'s AgX and Linear Rec.2020 cases). ## Checks - **The merge of master.** 51235f4 merges master 5af4834 (#3934, barrels as Cylinder bodies). The one conflict, `assets/scenes/first_map.blend.md`, keeps master's Props bullet (the barrels are Cylinder bodies) and this branch's Dummies bullet. `first_map.blend` is master's, unchanged. Against 629ec49 the merge changes exactly what master changed against the merge base, file by file. - `sak-gate full`: gate run 961 passed at 5a879eb, gate run 962 at 64c323d (a README-only change) and gate run 970 at 629ec49, 43 tests each, gate run 972 at the merge head 51235f4, 44 tests (with master's `barrels`), and gate run 975 at 014d8e3, 44 tests. - **The prefab on Standard.** `kaykit.py` was rerun at 014d8e3 over a fresh download of the archive, whose SHA-256 matches. The prefab's whole RNA dump, every scene, object, mesh, armature, material, image and collection (114951 lines), differs from 51235f4's only in `view_settings.view_transform`, AgX against Standard. The texture is byte-identical. `tools/bake.sh` at 014d8e3 and at 51235f4 gives byte-identical `dummy.hbm`, `test_scene.hbs`/`.hbw` and `first_map.hbs`/`.hbw`. 51235f4's prefab, saved on AgX, is now refused at `view_settings.view_transform`. - **The dummy's figures after the merge.** Verbose copies of `sim_dummy` and `physics_bench_scene` print the same figures at 51235f4 as at 629ec49: the hung dummy sleeps after 0.98 s, the walk-in swings its pelvis 0.572 m, it settles again after 9.95 s, it stays within its limits plus 0.027°, and the bench passes a limit by at most 5.503°. At master 5af4834 they read 0.53 s, 1.078 m, 13.73 s and 7.428°. - **Bakes.** Before the merge, the render-depsgraph extract wrote an intermediate byte-identical to 64c323d's, and `tools/bake.sh` in arms of 64c323d and 629ec49 gave byte-identical `baked/dummy.hbm`, `test_scene.hbs`/`.hbw` and `first_map.hbs`/`.hbw`. At the merge head, `first_map.hbs`/`.hbw` and `test_scene.hbs`/`.hbw` are byte-identical to master 5af4834's, and `dummy.hbm` to 629ec49's. - **In the app:** at the merge head, run from an empty directory, it opens its window, loads the model from its bundle (it exits 1 when it cannot), and exits 0 on SIGTERM. Before the merge, an offscreen render of the first map showed both skeletons hanging from the frame by their ropes. - **For Agra:** in the app, the dummies are skeletons, and they hang and swing as before.
agra merged commit 2f937ab3b1 into master 2026-10-30 18:31:49 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sx/hulken!26
No description provided.