Grudge Dev Tool (v1.0.10+) production import path. Play bodies: Toon RTS GLB via loadRaceKit / gltfProdLoader (r185 Draco+Meshopt+KTX2). CDN MIME: GLB model/gltf-binary, FBX application/octet-stream.
| Symptom | Cause | Fix in this tool |
|---|---|---|
| Yellow / sludge kit | FBX default color, missing atlas | Convert packs textures; sanitizeMaterials replaces default yellow; re-bake with atlas |
| Pure black silhouette | metalness=1 + no IBL, or missing map |
Cap metalness, brighter ambient in viewers, strip 1×1 maps |
| Scrambled colors | Wrong colorSpace / flipY |
Base color → sRGB; data maps → NoColorSpace; GLB/FBX flipY=false |
| Black cube / crash | HTML error page served as .glb |
Magic-byte gate (glTF header); refuse <!DOCTYPE + Content-Type text/html |
unknown magic [7b a 20 20] |
JSON starts with {\n but not glTF, or old probe missed late "asset" |
Scan 64–256 KiB for glTF markers; reject JSON error stubs; open multi-file scene.gltf with diskPath |
| Random / wrong PNGs | Sibling fill overwrote embeds or bare folder maps | onlyMissingMaps + role-tag / high affinity score; never overwrite usable maps |
Missing colors on scene.gltf |
Blob load lost relative .bin/textures |
Elite open via grudge-media + LoadingManager URL rewrite; optimize from original path (do not copy .gltf alone) |
size-verify → convert (FBX2glTF / Blender pack images)
→ magic-byte assertMeshFile
→ enrich / rig
→ suggest r2Key = prod/gltf/<category>/<name>.glb
→ D1/ObjectStore registry row
→ signed R2 PUT → os.registerAsset / writeManifest
Pattern: three.js multi-canvas /
webgl_multiple_elements — one WebGLRenderer, many display canvases.
| Surface | Implementation |
|---|---|
Inline panel / grid (Model3DViewer) |
lib/forge/multiCanvasHub.ts — shared GL, blit to 2D canvas, IntersectionObserver skip off-screen |
Pop-out Elite Viewer (ViewerWindow) |
Dedicated SceneEngine (tools, gizmo, screenshot) |
Prevents N WebGL contexts (black frames, context loss, yellow sludge when many previews open).
loadModel(file, { diskPath }) — TGA handler, Draco + Meshopt + KTX2 via gltfProdLoader (same factory as ThreeFlow r185 — bundled WASM, no gstatic/jsDelivr), magic bytes for GLB/glTF, sanitizeMaterials
diskPath from webUtils.getPathForFile (not file.path). Model loads via grudge-media:// so FBX/OBJ/glTF relative textures / .bin / .mtl resolve next to the file (not lost on blob:). GLTFLoader relatives that drop the media query are rewritten back to the sibling path.grudge-media://local/?path=…Textures/, textures/, Maps/, parents (Kenney packs)finishImportedAsset) only fills missing maps — never overwrites good embedded atlases; bare random PNGs need high name affinityshared/sceneKinds.ts + forge.resolveSceneOpenPath — .bin → sibling .gltf; JSON sniffs glTF / Three ObjectLoader / Forge entitiesloadModelFromUrl(url) — fetch + Content-Type gate + magic + sanitize (Model3DViewer, CDN); remote .gltf sets resourceDir to parent URLoptimizeWebFile / Elite Optimize for web — magic gate first; multi-file .gltf optimizes from original path (embeds into .web.glb)convertToGlb(file, { diskPath }) — convenience only: loadModel (production factory) → GLTFExporter embedImages. Production bake = main convertFile (FBX2glTF / Blender) + optimizeWebFile.?asset= (best scene) · Forge ?asset=/?mesh= (R3F deploy). SSOT shared/editorHandoff.ts.applySmartTextures({ onlyMissingMaps: true }) — sibling atlas fill; role-correct colorSpace (albedo sRGB, normal/ORM linear)bindAtlasToRoot — grudge6 single-atlas patternthree, meshoptimizer, @gltf-transform/core|extensions|functions, sharp (textureCompress)| Symptom | Fix path |
|---|---|
| Wrong / random textures on GLB | onlyMissingMaps (never overwrite embeds with sibling PNGs) |
| Kenney pink/missing maps | mediaProtocol texture fallback + Textures/ dir search |
| FBX yellow / no atlas | diskPath + grudge-media relative TGA/PNG + sanitize yellow |
| Black silhouette | ambient boost in Elite + metalness cap in sanitize |
| Entire viewport black (cube only) | v1.0.10 — ViewHelper was wiping the canvas; update Dev Tool |
| Scrambled sRGB / muddy normals | baseColor → sRGB; data maps → NoColorSpace; ColorManagement on |
| Viewport tints the mesh yellow | Elite default is studio #0a0e1a (sand #EFD1B5 is a preset only) |
| Draco/Meshopt empty mesh | GLTFLoader setDRACOLoader + setMeshoptDecoder |
| Tiny / giant mesh after open | keep author root scale (do not force scale=1) |
| OBJ grey / no maps | sidecar .mtl via MTLLoader + setResourcePath + sibling fill |
| Flat / black mesh | prepareMeshes normals + skinned frustumCulled=false |
| HTML open fails | css3d format → preview plane (not game bake) |
prod/gltf/characters/*.glb
prod/gltf/weapons/*.glb
prod/gltf/enemies/*.glb
prod/gltf/props|nature|buildings|vfx|scenes|misc/*.glb
CDN: https://assets.grudge-studio.com/prod/gltf/...
D1 / ObjectStore is the asset index only (keys, uuid, sha, category).
Player bag/XP remains Railway Postgres — never write heroes into D1.
Seed after upload via:
window.grudge.os.registerAsset(row)window.grudge.os.writeManifest({ packId, version, entries })| Catalog | URL |
|---|---|
| Packages | https://assets.grudge-studio.com/manifests/grudge-prod-packages.json |
| Prefabs | https://assets.grudge-studio.com/manifests/grudge-prod-prefabs.json |
| Asset index | https://assets.grudge-studio.com/manifests/assets-gltf-index.json |
Yellow-fixed races (atlas bake):
prod/gltf/characters/{wk,brb,elf,dwf,orc,ud}_characters.glb + human.glb
Fantasy weapons:
prod/gltf/weapons/{sword,bow,staff,dagger,axe,hammer,mace}.glb
Code SSOT in Dev Tool: src/shared/prodPackages.ts (GRUDGE6_PROD_GLTF, FANTASY_WEAPON_PROD_GLTF, fetchProdPackages).
Rebuild from ObjectStore:
cd ObjectStore
npm run packages:prod:upload
npm run build:weapon-prefabs
node scripts/wire-prod-gltf-weapon-prefabs.mjs
grudge-asset-convert — production bake CLIgrudge-d1-r2 — registry topologygrudge-character-correctness — yellow atlas rulesscripts/build-prod-gltf-packages.mjs — packages + yellow re-bakescripts/assets-to-gltf-pipeline.mjs — batch prod/gltf