Paperdoll on the character sheet, and a themed Export PNG character card #45

Open
opened 2026-09-15 12:39:33 +00:00 by wtclaude · 0 comments
Member

Future plan — not scheduled, nothing to implement yet. Filed so the investigation behind it survives.

What we want

On the character sheet (client/src/components/CharacterSheet.jsx): a real UO paperdoll showing the character as they actually appear in game — their own body, hair and worn gear, correctly hued and layered — plus an Export PNG button producing a shareable character card (paperdoll, name, standing, skills, stats) styled from the site's own CSS theme variables, so a themed install exports in its own palette.

Why it is not a small change

A paperdoll is composed from gump art: a body gump per race/gender, and one gump per worn item at ItemData.AnimID + 50000 (male) / + 60000 (female). Protocol 8 consumed no gump art, and that is a safety rule rather than a scoping preference.

docs/link/v8.md §4.7 / §17.15: Ultima.Gumps.GetGump(2) does not fail, it corrupts the processAccessViolationException, exit 0xC0000005. Phase 0 called it once from inside a running shard and killed the shard. The fault is on the hasExtra: true FileIndex branch that only Gumps reaches; servuo-plugins/tools/scaffolding/BridgeAssetProbe.cs keeps a gump section whose documented purpose is to reproduce the crash.

What the Asset Bridge did consume cannot substitute:

Family Key The picture
body body/400/a0 one animation frame of a naked, unhued body, direction 0
static / land static/3922/h33 the item's inventory icon, hued shard-side

Item icons are inventory sprites of assorted sizes. Stacked, they make a pile, not a figure.

Two further gaps, independent of the decoder:

  • The site is never told a character's hair. BridgeProfile.IsGearLayer excludes Layer.Hair and Layer.FacialHair, so any figure we draw today is bald. Fixing it is a char.profile shape change.
  • The body sprite carries no hue. BridgeCatalog decodes with preserveHue: false, and body keys have no hue segment, so a character's skin colour is not representable. A hued body key (body/<id>/a<n>/h<hue>) is well-formed under §5 and simply not served.

Scope when it is picked up

  • servuo-plugins — our own gump decoder (never Ultima.Gumps). Phase 4's BridgeUop.cs block-chain reader is reusable for the UOP container; the gump record format and the extra width/height field are new. Then: the gump key family, the AnimID→gump-id mapping with the female-gump-absent fallback, gump PartialHue, and Hair/FacialHair on char.profile.
  • link — most likely nothing: assets_call forwards a request body verbatim and respond_assets returns the reply verbatim, so a new key family has passed through untouched for three phases running.
  • Module-uo — the paperdoll layer/draw-order table, the composition model, the on-page renderer and the PNG card.
  • docslink/v9.md (or a v8 amendment), the protocol bump, modules/uo/SCHEMA.md.
  • servuo-plugins/overlay.toml + PROTOCOL_VERSION bumped in the same PR as the emitters, or the next bundle silently fails to compose.

The cheaper alternative, if the real thing stays parked

A character card with no protocol change at all, which is a strict subset of the above rather than throwaway work: the figure is the character's own body sprite (body/<bodyId>/a<n> — the catalogue already imports all six player bodies, §5.2), with the correctly hued item icons laid out in paperdoll-shaped slots around it, plus the Export PNG card. Server side is one fileForBody(bodyId) lookup in shardAssets.db (pinned to the catalogue key the way the atlas join is) and one bodyArt field from enrichCharProfile; client side is a pure composition model shared by the on-page canvas and the export canvas so the two cannot drift. Caveats are honest and visible: no worn gear on the figure, no hair, and the wrong skin colour.

The slot model, card layout, theme-token reader and export renderer all survive unchanged when gumps land — only the figure layer is swapped.

Design notes worth keeping

  • Export is client-side canvas + toBlob + a blob-URL anchor — no library, and no server-side image generation. /uploads is same-origin, so the canvas stays untainted. Precedent: website/client/src/components/security/RecoveryCodesDisplay.jsx.
  • Theme colours come from getComputedStyle(document.documentElement), which picks up the admin-configured tokens themeVars.js writes onto <html>. Use the flat tokens and rebuild --panel-grad from its endpoints — canvas cannot consume a CSS gradient string.
  • --display is Cinzel, a webfont: await document.fonts.load(...) before drawing or the card silently exports in Georgia on a cold load.
  • Hue is resolved shard-side and always will be — a hued item is a different PNG, never a tinted one, because PartialHue lives in tiledata.mul, which the browser will never have.
  • Missing art stays a first-class permanent state (9,963 static ids are empty on a stock client). Every layout must look right with nothing loaded.

Open questions

  • "Level" — UO has none. Skill total is the usual analogue; the fame/karma title is the in-fiction one.
  • Empty equipment slots: drawn as placeholders (reads as a paperdoll) or omitted (reads as a list)?
  • Is a public share URL ever wanted? /shard/char/:serial is owner-or-admin only, and a client-side export adds no exposure — a public card would mean server-side rendering, which this plan deliberately avoids.

Investigation and write-up AI-assisted (Claude Code); no code changed.

🤖 Generated with Claude Code

https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4

Future plan — **not scheduled, nothing to implement yet.** Filed so the investigation behind it survives. ## What we want On the character sheet (`client/src/components/CharacterSheet.jsx`): a real UO paperdoll showing the character as they actually appear in game — their own body, hair and worn gear, correctly hued and layered — plus an **Export PNG** button producing a shareable character card (paperdoll, name, standing, skills, stats) styled from the site's own CSS theme variables, so a themed install exports in its own palette. ## Why it is not a small change A paperdoll is composed from **gump art**: a body gump per race/gender, and one gump per worn item at `ItemData.AnimID + 50000` (male) / `+ 60000` (female). **Protocol 8 consumed no gump art, and that is a safety rule rather than a scoping preference.** `docs/link/v8.md` §4.7 / §17.15: `Ultima.Gumps.GetGump(2)` does not fail, it **corrupts the process** — `AccessViolationException`, exit `0xC0000005`. Phase 0 called it once from inside a running shard and killed the shard. The fault is on the `hasExtra: true` `FileIndex` branch that only `Gumps` reaches; `servuo-plugins/tools/scaffolding/BridgeAssetProbe.cs` keeps a `gump` section whose documented purpose is to reproduce the crash. What the Asset Bridge *did* consume cannot substitute: | Family | Key | The picture | |---|---|---| | `body` | `body/400/a0` | one animation frame of a **naked, unhued** body, direction 0 | | `static` / `land` | `static/3922/h33` | the item's **inventory icon**, hued shard-side | Item icons are inventory sprites of assorted sizes. Stacked, they make a pile, not a figure. Two further gaps, independent of the decoder: - **The site is never told a character's hair.** `BridgeProfile.IsGearLayer` excludes `Layer.Hair` and `Layer.FacialHair`, so any figure we draw today is bald. Fixing it is a `char.profile` shape change. - **The body sprite carries no hue.** `BridgeCatalog` decodes with `preserveHue: false`, and body keys have no hue segment, so a character's skin colour is not representable. A hued body key (`body/<id>/a<n>/h<hue>`) is well-formed under §5 and simply not served. ## Scope when it is picked up - **servuo-plugins** — our own gump decoder (never `Ultima.Gumps`). Phase 4's `BridgeUop.cs` block-chain reader is reusable for the UOP container; the gump record format and the `extra` width/height field are new. Then: the `gump` key family, the AnimID→gump-id mapping with the female-gump-absent fallback, gump `PartialHue`, and Hair/FacialHair on `char.profile`. - **link** — most likely nothing: `assets_call` forwards a request body verbatim and `respond_assets` returns the reply verbatim, so a new key family has passed through untouched for three phases running. - **Module-uo** — the paperdoll layer/draw-order table, the composition model, the on-page renderer and the PNG card. - **docs** — `link/v9.md` (or a v8 amendment), the protocol bump, `modules/uo/SCHEMA.md`. - **servuo-plugins/overlay.toml** + `PROTOCOL_VERSION` bumped in the same PR as the emitters, or the next bundle silently fails to compose. ## The cheaper alternative, if the real thing stays parked A **character card** with no protocol change at all, which is a strict subset of the above rather than throwaway work: the figure is the character's own body sprite (`body/<bodyId>/a<n>` — the catalogue already imports all six player bodies, §5.2), with the correctly hued item icons laid out in paperdoll-shaped slots around it, plus the Export PNG card. Server side is one `fileForBody(bodyId)` lookup in `shardAssets.db` (pinned to the catalogue key the way the atlas join is) and one `bodyArt` field from `enrichCharProfile`; client side is a pure composition model shared by the on-page canvas and the export canvas so the two cannot drift. Caveats are honest and visible: no worn gear on the figure, no hair, and the wrong skin colour. The slot model, card layout, theme-token reader and export renderer all survive unchanged when gumps land — only the figure layer is swapped. ## Design notes worth keeping - Export is client-side canvas + `toBlob` + a blob-URL anchor — **no library, and no server-side image generation.** `/uploads` is same-origin, so the canvas stays untainted. Precedent: `website/client/src/components/security/RecoveryCodesDisplay.jsx`. - Theme colours come from `getComputedStyle(document.documentElement)`, which picks up the admin-configured tokens `themeVars.js` writes onto `<html>`. Use the flat tokens and rebuild `--panel-grad` from its endpoints — canvas cannot consume a CSS gradient string. - `--display` is Cinzel, a webfont: `await document.fonts.load(...)` before drawing or the card silently exports in Georgia on a cold load. - Hue is resolved shard-side and always will be — a hued item is a different PNG, never a tinted one, because `PartialHue` lives in `tiledata.mul`, which the browser will never have. - Missing art stays a first-class permanent state (9,963 static ids are empty on a stock client). Every layout must look right with nothing loaded. ## Open questions - **"Level"** — UO has none. Skill total is the usual analogue; the fame/karma title is the in-fiction one. - Empty equipment slots: drawn as placeholders (reads as a paperdoll) or omitted (reads as a list)? - Is a **public** share URL ever wanted? `/shard/char/:serial` is owner-or-admin only, and a client-side export adds no exposure — a public card would mean server-side rendering, which this plan deliberately avoids. --- Investigation and write-up AI-assisted (Claude Code); no code changed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
wtclaude added this to the Paperdoll & character card (Protocol 9) milestone 2026-09-15 12:39:33 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RunicGateway/Module-uo#45
No description provided.