- gen_props.py: flux_local -> trellis_mac -> normalize pipeline; 5/8 props kept (arcade-cabinet, listening-booth, drinks-fridge, milkshake-mixer, novelty-record) - magazine-rack -> primitive (thin-wire gotcha); glass-case + bus-shelter -> fal.ai fallback - cash-register reused from depot; all 22 GLBs live, validate --depot 0/0 - loaders.js ?localdepot=1 + stage_local_depot.py for network-free GLB validation - qa.sh --strict GREEN 4/4 at commit time Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
63 lines
2.9 KiB
JavaScript
63 lines
2.9 KiB
JavaScript
// PROCITY core loaders — promise-cached, fail-soft (house pattern from both parent games).
|
|
// Missing asset ⇒ null / flat colour; callers keep their placeholder. The game runs asset-free.
|
|
|
|
import * as THREE from 'three';
|
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
|
|
export const DEPOT = 'https://digalot.fyi/3god'; // shared GLB CDN: GET /a/<file>, /api/list
|
|
|
|
// Local-depot mode (Lane E round-3): resolve depot:<file> to GLBs served by the dev server
|
|
// (web/assets/models/) instead of the CDN, so Lanes B/C/F can validate GLB loading with NO
|
|
// network / before the depot publish. Enable per-load with ?localdepot=1 (or ?localdepot=<base>),
|
|
// or globally with window.PROCITY_LOCALDEPOT = true | '<base>'. Staged GLBs = the normalized
|
|
// library set + hero props (mirror of pipeline/_normalized; run pipeline/stage_local_depot.py).
|
|
export const LOCAL_MODELS = 'assets/models/';
|
|
function localDepotBase() {
|
|
try {
|
|
const q = new URLSearchParams(location.search).get('localdepot');
|
|
if (q === '1' || q === '' || q === 'true') return LOCAL_MODELS;
|
|
if (q) return q.endsWith('/') ? q : q + '/';
|
|
} catch { /* no location (non-browser import) */ }
|
|
const g = (typeof window !== 'undefined') && window.PROCITY_LOCALDEPOT;
|
|
if (g === true) return LOCAL_MODELS;
|
|
if (typeof g === 'string' && g) return g.endsWith('/') ? g : g + '/';
|
|
return null;
|
|
}
|
|
const LOCAL_DEPOT = localDepotBase();
|
|
// resolve a depot:<file> ref to a concrete URL (local mode → served copy, else the CDN)
|
|
export function depotURL(file) {
|
|
return LOCAL_DEPOT ? LOCAL_DEPOT + file : `${DEPOT}/a/${file}`;
|
|
}
|
|
|
|
const gltfLoader = new GLTFLoader();
|
|
const texLoader = new THREE.TextureLoader();
|
|
const GLB_CACHE = {}; // url → Promise<gltf|null>
|
|
const TEX_CACHE = {}; // url → THREE.Texture (may still be loading; fallback colour beneath)
|
|
|
|
// loadGLB('gen/x.glb' | 'depot:file.glb' | full URL) → Promise<gltf|null>, cached, never rejects
|
|
export function loadGLB(ref) {
|
|
const url = ref.startsWith('depot:') ? depotURL(ref.slice(6)) : ref;
|
|
return (GLB_CACHE[url] ||= gltfLoader.loadAsync(url).catch(err => {
|
|
console.warn('[loaders] GLB failed, placeholder stays:', url, err?.message || err);
|
|
return null;
|
|
}));
|
|
}
|
|
|
|
// loadTex(url, {repeat:[u,v]}) → texture immediately (fills in when loaded), sRGB, aniso 4
|
|
export function loadTex(url, opts = {}) {
|
|
if (TEX_CACHE[url]) return TEX_CACHE[url];
|
|
const t = texLoader.load(url, undefined, undefined,
|
|
() => console.warn('[loaders] tex failed, fallback colour stays:', url));
|
|
t.colorSpace = THREE.SRGBColorSpace;
|
|
t.anisotropy = 4;
|
|
if (opts.repeat) { t.wrapS = t.wrapT = THREE.RepeatWrapping; t.repeat.set(...opts.repeat); }
|
|
TEX_CACHE[url] = t;
|
|
return t;
|
|
}
|
|
|
|
// fetch + cache the depot listing (editor palettes, Lane E validation)
|
|
let _depotList = null;
|
|
export async function depotList() {
|
|
return (_depotList ||= fetch(`${DEPOT}/api/list`).then(r => r.json()).catch(() => []));
|
|
}
|