Skip to content
Felucca Online

Docs

Brief: modelling a group of UO objects

You model and bake one group of world objects for UO Remastered, a remaster of Ultima Online with 2.5D pixel-art sprites lit per pixel. Work in the git worktree /Users/aler/projects/daily/day-1014-uo/.claude/worktrees/uo-objects (branch feat/uo-objects): run every command from there, with plain relative paths, never cd elsewhere. Other agents model other groups in parallel in the same worktree.

Read first

  1. docs/uo-objects.md: the catalog, conventions (scale, facings, walls, multi-tile origin) and your group's list. Model every sprite listed for your group.
  2. docs/asset-pipeline.md §1 Modelling (Principled BSDF only, relief as geometry, closed meshes, think in texels, emission).
  3. tools/blender/models/object_kit.py: shared helpers (param, material, pal/PALETTE, flame, glow, box, span, cylinder, post, cone, sphere, torus, lathe (caps=False for open shapes), ring (hoops, rims), rod/beam (between two points), extrude (2D outline prism), dome (rests on the ground), footprint_root (the _s/_e rotation for multi-tile objects), empty, cutter/subtract, add_bevel; constants WALL_PLANE = 0.425 and WALL_FACE = 0.35, the front of a north wall where wall-mounted items hang). Look at an existing group kit before writing helpers (e.g. lights_crafting/lc_kit.py: fire, candles, coals, masonry; civic_dungeon/civic_kit.py; nature/nature_kit.py: blobs, crowns). Note: the preview's lit row has no environment light, so metals look darker there than in the game (toon mode lights metals diffusely): don't lower metallic to compensate.
  4. Reference models: tools/blender/models/wallkit.py + wall_x_window.py (walls, booleans, trims, glass glow), crate.py, brazier.py (flames), tree.py, character.py (scale).

What to produce

  • Scripts in tools/blender/models/<group>/, one per asset or one per family selected by param(...). Each must build its model into an empty scene with no other input. Import the kit with

    import os, sys
    sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
    from object_kit import ...  # noqa: E402
    

    A group-private helper module (e.g. <group>/<group>_kit.py) is fine.

  • Manifest entries in assets/manifest.d/<group>.toml (create it), e.g.

    [[asset]]
    name = "chair_wood"
    model_script = "../../tools/blender/models/furniture/chair_wood.py"
    directions = 4
    
    [[asset]]
    name = "wall_stone_window_x"
    model_script = "../../tools/blender/models/walls/wallset_stone.py"
    params = { piece = "window_x" }
    

    Multi-tile objects with a front (beds, long tables, fireplaces, troughs) do not use directions = 4 (rotating about the origin would leave the footprint). Like UO, bake one asset per orientation, <name>_s (front south) and <name>_e (front east), from one script with params = { facing = "s" } / "e", each with its own footprint ([w, h] and [h, w]), origin at the north-west tile centre.

    Keys: directions (1 | 4), footprint = [w, h] for multi-tile objects, params (seen by the script as param("piece")), padding (default 2).

  • Baked sprites in assets/sprites/<name>/ (the baker writes them).

Modelling rules

  • 1 unit = 1 tile; origin on the ground at the tile centre (multi-tile: centre of the north-west-most tile, extending east +X and south −Y); Z >= 0; front faces −Y (south). Fit inside the footprint (a 1x1 object ≤ ~0.9 wide).
  • Think in texels: a tile is 32 px wide, 1 texel ≈ 0.045 units. Build on the texel lattice (pixel_kit.py, rules in docs/asset-pipeline.md): whole texels and levels, no sub-texel details or bevels. Make silhouettes bold, exaggerate key features (a chair back, a sword hilt), use colour contrast between parts (dark frame / light panel, metal vs wood) rather than fine geometry.
  • UO look: muted, warm, readable; use PALETTE so groups match. Metals metallic = 1; glass/water low roughness; flames and glowing things use emission (flame(), glow()), nothing else emits.
  • Bevel hard edges (bevel= / add_bevel) so normals catch highlights; smooth-shade curved parts. Closed meshes; attached parts overlap a few mm.
  • Small items lying on the ground (weapons, tools, goods) are modelled at a readable, slightly exaggerated size (a sword ~0.7 long, lying flat or propped), still within the tile.

Bake and review

Binaries are built; never run cargo. From the worktree root:

target/release/uo-baker bake-all --only chair_wood --only stool --preview
python3 -I tools/gallery.py --group assets/manifest.d/<group>.toml -o <scratch>/<group>.png

bake-all reads assets/manifest.toml plus every assets/manifest.d/*.toml. Validation must pass for every sprite. Look at the gallery (albedo row and lit row per facing; --max-frames, --scale 2 for detail; write sheets and crops to the scratch directory you are given, never into the repo) and iterate until each object reads clearly as what it is at this size, sits on the ground at the right scale, and the facings are right (South frame shows the front). Compare scale against assets/sprites/character and assets/sprites/crate in the gallery.

Constraints

  • Only create/edit files of your group: tools/blender/models/<group>/**, assets/manifest.d/<group>.toml, assets/sprites/<your sprites>/.
  • Do NOT edit object_kit.py, other groups' files, assets/manifest.toml, docs, CLAUDE.md or Rust code; put suggestions in your report.
  • Do NOT use the Blender MCP / live Blender; bake headless only.
  • Never kill processes you did not start; no git commands that change state.

Report

Files created, sprites baked (all names), anything skipped and why, and any object that still does not read well; plus suggestions for object_kit.py.