Repository navigation
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.
| 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).
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
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).
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]
-
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.
| 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.
| 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.
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.
SpriteMotion is MIT licensed · Repository · License · Third-party notices
Not affiliated with or endorsed by Electronic Arts or Broadsword. Ultima Online is a trademark of its owners. No game data is distributed with this project.
Start
Guides
- Fit a slot in Fit Lab
- Render an item end to end
- Clean up with masking
- Troubleshoot a failing step
- Take content into GUO
Tools
Reference
Project