Use when discovering, downloading, or adding new 3D/2D/audio assets to this game from curated free sources (KayKit, Quaternius, Kenney, Poly Pizza) or generating our own via image-to-3D. Covers the two automation channels (KayKit GitHub + Poly Pizza API), the download->manifest->import workflow, licensing/style rules, and the experimental AI authoring path. Policy and the asset manifest live in assets/AGENTS.md; this skill is the how-to and the scripts. Not needed for editing assets already in the repo.
Resources
1Install
npx skillscat add arabold/rogue-gauntlet/assets Install via the SkillsCat registry.
Asset acquisition for Rogue Gauntlet
Find, fetch, and register third-party (or self-authored) assets while staying license-clean
and visually cohesive. assets/AGENTS.md is the policy + manifest source of truth
(the open-source free-download rule, the KayKit cohesion rule, the licensing rules, and theATTRIBUTIONS.json schema). This skill is the mechanics: which script, what command, and how
the pieces fit. Read assets/AGENTS.md before adding anything.
Secrets
Keys live in the repo .env (gitignored): POLYPIZZA_KEY, HF_TOKEN. Load them per shell:
set -a; . /Users/andrerabold/Projects/Personal/rogue-gauntlet/.env; set +aNever print key values or pass them on a command line that gets logged.
The two automation channels
1. KayKit -> GitHub (no key) — cohesive whole packs
Our primary 3D style. Public repos under github.com/KayKit-Game-Assets, all CC0.
python3 scripts/fetch_kaykit.py KayKit-Game-Assets/KayKit-Halloween-Bits-1.0 assets/kaykit-halloween-bits --dry-run
python3 scripts/fetch_kaykit.py KayKit-Game-Assets/KayKit-Halloween-Bits-1.0 assets/kaykit-halloween-bitsPulls the gltf set (.gltf+.bin+shared texture) + LICENSE, flattened to match existingassets/kaykit-* dirs. Stdlib only. (GH_TOKEN optional, only to lift the unauth rate limit.)
2. Poly Pizza -> API (POLYPIZZA_KEY) — discovery + single models
Indexes Quaternius, Kenney, and many others. Mixed CC0 / CC-BY.
# discover: see tris (low-poly?), licence, and Animated before committing
python3 scripts/fetch_polypizza.py search goblin --limit 25 --cc0 --max-tris 3000
# download one + get a ready-to-paste manifest entry (CC-BY attribution captured)
python3 scripts/fetch_polypizza.py get <ID> assets/<id>Prefer --cc0 to avoid attribution debt; if you take a CC-BY model, the printedattribution_text MUST go in the manifest and the shipped credits.
Add-an-asset workflow
- Discover / fetch with the right channel above.
- Register in
assets/ATTRIBUTIONS.json— the Poly Pizzagetprints a near-complete
entry; for KayKit, copy an existingkaykit-*entry and adjust. Set the rightcompatibility_group(kaykitfor KayKit; for Poly Pizza models triage out ofpolypizza-unsortedonce you've judged style fit). - Import into Godot (generates import caches +
.uidsidecars), from the repo root:.agents/skills/godot-mcp/scripts/godot.sh --headless --path "$PWD" --import - Verify:
/usr/local/share/dotnet/dotnet build "Rogue Gauntlet.sln", and validate a
sample model loads (.agents/skills/godot-mcp/scripts/inspect_resource.gd). - New creator/style only: do the in-engine visual side-by-side (see
assets/AGENTS.md).
Turning a downloaded character into an enemy (recipe)
Validated on the Quaternius monsters. Enemies instance scenes/enemies/enemy_base.tscn and
override the model + an AnimationTree state machine + tuning resources (see the skeleton
variants in scenes/enemies/skeleton/ as the reference). Steps:
- Pick a coherent set, not just same-creator. One creator's "monster pack" often spans
several visual sub-series — e.g. Quaternius has a textured-atlasEnemyArmatureseries (goblin,
zombie) AND a vertex-colored series (orc, demon, ghost, yeti, slime, bat, spider) that do not
match. Probe candidates and group by look (textured vs vertex-color via the material check) before
committing; mixing sub-series reads as broken even though it's "all Quaternius". Within one
coherent set, rigs still differ per body type (CharacterArmature/MonsterArmature/BatArmature/SpiderArmature) — that's fine, just map clips per model. - Probe the model's animations. Load the GLB and list its
AnimationPlayerclips — names vary
per model and rig: humanoids useIdle/Walk+Punch|Bite_Front+HitReact|HitRecieve+Death; the bat flies (Bat_Flyingfor idle+move,Bat_Hit); the spider has no hit clip (reuseSpider_Idlefor the Hit state). The state machine must reference the exact strings. Flyers
(bat/ghost) get a modeltransformY-offset to hover; true flight needs a movement/behavior change. - Fix looping in the
.glb.import. Fresh imports have_subresources={}, so Idle/Walk/Run
play once and freeze. Add"animations": { "<clip>": { "settings/loop_mode": 1 } }for the
looping clips (leave Attack/Hit/Death at 0 so their at-end transitions fire), then reimport. - Normalize scale — by eye, not by measurement. Downloaded characters arrive at arbitrary
sizes. Set a uniformtransformscale on the model node underPivot, scaling about the origin
so the feet stay on the ground and the pivot is intact. Confirm the size visually in the
editor against an existing actor — headless height measurement of GPU-skinned GLBs is
unreliable (bind-pose AABBs and bone spans both mislead and disagree with each other), so the
editor eyeball is authoritative. As a starting point, Quaternius monsters sit at ≈ 0.75 to
read right next to the KayKit skeleton; expect to nudge per model. - Match the collision shapes to the reference enemy. Use the same capsule sizing the skeleton
variants use —radius 0.75,height 2.0, bodyCollisionShape3Daty = 1.2, plus the
hurtboxCollisionShape3D. Don't shrink them to a mis-scaled model; size the model to them. - Author the scene (
scenes/enemies/<creature>/<name>.tscn, one folder per creature family likeskeleton/— not per author): instanceenemy_base.tscn; add the
model underPivot; add anAnimationTreeunderEnemyBehaviorComponentwithroot_node→
the model,anim_player→ the model'sAnimationPlayer,advance_expression_base_node→..,
and atree_rootstate machine (Idle/Walking/Attack/Hit/Dying/Dead) whose transitions key off
the behavior flagsIsMoving/IsAttacking/IsHit/IsDead. Add the twoCollisionShape3Ds
(hurtbox child + body). SetHealthComponent,DeathComponent.Xp,HurtBoxComponent.Evasion,LootTableComponent.Table, andEnemyBehaviorComponent.Profile. - Author a profile co-located in the creature folder (
scenes/enemies/<creature>/<name>_behavior.tres,
scriptEnemyBehaviorProfile) — each creature folder is self-contained (scene + profile). If the
model has no spawn clip, setInitialAction = 0(Idle), not Spawning. - Register the scene path in
scenes/levels/dungeon/dungeon_mob_factory.tres. - Verify: reimport,
dotnet build, then a headless probe that instantiates the scene and
asserts the AnimationTree resolves and every state maps to a clip the player has; finally bootres://scenes/main/main.tscnand confirm the monster spawns. The cohesion/look call is the
maintainer's — eyeball it next to existing actors.
Visual catalog (model screenshots)
scripts/render_catalog.gd renders a transparent-background PNG of every .glb/.gltf in a
directory (posed in Idle, 3/4 camera) — used to build docs/monster-catalog/.
.agents/skills/godot-mcp/scripts/godot.sh --path "$PWD" \
--script .agents/skills/assets/scripts/render_catalog.gd -- docs/monster-catalog res://assets/quaternius-monstersscripts/render_item_catalog.gd does the equivalent for items: loads each item .tres,
instances its Scene, auto-centers/auto-rotates it (mirroring Preview.cs's in-game inventory
preview framing) and screenshots it alone against a transparent background — used to builddocs/item-catalog/.
.agents/skills/godot-mcp/scripts/godot.sh --path "$PWD" \
--script .agents/skills/assets/scripts/render_item_catalog.gd -- docs/item-catalog res://scenes/items/weapons/axe_common.tres [more...]- Run WINDOWED — not
--headless. Headless uses the dummy renderer: viewport captures come
back blank and the script hangs. A real window opens briefly and renders on the GPU. (timeout
isn't on macOS — rely on the runner's own time cap if you need a guard.) - Pass the dir to
render_catalog.gdand let it scan (DirAccess); don't word-split a file list
on the shell — zsh doesn't split unquoted$VARSlike bash, so only one path reaches the
script.render_item_catalog.gdtakes explicit item paths instead (items live scattered
acrossscenes/items/<category>/, not one flat asset folder). - After regenerating, update the table in
docs/monster-catalog/README.mdordocs/item-catalog/README.md.
In-hand weapon/shield verification (does NOT catch everything the catalog does)
The floating catalog render above is not sufficient to validate a new weapon or shield.
It auto-frames and auto-rotates for a nice thumbnail, which can hide exactly the two defects
that only show up once a model is actually bone-attached to the player:
- Wrong local-axis convention.
BoneAttachmentManagerparents the item'sScenedirectly
under aBoneAttachment3Dwith zero corrective transform, so the model's own local axes must
already match what that bone's rest pose expects. Different weapon-hand bones expect
different conventions (a melee bone expects "extends away from grip" along local +Y; a
raw bow/crossbow-style asset instead puts its aim along local Z with limbs along X) — get
it wrong and the model can end up thin-edge-on to every normal camera angle, reading as fully
invisible, not just "rotated a bit wrong." - Cross-tier scale inconsistency. A raw downloaded model isn't guaranteed to be scaled
consistently with an existing baked-mesh sibling in the same item ladder (e.g. a new tier-2.glbcoming in at half the height of the tier-1 item it's supposed to visually escalate from).
scripts/render_held_items.gd equips a sequence of items on a standalone player (no
GameSession/class active — the authored Barbarian rig) and screenshots the character from three
angles per item (front, weapon-hand 3/4, shield-hand 3/4), so whichever hand an item lands in is
covered without needing to guess the right camera angle up front:
.agents/skills/godot-mcp/scripts/godot.sh --path "$PWD" \
--script .agents/skills/assets/scripts/render_held_items.gd -- /tmp/held_test \
res://scenes/items/weapons/axe_rare.tres res://scenes/items/armor/shield_rare.tres [more...]- Windowed only, same reason as above.
- Requires a floor: the player is a physics-driven
CharacterBody3Dand free-falls under gravity
in an otherwise-empty scene (for however many physics ticks elapse before the first idle frame
— decoupled from frame count and inflated by first-load shader/asset stalls), landing at an
unpredictable height by the time you'd screenshot. The script adds aStaticBody3Dfloor and
waitsSETTLE_FRAMESbefore measuring/framing; don't drop this if you fork the script. - If an item is invisible in ALL three angles (not just badly positioned in one), suspect the
local-axis convention, not scale/position. Diagnose by rendering the raw.glbalone at
identity transform with RGB axis gizmos (R=+X, G=+Y, B=-Z), then compare against a sibling
weapon on the same bone that's known to render correctly rendered the same way — the
direction its long axis points tells you which local axis needs to land on which. Fix with a
thin corrective wrapper.tscn(rotation_degreeson the instanced child); see thegodot-mcpskill'stres-authoring.mdfor the pattern. IsTwoHandedon aWeaponchanges whichBoneAttachment3Dit resolves to (seedocs/character-classes.md), so re-verify in-hand appearance after changing it — a weapon
that looked right on one bone isn't guaranteed to look right on another.
Authoring our own (image-to-3D) — experimental
scripts/hf_image_to_3d.py runs image-to-3D on HF hardware (TripoSR Space) via gradio_client.
Honest scope before you invest:
- Output is high-poly PBR + unrigged — needs a manual Blender low-poly/palette pass and
skinning to the KayKit rig. HF solves compute, not the style/rig gap. - The GPU step needs ZeroGPU quota; a free token usually hits the wall (validated). Reliable
routes: HF Pro, or duplicate the Space to a dedicated GPU. - Needs
gradio_client:python3 -m venv .venv && .venv/bin/pip install gradio_client.
.venv/bin/python scripts/hf_image_to_3d.py concept.png monster.glb 256Gotchas
- Binary repo files may be Git-LFS — fetch via
raw.githubusercontent.com(the scripts do);
it resolves LFS transparently.?download=trueresolves HF LFS similarly. - Triangle count is the quick low-poly filter on Poly Pizza; KayKit-grade props are roughly
hundreds-to-low-thousands of tris. A 70k-tri "skeleton" will not match. - CC-BY is not free of obligations — attribution is legally required in shipped builds.
Prefer CC0; record attribution when you don't. - Tiny palette-atlas textures need lossless + no mipmaps. Many low-poly models (Quaternius is
32×32) color faces by sampling flat cells from a minuscule atlas. Godot's default 3D texture
import (compress/mode=2VRAM/BC + mipmaps) wrecks these — BC's 4×4 blocks straddle palette
cells and mipmaps blend neighbours, so the "skin" comes out muddy/wrong. Setcompress/mode=0
andmipmaps/generate=falseon the atlas.importand reimport. (Large UV textures like
KayKit's 1024² are fine with the default.) - Don't commit the scratch venv or downloaded archives. Keep
.venv/and any zips out of git. - For Godot import/scene/resource specifics, defer to the
godot-mcpskill.