Files
roam/trmnassets.org
2026-06-19 18:09:39 +03:00

6.4 KiB
Raw Permalink Blame History

trmn/assets

trmn — asset pipeline: SVG sprites + GLB objects + location scenes

Architecture

Three asset types, all served from $out/assets/ by Caddy:

  • Sprites: SVG files in assets/sprites/{eyes,nose,mouth}/, static/committed, no build step
  • 3D objects: GLB files in assets/objects/, generated during nix build by Node.js scripts in scripts/
  • Location scenes: .blend files in assets/scenes/** are the source of truth; Blender exports GLBs at build time, then packed into assets/scenes/scenes.pack (single binary, one browser fetch)

Face compositing at runtime: load SVG parts → draw onto OffscreenCanvas(128,128)THREE.CanvasTexture. Applied to the +z front face (index 4) of the head BoxGeometry via a material array.

Location Scene Pipeline

.blend files are the source of truth (edit in Blender, commit .blend, GLBs are build artifacts):

  1. flake.nix buildPhase: blender --background file.blend --python scripts/export-glb.py -- out.glb for each .blend in assets/scenes/**
  2. scripts/pack-scenes.mjs glb_tmp assets/scenes/scenes.pack: validates all SCENES location IDs have a GLB (warn by default, STRICT_LOCATIONS=1 to error), concatenates into scenes.pack
  3. Pack format: [4-byte LE uint32: manifest len][JSON manifest][...GLB bytes]
  4. Runtime: loadPack() fetches once, loadLocationScene(id) slices + parses per scene, cached

34 unique location IDs (e.g. home/bedroom, office, school/classroom). Directory mirrors IDs: assets/scenes/home/bedroom.blend → ID home/bedroom. Full mapping in ASSETS.md.

Blender Naming Conventions (procedural overrides)

  • IF_<NAME> meshes: toggled visible/hidden at runtime by applyLocationProps() Examples: IF_CRIB (age<3), IF_ADULT_BED (age≥13), IF_TV_LARGE (wealthLevel>70)
  • WALLS, FLOOR: material color replaced at runtime with deterministic palette color
  • All other mesh names: always visible, untouched

Full IF_* table and authoring guide in ASSETS.md.

Blender Scene Setup

  • Character is ~2 units tall; typical interior: 58 units wide, 3 units tall
  • Camera: pos (0.5, 1.6, 6.5), looks at (0, 1.0, 0), FOV 26°, aspect 2.39:1
  • No lights in .blend files — runtime adds ambient + two directional lights
  • Materials: Diffuse BSDF (flat color); Principled BSDF works but is overkill under Lambert
  • export_apply=True in export script — modifiers applied; no armatures/animations
  • Poly budget: ~500 triangles/scene; outdoor scenes skip WALLS/FLOOR (no runtime error)
  • Run nix develop to get a shell with blender on PATH for authoring

Runtime API (index.html Three.js block)

  • loadPack(): called once at init; gracefully no-ops if pack not built yet
  • loadLocationScene(locationId): returns THREE.Group (cloned from cache), or null if not in pack
  • applyLocationProps(threeScene, props): traverses scene, applies IF_* visibility + WALLS/FLOOR colors
  • generateLocationProps(locationId, masterSeed, playerState, eventLog?) from locations.js
  • window.__sceneLoader.loadAndShow(locationId, props): removes previous loc scene, loads + applies new one

Debug Panel (▸ scene debug in browser)

Collapsible <details> panel below the chat box. Controls:

  • Location dropdown (all 34 IDs, populated from SCENES at runtime)
  • Sliders: age, wealth, children, job tier, moves (home move count)
  • sync from today: pulls actual simulated state from window.__life
  • load scene: calls window.__sceneLoader.loadAndShow(); shows [not in pack] if .blend missing
  • Live props display: wall/floor color swatches + hex, visible IF_* mesh names

Props update on every slider move even without a loaded scene.

locations.js

ES module, imported by Three.js block and debug module. Exports generateLocationProps.

  • Home: seeded from makeRng(masterSeed, 200000 + moveIndex) where moveIndex = count of move_city events ≤ currentDay → new house each time player moves
  • Office: offset 300000; School: 301000; University/dorm: 302000; others: hash into 303000399999
  • playerState fields: { age, wealthLevel, numChildren, jobTier, inCollege, retired, currentDay }

PRNG Offset Allocation (all streams)

Range Owner
-2 to -1 Family layout + protagonist traits
0 36,500 Life simulation daily
50,000+ Schedule (per-day, offset 50000)
200,000 + moveIndex Home appearance (per house)
300,000 Workplace
301,000 School
302,000 University / dorm
303,000399,999 Other locations (hashed from ID)

Conventions

  • All sprite SVGs are 128×128; composite with drawImage(img, 0, 0, 128, 128)
  • Face part selection seeded from window.__life.masterSeed via inline mulberry32 in Three.js block
  • GLB generators: pure Node.js stdlib only (no npm), accept output path as arg
  • pkgs.blender in nativeBuildInputs (build) and devShells.buildInputs (nix develop shell)

Gotchas

  • src = ./. only includes git-tracked files. Run git add assets/scenes/ scripts/export-glb.py scripts/pack-scenes.mjs locations.js before nix build or they are silently excluded.
  • BoxGeometry material array face order: [+x, -x, +y, -y, +z, -z] → index 4 is front face
  • GLB generator runs in Nix buildPhase and writes to build dir; cp -r assets $out/assets picks up generated files
  • locations.js imports makeRng from ./life.js; served by Caddy alongside other JS modules
  • Blender headless needs --background flag; no X11 required in stdenvNoCC sandbox

Key Files

  • assets/sprites/eyes/eyes_{1,2}.svg — wide-open vs narrow/tired eyes
  • assets/sprites/nose/nose_{1,2}.svg — dot vs nostrils nose
  • assets/sprites/mouth/mouth_{1,2}.svg — smile vs neutral mouth
  • assets/objects/book.glb — generated at build time; dark-red flat box 0.14×0.18×0.03
  • assets/scenes/.blend source files, mirroring location ID hierarchy
  • assets/scenes/scenes.pack — build artifact; binary pack of all location GLBs
  • scripts/gen-book.mjs — pure Node.js GLB writer for book object
  • scripts/export-glb.py — Blender Python: exports active scene to GLB
  • scripts/pack-scenes.mjs — validates + concatenates GLBs into scenes.pack
  • locations.jsgenerateLocationProps(locationId, masterSeed, playerState, eventLog?)
  • ASSETS.md — full authoring guide: location→file map, IF_* table, Blender setup, workflow