feat(assets): a creature's picture is whichever action has one (Phase 6) #39

Merged
whitlocktech merged 1 commits from feat/asset-bridge-p6 into edge 2026-09-14 06:16:12 +00:00
Member

What & why

The website half of Asset Bridge phase 6 (docs/link/v8.md §11.2, §12.1). Pairs with servuo-plugins#33.

The shard can now catalogue a body at whichever action has art, so 73 bodies that were absent get a picture — body 820's is at action 23, and it is a horse. This side stores that action and stops assuming a0.

The atlas join is the part that mattered. It read

JOIN shard_assets a ON a.asset_key = CONCAT('body/', b.body, '/a0')

which would have silently dropped exactly the creatures this phase adds. It now reads the row's own action:

JOIN shard_assets a ON a.body = b.body AND a.family = 'body'
 AND a.asset_key = CONCAT('body/', b.body, '/a', COALESCE(a.action, 0))

COALESCE because a row written before the column existed has NULL there, and a NULL inside CONCAT makes the whole comparison NULL — which would have taken every portrait off the site on upgrade, with the database perfectly correct and nothing in any log. It still matches at most one row per slug: a deeper key (body/820/a23/f4) does not equal the catalogue key, which is what §12.1 asked this join to keep being true.

action is stored rather than parsed back out of the key, because the join needs it in SQL and re-deriving it there would put a second, weaker parser of §5's key scheme in the schema.

No routes, no MODULE_API bump, no client changes.

How it was tested

npm test691 pass, including two new bridge tests (a row's action survives the manifest walk; an overlay older than phase 6 reads as action 0) and one new model test (a fallback key is fetched, written as uo-body-820-a23-<sha8>.png and stored with its action).

And the part no stub can check — the real shardAssets.db against a real MariaDB 11.8, in a throwaway database, using the module's own schema.sql statements and the live shard's 1,095-row manifest:

columns before the upgrade: asset_key, family, sha256, bytes, width, height, body, direction, file, imported_at, catalog
after the upgrade: action tinyint(4)          (and re-running the ALTER is a no-op, as boot requires)
saved 1095 rows through saveAssets
read back: { key: 'body/820/a23', action: 23, file: 'uo-body-820-a23-be5a991c.png' }
artBySlug: { 'timber-wolf': 'uo-body-34-a0-…', human: 'uo-body-400-a0-…',
             horse: 'uo-body-820-a23-…', phoenix: 'uo-body-832-a1-…' }
a pre-phase-6 NULL-action row: still joins
with a deep frame key stored, horse -> uo-body-820-a23-be5a991c.png

Checklist

  • I have read CONTRIBUTING.md.
  • The change builds and existing tests/checks pass locally.
  • I have added or updated tests/docs where it makes sense.
  • My commits are reasonably scoped with clear messages.

AI-assisted contributions (required)

  • AI tools were used. Tool(s): Claude Code (Opus 5). I have reviewed and understand every change, and take responsibility for it. AI-authored commits are marked with a Co-Authored-By trailer.

🤖 Generated with Claude Code

https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4

## What & why The website half of Asset Bridge phase 6 ([`docs/link/v8.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v8.md) §11.2, §12.1). Pairs with servuo-plugins#33. The shard can now catalogue a body at whichever action has art, so 73 bodies that were absent get a picture — body 820's is at action 23, and it is a horse. This side stores that action and stops assuming `a0`. **The atlas join is the part that mattered.** It read ```sql JOIN shard_assets a ON a.asset_key = CONCAT('body/', b.body, '/a0') ``` which would have silently dropped exactly the creatures this phase adds. It now reads the row's own action: ```sql JOIN shard_assets a ON a.body = b.body AND a.family = 'body' AND a.asset_key = CONCAT('body/', b.body, '/a', COALESCE(a.action, 0)) ``` `COALESCE` because a row written before the column existed has NULL there, and **a NULL inside `CONCAT` makes the whole comparison NULL** — which would have taken every portrait off the site on upgrade, with the database perfectly correct and nothing in any log. It still matches at most one row per slug: a deeper key (`body/820/a23/f4`) does not equal the catalogue key, which is what §12.1 asked this join to keep being true. `action` is **stored** rather than parsed back out of the key, because the join needs it in SQL and re-deriving it there would put a second, weaker parser of §5's key scheme in the schema. No routes, no `MODULE_API` bump, no client changes. ## How it was tested `npm test` — **691 pass**, including two new bridge tests (a row's action survives the manifest walk; an overlay older than phase 6 reads as action 0) and one new model test (a fallback key is fetched, written as `uo-body-820-a23-<sha8>.png` and stored with its action). And the part no stub can check — the real `shardAssets.db` against a **real MariaDB 11.8**, in a throwaway database, using the module's own `schema.sql` statements and the **live shard's 1,095-row manifest**: ``` columns before the upgrade: asset_key, family, sha256, bytes, width, height, body, direction, file, imported_at, catalog after the upgrade: action tinyint(4) (and re-running the ALTER is a no-op, as boot requires) saved 1095 rows through saveAssets read back: { key: 'body/820/a23', action: 23, file: 'uo-body-820-a23-be5a991c.png' } artBySlug: { 'timber-wolf': 'uo-body-34-a0-…', human: 'uo-body-400-a0-…', horse: 'uo-body-820-a23-…', phoenix: 'uo-body-832-a1-…' } a pre-phase-6 NULL-action row: still joins with a deep frame key stored, horse -> uo-body-820-a23-be5a991c.png ``` ## Checklist - [x] I have read [CONTRIBUTING.md](CONTRIBUTING.md). - [x] The change builds and existing tests/checks pass locally. - [x] I have added or updated tests/docs where it makes sense. - [x] My commits are reasonably scoped with clear messages. ## AI-assisted contributions (required) - [x] AI tools were used. Tool(s): `Claude Code (Opus 5)`. I have reviewed and understand every change, and take responsibility for it. AI-authored commits are marked with a `Co-Authored-By` trailer. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
wtclaude added 1 commit 2026-09-14 06:11:20 +00:00
feat(assets): a creature's picture is whichever action has one (Phase 6)
Some checks failed
PR Checks / frozen-manifest (pull_request) Successful in 51s
PR Checks / client-build (pull_request) Successful in 8m9s
PR Checks / server-tests (pull_request) Failing after 14m27s
c6c51b190d
The shard's catalogue can now answer for 73 bodies it used to report absent —
they have no art at action 0 and real art at a later one, and their key says
which (`body/820/a23` is a horse). This side stores that action and stops
assuming `a0` anywhere.

The atlas join is the part that mattered. It read

  a.asset_key = CONCAT('body/', b.body, '/a0')

which would have silently dropped exactly the creatures this phase adds. It now
reads the row's own action, with COALESCE for rows written before the column
existed — a NULL inside CONCAT makes the whole comparison NULL, which would have
taken every portrait off the site on upgrade with the database perfectly correct
and nothing in any log. It still matches at most one row per slug: a deeper key
(`body/820/a23/f4`) does not equal the catalogue key.

Verified against a real MariaDB with the live shard's own 1,095-row manifest: the
ALTER applies to an installed-shape table and is idempotent, the horse joins to
its a23 picture, a pre-phase-6 NULL-action row keeps its portrait, and a stored
frame key does not become a second candidate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
whitlocktech merged commit 679762b643 into edge 2026-09-14 06:16:12 +00:00
whitlocktech deleted branch feat/asset-bridge-p6 2026-09-14 06:16:15 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

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