Skip to content

Architecture

Moshu edited this page Oct 5, 2026 · 1 revision

Architecture

SpriteMotion is a monorepo of small tools around one shared Python package. Game-specific knowledge lives in game folders; the shared layer knows nothing about any game.

Repository layout

Folder Role
common/ The shared package, imported as spritemotion: sprites/datasets, poses, estimation, fitting (rig, camera, solver), rendering helpers, pipeline CLI, JSON/schema I/O. Standard library + NumPy + Pillow only.
schemas/ Every JSON data contract (Data Contracts)
games/<game>/ Everything game-specific, reached through game.json: the extraction adapter, profiles, skeletons, annotations, equipment slots, recipes, research. games/ultima-online/ is the reference.
tools/<job>/ One folder per job with an entry point: uo-content (Content Studio + Blender build + client staging), fit-lab, workbench, starter-assets, blender (reconstruction scripts), sprite-pose-editor (Godot), vd, agents, godot (third-party runtime) and more
launchers/ Windows .bat (and some .sh) launchers grouped by job; every one calls launchers/_shared/common.bat first
examples/ Redistributable content: the procedural sample character, the CC0 starter equipment, a fictional asset-pack mapping
tests/ pytest suites, including repository guards
workspace/, outputs/ Ignored. Extracted frames, installed model, jobs, renders, lab data

Outside the repository: your UO client (SPRITEMOTION_UO_SOURCE), the UO_Model3D source, Blender (SPRITEMOTION_BLENDER) and the private sidecar for licensed packs (SPRITEMOTION_SIDECAR).

Code dependencies

flowchart TB
  subgraph shared["common/ (package: spritemotion)"]
    S1[sprites + datasets]
    S2[poses + estimation]
    S3[fitting: rig, camera, solver]
    S4[rendering helpers]
    S5[pipeline CLI + schemas]
  end
  SCH[(schemas/*.json)]
  subgraph games["games/ultima-online/"]
    G1[game.json + extraction adapter]
    G2[profiles, skeletons, annotations]
    G3[equipment/layers.json]
    G4[outfit-lab, region-masks]
  end
  subgraph tools["tools/"]
    T1[uo-content: Studio, pipeline, client staging]
    T2[fit-lab: server + web UI]
    T3[workbench]
    T4[starter-assets]
    T5[blender scripts]
    T6[sprite-pose-editor - Godot]
    T7[vd tools]
  end
  L[launchers/*.bat, *.sh] --> tools
  L --> shared
  shared --> SCH
  shared -->|loads adapter named in game.json| G1
  T1 --> shared
  T1 --> G3
  T2 --> T1
  T3 --> T1
  T3 --> T2
  T4 --> T1
  T5 --> shared
  T6 -->|reads datasets| S1
Loading

common/ never imports game code by name: it reads games/<game>/game.json and loads the adapter file it names. Adding a game adds files only under games/<game>/ (docs/adding-a-game.md).

Data flow: from a model to the client

flowchart LR
  subgraph inputs["Your local inputs"]
    M[3D item / CC0 starter / pack parts]
    UOM[UO_Model3D v13 source]
    UOC[(Your UO client)]
  end
  subgraph canon["workspace/ultima-online/canonical-model"]
    BODY[UO_Body_0x190.blend + UO_Rig]
  end
  UOM -->|pipeline.py setup| BODY
  M --> CS[Content Studio :8772]
  M -->|pack export| FL[Fit Lab :8774]
  BODY --> FL
  FL -->|lab-adjustments.json| CS
  FL -->|Build item / Rebuild changed blocks| BB
  CS -->|job settings| BB[Blender build]
  BODY --> BB
  BB --> JOB[job: item.blend, PNG frames, item.vd, review, validation, import-package.zip]
  JOB -->|Blender render pane, review| FL
  JOB -->|Animation review| CS
  JOB --> FID[UOFiddler import]
  JOB -->|client_import.py / equipment.py| STG[Staged copies: anim, art, tiledata, server item class]
  UOC -->|read only| STG
  STG --> GAME[Your test client + shard]
Loading
  • Content Studio ↔ Fit Lab. Both run locally and link to each other. Fit Lab saves lab-adjustments.json; Studio pack jobs and lab builds read it (snapshotted at job creation). The workbench starts both.
  • Fit Lab ↔ Blender. The lab's live 3D view is a WebGL preview of exported GLBs. Build item / Rebuild changed blocks run the real Blender build in the background, and the render pane shows the result next to the live view. Fit rules are implemented once in Python and once in JavaScript and are parity-tested.
  • Blender ↔ client staging. Only finished, validated jobs are staged, and only into new folders. The source client is read, never written.

Local services

Service Port Started by
Content Studio 8772 launchers/editor/content-studio.bat, python tools/uo-content/studio.py
Fit Lab 8774 launchers/editor/fit-lab.bat <pack>, python tools/fit-lab/run.py serve --pack <pack>
Both 8772 + 8774 launchers/editor/workbench.bat [pack]

Both bind to 127.0.0.1. Fit Lab exposes a versioned service contract (/api/service, schema_version: 1) so that other editors, such as GUO, can share one process. Revision-checked saves (HTTP 409 on a stale revision) keep concurrent editors from overwriting each other.

Launchers

Folder Contents
launchers/_shared/ config.bat (the only file you edit), common.bat (shared logic, never run directly)
launchers/editor/ workbench, content-studio, fit-lab, sprite-pose-editor, sample-character, outfit-lab, open-godot-project
launchers/pipeline/ 0-setup … 6-compare, versions: the reconstruction steps in order
launchers/dev/ run-tests, editor-tests, editor-screenshot, blender-smoke, sample-loop, regenerate-sample, install-blender-addon

Settings resolve environment variable → config.bat → default; no launcher hard-codes a path.

Agents

Workflows for coding agents live in .claude/skills/. python tools/agents/run.py generates the routers (AGENTS.md, .github/copilot-instructions.md, .cursor/rules/) from them; --check verifies they are current. Edit CLAUDE.md or a skill, never a generated router.

Clone this wiki locally