THE FINDING THAT CHANGES THE PREMISE: "1 rigged model in 4,442" was measured
with the BROKEN GLB probe (the gLUF magic bug). With the probe fixed the
library holds 36 rigged models across three rigs:
41 joints — 9 KayKit heroes + undead, up to 95 clips
7 joints — 22 KENNEY civilians (male/female a-f, orc, human, archer, shop
employees, skaters, soldiers), 32 clips
6 joints — 5 Kenney platformer characters, 25 clips
So a village can be populated with 22 visually distinct civilians TODAY, with
no third-party pack at all. Every conclusion drawn from that probe before it
was fixed needs re-checking, not just this one.
KayKit: 9 characters fetched (Adventurers + Skeletons), pinned by commit +
sha256, 37 MB, gitignored. CC0 verified by READING LICENSE.txt at each pinned
commit, recorded in the script header and CREDITS.toml.
THE SHARED RIG HOLDS ACROSS PACKS, proven rather than assumed: hashing the
joint-name list of all nine files yields the SAME digest — 41 joints, same
names, same order — despite two separate repositories. Skeleton clips are a
strict superset (95 = the adventurers' 76 + 19 undead extras: awaken,
resurrect, spawn, taunt). So a clip authored for the knight plays on the
skeleton warrior and one animation path drives the cast. A test pins this,
including that both packs are present, so a version bump cannot silently break
it.
The texture trap that cost the Kenney fetch three attempts does NOT apply:
KayKit GLBs EMBED their atlas (image/png in a bufferView), verified by parsing
all nine.
tests/rigged.rs parses all 36 rigged models through makepad_game_render::skin
— the loader the app actually runs — and asserts the index's joint and clip
counts match it. Deliberate: the index's own probe was wrong for the entire
library once and survived because the fixture encoded the same error.
find_cast groups by JOINT COUNT rather than pack, because the valuable fact is
cross-pack interchangeability. Cast states are the INTERSECTION, not the union
— advertising a state one member cannot perform is worse than a shorter list.
Added the state words the skeletons needed (spawn/resurrect/taunt/use):
Skeletons_Awaken_Floor previously matched nothing, so "an undead that rises
from the ground" was unfindable.
One bug found in its own work: casts_to_json emitted a doubled closing brace —
malformed JSON that still looked fine in a log. Fixed with a structural test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Third attempt at this, and the first two failed in instructive ways.
A Kenney GLB's material URI is `Textures/colormap.png` RELATIVE TO THE GLB,
and the GLBs land in the pack root. So the atlas has to end up at
`<pack>/Textures/colormap.png` — no more, no less.
- Attempt 1 extracted no PNGs at all ("GLB is self-contained" — false here).
- Attempt 2 flattened every PNG into the pack root, so the file was present
but at a path nothing resolves; indistinguishable from missing, and it also
dragged in ~200 MB of Preview/Sample/thumbnail images nothing loads.
- This one keeps the `Textures/` tail and drops everything above it, because
archives nest it under a per-pack folder (`FBX format/`, etc.) that must not
survive. Preview/Sample images are filtered out.
The `.extracted` marker added earlier is what makes each attempt verifiable
rather than hopeful: a pack extracted by an older, wrong version is refetched
instead of being reported cached.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two bugs, and the second hid the first.
The extraction moved only .glb/.gltf/.bin on the reasoning "GLB is
self-contained" — false for Kenney, whose materials reference an external
Textures/colormap.png shared across the pack. PNGs are extracted now. They are
tiny: 212 atlases, ~42 KB total.
The resume check then counted MODELS only, so a pack whose atlas had never
been extracted was cheerfully reported "already cached" and never refetched —
which is why a full re-run fixed nothing and 48 of 52 packs rendered
untextured. Caching is now gated on a `.extracted` marker written only after a
complete extraction, so a partial or superseded extraction can never pass as
done.
(The PNG fix existed briefly in a concurrent branch of work and was clobbered
by another edit to the same script; the marker is what makes it verifiable
rather than hopeful.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
52 packs, 4442 GLB models, 136 MB on disk — fetched sequentially with resume
(a hash-valid pack is skipped, so an interrupted run costs nothing) via
kenney.nl's content-hashed URLs with per-zip sha256. MIRROR.toml records
every pack's canonical URL, sha256, size and file count, so a mirror is
reproducible; --mirror=/ARCADE_ASSET_MIRROR redirects the base URL and fetch()
verifies the digest identically whatever host served the bytes — a mirror we
control is never trusted more than upstream. --packs= keeps a fresh clone from
being forced to pull everything.
Aliases restructured to survive the scale: per-pack theme rows (55) so every
model in a pack inherits its setting, filename-token parsing with variant-
marker stripping as the workhorse, and ~240 hand-curated query-time synonyms —
the layer whose curation compounds across the whole catalogue. 82-query suite
reports misses instead of being tuned green; the list is down to 2, both
defensible (a floor IS somewhere to stand; a bell IS a metal clang).
Three ranking bugs root-caused, not patched:
- No stemming, so "smashing" never reached the alias "smash" and "glass
smashing" returned glass PIPES. Added a conservative stemmer probed at
synonym strength (only ever adds matches), which refuses to mangle
glass/grass/class and routes "trees" to "tree", not "tre"
- An overreaching alias: `spaceship` sat on four spaceEngine SOUND families.
An engine hum is not a spaceship. Removed; "spaceship engine" still resolves
- Kind confusion on ties: spacecraft models tied with spaceTrash sounds and
lost the alphabetical tie-break. Added kind-aware tie-breaking driven by
query intent — deliberately a TIE-BREAK, not a score bonus, so it cannot
drag a weak model above a strong sound (laser gun / explosion / coins scores
verified unchanged)
Repo-policy violation fixed: all three asset .gitignore files were deny-lists
covering only .glb/.png/.jpg, leaving 302 .gltf files from 3d-road-tiles fully
committable. Converted to allow-lists — 4,744 asset files are now unstageable
by accident.
Scale at 4,999 entries: build 120 ms, search ~0.2 ms, 2.1 MB heap, and the
prompt summary still 479 chars — flat as the catalogue grows, which is what
keeps it affordable in every AI turn.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
75 models (5 KenneyNL starter kits, .glb, pinned commits + sha256, 2.8MB)
and 556 sounds (7 packs via kenney.nl content-hashed URLs, each zip
sha256-verified, 13MB). Nothing large enters git: dirs are gitignored, only
CREDITS.toml and .gitignore files are tracked.
The index is the point — an AI cannot use a library it can't name:
- id is kenney/racing/vehicle-truck-yellow, anchored to where the file
LIVES, not its category, so retuning the category tree never invalidates
a saved game
- Filename tokens are the floor; the value is two hand-curated alias tables
(76 model rows, 116 audio FAMILY rows — Kenney's footstep_wood_000..004
collapse to one family, so 556 files stay maintainable) spanning
synonyms, kid vocabulary and misspellings (vehical, hosue, motercycle),
function over identity ("something to hide behind"), colour/size/
material, and theme, plus ~190 query-time synonym expansions
- AssetKind model/sound/music so a 30-second track can't be returned as a
hit sound; GLB probe reads skins -> rigged, animations -> animated
- FIND_MODEL tool descriptor (provider-neutral plain data) + compact
results; library_summary() is 469 chars for 632 entries and provably
doesn't grow with the catalogue; resolve_or_explain() rejects
hallucinated ids with near-misses; local_spawn() gives the local
librarian a best match plus a confidence blending strength with margin
HONEST GAP: every Kenney audio pack is Ogg Vorbis only — no WAV exists
upstream — and this tree has no vorbis decoder. Sounds are indexed and
searchable but NOT playable: entries carry decodable:false, the agent JSON
emits playable:false so a game cannot fire a silent sound, and
--transcode converts via ffmpeg when present. A real decoder is the fix.
Three bugs found by testing, all fixed: sci-fi-sounds.zip ships a directory
with no owner-write bit so that pack alone silently extracted 0 of 73 files;
prepositions matched phrase aliases ("...at the roadworks" hit "something to
shoot at"), so function words must be dropped, not down-weighted; and exact
names lost to incidental aliases (a coin SOUND outranked the coin MODEL).
Miss list left visible at 2/52 rather than tuned away — both are defensible
answers against over-narrow expectations.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- libs/game/render/skin.rs: GLB container + owned-JSON parser, dense
accessors, multi-primitive skinned meshes, skin + inverse binds, full
node-hierarchy palette, T/R/S clips (linear/step), nlerp blending.
Hermetic tests via an in-code 2-joint GLB; real-asset test skips with a
hint when the download hasn't run
- DrawGameSkinned shader (PbrVertex + albedo texture, terrain-style
lighting); skinned batch draws between opaque and alpha passes.
CPU skinning for now (sub-ms at 3716 verts): the uniform_buffer GPU path
has zero in-tree runtime consumers — SkinnedModel::palette is the seam
for the GPU swap
- apps/arcade: download_assets.sh (KayKit pinned commit + sha256,
idempotent, dir gitignored, CC0 license note in-repo); Knight patrols
the demo world with idle/walk blending, yaw follows path; captures
verified (pose advances, depth-occludes correctly)
- renderer binary_search sites use the shared sorted-id helper from M0r
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>