Custom GLBs drop into 14 named slots without code changes; an empty manifest
produces today's game by construction (the fallback builders are the original
code moved into a closure, and an empty registry returns the caller's own
object by identity).
- src/assets/{slots,manifest,idb,registry,blobBody}.ts — schema + validation,
GLTF cache with per-instance material cloning, fit nodes, IndexedDB override
layer, paintability report.
- Slot hooks in createBlob, parts, cannon, greybox, puddles, ghost. Mesh-only:
no collider, physics or logic line is touched. Animated sub-parts (plate cap,
belt chevrons, fan blades, cannon pivot) stay procedural so a custom model
cannot stop them moving.
- public/assets/ — the build had NO asset copy step at all, so every asset URL
would have 404'd in production. public/ is vite's default publicDir, so this
needs no vite.config change.
- Tests: 63 headless checks + a farm-GLB audit that fires PaintSkin's own
raycast against the fitted body.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
26 lines
881 B
JavaScript
26 lines
881 B
JavaScript
/**
|
|
* Node resolve hook so the repo's extensionless TS imports (`./manifest`) work
|
|
* under `node --experimental-strip-types`, which otherwise demands a real file
|
|
* extension. Vite resolves these at build time; node does not.
|
|
*
|
|
* node --import ./scripts/ts-resolve.mjs --experimental-strip-types src/assets/manifest.test.ts
|
|
*/
|
|
import { registerHooks } from 'node:module'
|
|
import { existsSync } from 'node:fs'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
registerHooks({
|
|
resolve(specifier, context, next) {
|
|
try {
|
|
return next(specifier, context)
|
|
} catch (err) {
|
|
if (!specifier.startsWith('.') || !context.parentURL) throw err
|
|
for (const ext of ['.ts', '/index.ts']) {
|
|
const url = new URL(specifier + ext, context.parentURL)
|
|
if (existsSync(fileURLToPath(url))) return next(specifier + ext, context)
|
|
}
|
|
throw err
|
|
}
|
|
},
|
|
})
|