arabold

assets

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.

arabold 4 Updated 2mo ago

Resources

1
GitHub

Install

npx skillscat add arabold/rogue-gauntlet/assets

Install via the SkillsCat registry.

SKILL.md

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 the
ATTRIBUTIONS.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 +a

Never 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-bits

Pulls the gltf set (.gltf+.bin+shared texture) + LICENSE, flattened to match existing
assets/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 printed
attribution_text MUST go in the manifest and the shipped credits.

Add-an-asset workflow

  1. Discover / fetch with the right channel above.
  2. Register in assets/ATTRIBUTIONS.json — the Poly Pizza get prints a near-complete
    entry; for KayKit, copy an existing kaykit-* entry and adjust. Set the right
    compatibility_group (kaykit for KayKit; for Poly Pizza models triage out of
    polypizza-unsorted once you've judged style fit).
  3. Import into Godot (generates import caches + .uid sidecars), from the repo root:
    .agents/skills/godot-mcp/scripts/godot.sh --headless --path "$PWD" --import
  4. Verify: /usr/local/share/dotnet/dotnet build "Rogue Gauntlet.sln", and validate a
    sample model loads (.agents/skills/godot-mcp/scripts/inspect_resource.gd).
  5. 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:

  1. 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-atlas EnemyArmature series (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.
  2. Probe the model's animations. Load the GLB and list its AnimationPlayer clips — names vary
    per model and rig: humanoids use Idle/Walk + Punch|Bite_Front + HitReact|HitRecieve +
    Death; the bat flies (Bat_Flying for idle+move, Bat_Hit); the spider has no hit clip (reuse
    Spider_Idle for the Hit state). The state machine must reference the exact strings. Flyers
    (bat/ghost) get a model transform Y-offset to hover; true flight needs a movement/behavior change.
  3. 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.
  4. Normalize scale — by eye, not by measurement. Downloaded characters arrive at arbitrary
    sizes. Set a uniform transform scale on the model node under Pivot, 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.
  5. Match the collision shapes to the reference enemy. Use the same capsule sizing the skeleton
    variants use — radius 0.75, height 2.0, body CollisionShape3D at y = 1.2, plus the
    hurtbox CollisionShape3D. Don't shrink them to a mis-scaled model; size the model to them.
  6. Author the scene (scenes/enemies/<creature>/<name>.tscn, one folder per creature family like
    skeleton/ — not per author): instance enemy_base.tscn; add the
    model under Pivot; add an AnimationTree under EnemyBehaviorComponent with root_node
    the model, anim_player → the model's AnimationPlayer, advance_expression_base_node..,
    and a tree_root state machine (Idle/Walking/Attack/Hit/Dying/Dead) whose transitions key off
    the behavior flags IsMoving/IsAttacking/IsHit/IsDead. Add the two CollisionShape3Ds
    (hurtbox child + body). Set HealthComponent, DeathComponent.Xp, HurtBoxComponent.Evasion,
    LootTableComponent.Table, and EnemyBehaviorComponent.Profile.
  7. Author a profile co-located in the creature folder (scenes/enemies/<creature>/<name>_behavior.tres,
    script EnemyBehaviorProfile) — each creature folder is self-contained (scene + profile). If the
    model has no spawn clip, set InitialAction = 0 (Idle), not Spawning.
  8. Register the scene path in scenes/levels/dungeon/dungeon_mob_factory.tres.
  9. 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 boot
    res://scenes/main/main.tscn and 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-monsters

scripts/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 build
docs/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.gd and let it scan (DirAccess); don't word-split a file list
    on the shell — zsh doesn't split unquoted $VARS like bash, so only one path reaches the
    script. render_item_catalog.gd takes explicit item paths instead (items live scattered
    across scenes/items/<category>/, not one flat asset folder).
  • After regenerating, update the table in docs/monster-catalog/README.md or
    docs/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:

  1. Wrong local-axis convention. BoneAttachmentManager parents the item's Scene directly
    under a BoneAttachment3D with 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."
  2. 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
    .glb coming 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 CharacterBody3D and 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 a StaticBody3D floor and
    waits SETTLE_FRAMES before 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 .glb alone 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_degrees on the instanced child); see the
    godot-mcp skill's tres-authoring.md for the pattern.
  • IsTwoHanded on a Weapon changes which BoneAttachment3D it resolves to (see
    docs/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 256

Gotchas

  • Binary repo files may be Git-LFS — fetch via raw.githubusercontent.com (the scripts do);
    it resolves LFS transparently. ?download=true resolves 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=2 VRAM/BC + mipmaps) wrecks these — BC's 4×4 blocks straddle palette
    cells and mipmaps blend neighbours, so the "skin" comes out muddy/wrong. Set compress/mode=0
    and mipmaps/generate=false on the atlas .import and 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-mcp skill.