# 3d Game Assets

> Use when generating or integrating GLB assets for a browser game — orientation audits, manifests, Three.js mixers, visual consistency.

- Skill: `yogvidwankhede/3d-game-assets` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add yogvidwankhede/3d-game-assets`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yogvidwankhede/3d-game-assets/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: yogvidwankhede (https://skillmd.com/u/yogvidwankhede)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/yogvidwankhede/3d-game-assets

---


<!--
  Generated by Vishwakarma. Do not edit this file directly.
  Edit the source skill and run `vishwakarma sync` to regenerate.
-->

# 3D Game Assets

Design intelligence for AI-generated GLB assets in browser games. The goal is a set of
game-ready `.glb` models that read as a coherent visual system rather than a collection of
unrelated generated objects: orientation verified, visual language consistent, material palette
shared, and animation integrated through `@vishwakarma/three`.

Generation runs against a remote asset generation service, so no local GPU is required. **This
skill is free for non-commercial use. If you use the remote asset generation service in a
commercial project, contact the service provider for written permission before shipping.** That
is a third-party service term and is separate from the Apache-2.0 licence on this skill; check it
before a commercial release, not after.

**Scope.** This skill owns the asset itself — generating it, verifying its orientation, keeping a
set visually coherent, and wiring its clips through a Three.js mixer. The engine systems that
*consume* assets — animation state machines and blend trees, the rendering pipeline and its frame
cost, collision shapes and the physics solver, skinning and animation LOD — belong to
`vishwakarma-studios`. If the question is about the file, it is this skill; if it is about the
system playing the file, it is that one.

An asset set passes the contract when all five sections below are satisfied. Orientation failures
are errors; visual-language violations are warnings.

---

## 1. Orientation verification (error if absent)

Every direction-sensitive asset — player, NPC, enemy, vehicle — must have its native forward axis
independently verified before integration, recorded in `asset_manifest.json` under
`asset.orientation` with `nativeForwardAxis`, `calibrationYawDegrees`, `auditMethod` and
`contentHash`.

**Mechanism.** Generated models have no universal forward-axis convention. A character that
visually faces +Z while the game code expects −Z will move sideways or backwards under standard
movement code. The defect is invisible in a static preview and immediate in motion, so
calibration has to be a separate, evidence-carrying step rather than an assumption.

The states, in ascending order of confidence: `UNVERIFIED`, not yet checked; `AXIS_AUDITED`,
geometry and bones inspected and the forward axis recorded; `MATH_VERIFIED`, movement equations
checked against that axis; `VISUALLY_VERIFIED`, rendered motion confirmed against the velocity
vector; `ACCEPTED`, all three methods agree and the asset is safe to ship.

Do not advance an asset past `AXIS_AUDITED` on mathematical reasoning alone. A character can be
entirely self-consistent with the wrong axis — the equations agree with each other and disagree
with the mesh. Visual verification against actual movement is the step that catches it.

## 2. Visual language consistency (warning if violated)

All assets in a game share one visual grammar: the same polygon-density tier, the same material
complexity — all PBR or all flat-shaded, not mixed — and one silhouette readability distance.

**Mechanism.** Players compare assets against each other, not against an external standard. An
8,000-polygon PBR enemy next to a 500-polygon flat-shaded collectible breaks the fictional reality
of the world. Viewers accept low-fidelity worlds readily; they reject internally inconsistent ones.

| Tier | Poly budget (character) | Poly budget (prop) | Material |
|---|---|---|---|
| Minimal | 500–1 500 | 100–500 | Flat/vertex colour |
| Standard | 2 000–5 000 | 500–1 500 | PBR, 1 texture |
| Detailed | 5 000–12 000 | 1 500–4 000 | PBR, 2+ textures |

Declare the tier once in `asset_manifest.json` under `visualLanguage.tier`, and include the
tier label in every generation prompt.

## 3. Material palette (warning if violated)

Surface colours come from a shared OKLCh palette in `asset_manifest.json` under
`visualLanguage.palette`. Vary chroma by category — player high, enemies mid, environment low —
so colour carries hierarchy. Raw hex literals in generation prompts are discouraged; describe by
hue and role instead.

**Mechanism.** Generation services interpret colour descriptions contextually. "Deep teal with
high chroma armour" produces more consistent results across calls than `#0f766e`, because the
model reasons perceptually rather than colour-matching a value. OKLCh anchors keep every asset in
one perceptual gamut without forcing identical colours.

    Player:       L 55, C 70, H [primary brand hue]
    Enemies:      L 45, C 50, H [complementary hue ±150°]
    Environment:  L 35, C 20, H [analogous hue ±30°]
    Collectibles: L 80, C 80, H [accent hue ±90°]   // high L = immediate readability

## 4. Silhouette readability (warning if violated)

Each character's silhouette must be distinguishable from every other character class at the
minimum legible display size, typically 64 × 64 px of screen area. Test it by rendering each asset
as a filled silhouette and comparing them side by side.

**Mechanism.** In fast gameplay players read silhouette before colour and before detail. Two
enemies with similar proportions and different surface treatments are indistinguishable at speed,
so shape differentiation — tall, wide, compact; spiky, smooth, boxy — carries more functional
information than material variation.

## 5. Animation continuity (warning if violated)

Character animation states share a single `THREE.AnimationMixer` on the root object. Clips
cross-fade rather than cut. Idle-to-walk completes within 200 ms. Death and exit animations run to
completion before the asset leaves the scene.

**Mechanism.** Separate mixers per clip write simultaneous conflicting pose data to the same
skeleton. Cross-fading preserves skeletal continuity; a cut produces a single-frame pose snap that
players see even at 60 fps.

---

Generation route selection, prompt structure, and the orientation audit procedure are in the
generation-workflow reference. The canonical manifest shape, the Three.js and React Three Fiber
integration, fallback primitives, and Motion Grammar timings are in the integration reference.

## Rules

### MUST NOT — Do not advance an asset past AXIS_AUDITED on mathematical reasoning alone, and re-run the audit whenever contentHash no longer matches the GLB.

*Why:* Self-consistency is a property of the movement equations, not of the mesh: a character can satisfy every equation while built on the wrong axis, because the equations agree with each other rather than with the geometry. The hash exists so that verification attaches to a specific file — a regeneration produces a new GLB that may differ in orientation from the audited one.

Incorrect:

```javascript
// forward is -Z everywhere in the code, so the asset must be -Z
asset.orientation.state = 'ACCEPTED'
```

Correct:

```javascript
// drove the asset along +X, -X, +Z, -Z in the turntable viewer and watched it
asset.orientation.state = 'VISUALLY_VERIFIED'
asset.orientation.contentHash = sha256(glbBytes)
```

### MUST — Verify every direction-sensitive asset’s forward axis by rendered motion before accepting it, recording nativeForwardAxis, calibrationYawDegrees, auditMethod, contentHash and a state of VISUALLY_VERIFIED or ACCEPTED in the manifest.

*Why:* Generated meshes follow no universal forward-axis convention, so a model that visually faces +Z while the code assumes −Z moves sideways or backwards under standard movement. The defect is invisible in a static preview and immediate in motion, which is why the audit has to be a separate evidence-carrying step rather than an inference from the file.

Incorrect:

```json
"orientation": { "nativeForwardAxis": "+Z", "state": "MATH_VERIFIED" }
```

Correct:

```json
"orientation": {
  "nativeForwardAxis": "+Z",
  "calibrationYawDegrees": 0,
  "auditMethod": "turntable-visual + movement-test",
  "contentHash": "sha256:...",
  "state": "VISUALLY_VERIFIED"
}
```

### MUST — Apply calibrationYawDegrees as a single Y-axis rotation on the visual child mesh, never on the physics or collision root.

*Why:* Rotating the collision root rotates the simulation with it: the capsule or box turns, the contact normals turn, and the character now collides with the world at an angle that no longer matches what is drawn. Rotating only the visual child leaves the physics in the axis the engine expects and corrects appearance alone.

Incorrect:

```javascript
character.rotation.y = THREE.MathUtils.degToRad(yaw)
```

Correct:

```javascript
const visual = character.getObjectByName('visual') ?? character
visual.rotation.y = THREE.MathUtils.degToRad(yaw)
```

### MUST — Resolve every model URL, clip URL, and orientation value from asset_manifest.json at runtime; do not hardcode asset paths in game logic.

*Why:* The manifest is where the calibration yaw and the verification state live alongside the URL, so code that hardcodes a path silently drops both and integrates an unverified asset. It is also what makes regeneration a data change rather than a code change, since a regenerated asset gets a new URL and a new hash.

Incorrect:

```javascript
const gltf = await loader.loadAsync('/assets/player_v3_final.glb')
```

Correct:

```javascript
const gltf = await loader.loadAsync(manifest.actions['player-character'].model.url)
```

### MUST — Construct exactly one THREE.AnimationMixer per character, on the character root rather than on the calibrated visual child.

*Why:* Two mixers driving one skeleton write conflicting pose data on the same frame and the winner is whichever ran last, which presents as jitter rather than as an obvious error. Rooting the mixer on the calibrated child is the subtler failure: clip tracks resolve against a subtree carrying an extra rotation, so every bone track arrives rotated.

Incorrect:

```javascript
const idleMixer = new THREE.AnimationMixer(character)
const walkMixer = new THREE.AnimationMixer(character)
```

Correct:

```javascript
const mixer = new THREE.AnimationMixer(character)   // root, one per character
const idle = mixer.clipAction(idleClip)
const walk = mixer.clipAction(walkClip)
```

### MUST — Blend between animation clips with a cross-fade of roughly 200 ms, and let idle-to-walk and death or exit clips complete rather than cutting them.

*Why:* A cut swaps skeletal poses between consecutive frames, which is a discontinuity visible even at 60 fps because it violates the velocity continuity every other frame of the animation has established. Cross-fading interpolates the two poses through the interval, so the skeleton never teleports and the transition reads as movement rather than as a glitch.

Incorrect:

```javascript
idle.stop()
walk.play()
```

Correct:

```javascript
walk.reset().play()
idle.crossFadeTo(walk, 0.2, false)
```

### MUST — Confirm the remote asset generation service’s terms before shipping commercially: it is free for non-commercial use, and commercial use requires prior written permission from the service provider.

*Why:* This is a third-party service term and is separate from the Apache-2.0 licence on the skill itself, so a permissive skill licence gives no permission over the generated assets. The obligation attaches to the assets already embedded in the build, which makes it far cheaper to resolve before release than after — and generated likenesses of copyrighted characters, brands, or celebrities are a shipping blocker on the same axis.

Incorrect:

```text
The skill is Apache-2.0, so the generated assets can ship in the paid version.
```

Correct:

```text
Non-commercial prototype: proceed. Paid release: obtain written permission from the service provider before shipping, and confirm no prompt produced a copyrighted or celebrity likeness.
```

### SHOULD — Define a fallback primitive for every GLB that shares the asset’s OKLCh palette colour and approximate silhouette, and render it while the model is missing or retrying.

*Why:* Remote GLB fetches fail in low-connectivity environments, and an entity with no mesh still has full collision — so the player collides with something invisible and reads it as a physics bug rather than as a missing asset. Matching the palette colour and rough silhouette makes the stand-in legible as the entity it replaces instead of an unexplained white box.

Incorrect:

```javascript
const model = await load(url)   // entity spawns with collision and no mesh on failure
```

Correct:

```javascript
const fallback = new THREE.Mesh(
  new THREE.CapsuleGeometry(0.4, 1.2),
  new THREE.MeshStandardMaterial({ color: oklchToHex(palette.player) })
)
```

### SHOULD — Declare one visual language tier and one OKLCh palette in the manifest, generate every asset within that tier’s poly and material band, and describe colour by hue and role rather than by hex in prompts.

*Why:* Players compare assets against each other rather than against an external standard, so an 8,000-polygon PBR enemy beside a 500-polygon flat-shaded pickup reads as two different games — low fidelity is accepted, internal inconsistency is not. Described colour also survives across generation calls better than a hex literal, because the service reasons perceptually instead of colour-matching a value.

*Exceptions:*
- A deliberate diegetic contrast — for example a single artefact meant to read as alien to the world — where the break is the point and is applied once rather than accumulating.

Incorrect:

```text
Enemy drone, highly detailed, PBR metal, #0f766e hull
```

Correct:

```text
Enemy scout drone. Compact disc shape, wide and flat.
Standard polygon budget, single PBR texture.
Dark teal metallic hull, high-chroma red sensor ring.
Approximately 2000 polygons. No real-world brand references.
```

## Before reporting completion

Run these checks against your own output. Answer each question explicitly rather than
assuming the answer, because the point of the exercise is to notice what you did not
notice while building.

### Confirm every direction-sensitive asset has an evidence-carrying forward-axis verification and a correctly placed calibration. (blocking)

- For each direction-sensitive asset, was it driven along +X, −X, +Z and −Z in the turntable viewer and watched, or is the recorded state resting on mathematical reasoning alone?
- Is every such asset’s orientation.state VISUALLY_VERIFIED or ACCEPTED, with nativeForwardAxis, calibrationYawDegrees, auditMethod and contentHash all present?
- Does contentHash match the current GLB bytes, and was the audit re-run after every regeneration?
- Is calibrationYawDegrees applied as a single Y-axis rotation on the visual child rather than on the physics or collision root?
- Does each character move in the direction it faces when driven by the game’s own velocity vector, not just in the audit harness?

### Confirm the asset set reads as one system: consistent tier, shared palette, distinguishable silhouettes, defined fallbacks. (blocking)

- Was asset_manifest.json created before the first generation call with visualLanguage.tier and palette declared, and did every character or creature use the Gemini + Tripo route rather than Tripo alone?
- Does every asset sit inside the declared tier’s polygon budget and material complexity, with no mix of PBR and flat-shaded within one set?
- Rendered as filled silhouettes at 64 × 64 px, is every character class distinguishable from every other?
- Are player, enemy, environment and collectible colours drawn from the manifest OKLCh palette, and do they pass 3:1 contrast against the game background?
- Does every GLB have a fallback primitive sharing its palette colour and approximate silhouette, so a failed fetch never leaves an invisible entity with collision?

### Confirm runtime resolution, animation wiring, motion timings, and licensing obligations before shipping.

- Does any game logic hardcode a GLB URL, or is every model, clip, and orientation value resolved from manifest.actions[slot]?
- Is there exactly one AnimationMixer per character, constructed on the root, and is each clip GLB contributing animations[0] only rather than having its scene added to the game?
- Do all clip transitions cross-fade over roughly 200 ms, with idle-to-walk completing within 200 ms and death or exit clips running to completion before removal?
- Do spawn, removal, damage, pickup, attention, and loading transitions use the Motion Grammar intents and durations, each with a prefers-reduced-motion branch that collapses to opacity?
- Did any generation prompt risk a copyrighted character, brand logo, or celebrity likeness, and is the generation service URL confirmed with no credentials stored in code?
- If this is heading to a commercial release, has written permission for the remote asset generation service been obtained from the service provider before shipping?

## Further reference

These are not loaded by default. Read one only when its question is the question you
currently have.

- `references/asset-generation-workflow.md` — How do I set up an asset manifest before generating, which generation route should a character versus a prop take, what does a good generation prompt contain, and exactly how do I audit and record an asset’s forward axis?
- `references/manifest-structure-and-threejs-integration.md` — What is the canonical shape of asset_manifest.json, how do I load a character and its clips into Three.js or React Three Fiber with one mixer and correct calibration, what should render when a GLB fails to load, and which Motion Grammar timings apply to asset state changes?

