fix(shard): decode the atlas places objects and render them #32

Merged
whitlocktech merged 1 commits from fix/atlas-places-decode into feat/protocol-3-visibility 2026-08-01 06:03:55 +00:00
Member

What & why

Found by running the M11 on-device five-rung walk against a live shard (ServUO + sidecar + website + AVD) — the gate the edgemain cutover is waiting on.

The blocker: AtlasCreatureDto.places was typed List<String>, but the server sends {facet, label, spawners, maxAlive} objects. The detail route answers 200 with ~49 KB, kotlinx throws on decode, and the screen renders "Something went wrong on the server" — so the whole Atlas creature page was dead, and the message blamed a server that was fine. Nullable-with-defaults protects against a missing field, never a wrong element type.

Because this PR targets feat/protocol-3-visibility, the fix lands in #30 rather than letting a broken screen reach main first.

Changes

  • AtlasPlaceDto added and places retyped — the actual fix.
  • art field added so a decode can't depend on the server not sending it. Neither client renders creature art today; not changing that here, since doing so app-only would break parity the other way.
  • "Where it spawns" section. places was never rendered at all, so the aggregate the atlas exists to give — "Shrines, Isamu-Jima, Yew", resolved server-side by point-in-rect — was missing from the app while the web page leads with it. Placed above the individual spawners, matching web's ordering.
  • Plural for the spawner count, once single-spawner places appeared in bulk on screen ("1 spawners" on most rows).
  • Placeholder row on an unscored leaderboard — the instance name with an em dash where a score goes, deliberately not shaped like a real entry (no rank, no medal, no bar), because a placeholder that looked like a standing would be a fabricated one. The first real entry replaces it. Presentation only; the API still sends an empty top.

The root cause worth fixing, not just the symptom

ShardContentDtoTest is the first decode test any of the four Protocol 3.0 DTOs has had. The fakes in data/api/fake/ construct DTOs directly in Kotlin, so no test in the suite could ever see a wire mismatch — PLAN.md §9 already required "DTO decode for each new shape" and it hadn't been done. The new tests feed payloads captured from a live server, and cover the projected/empty shapes the visibility framework produces as well.

How it was tested

  • ./gradlew testDebugUnitTest lintDebug assembleDebug -Pksp.incremental=false344 tests, 0 failures, lint clean (was 336).
  • Rebuilt, reinstalled, and re-walked all four Protocol 3.0 screens on the emulator against a live shard: the Atlas detail now renders SeaSerpent → Wilderness · Felucca · 91 spawners · up to 1194 at once, and Rules / Market / Leaderboards re-verified unchanged.
  • Market visibility re-checked on-device at the player rung with market.ownerName gated to staff: the "Kept by …" line correctly disappears while the location stays.

Docs for the places shape and the decode-test rule: RunicGateway/docs#76.

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)

  • No AI tools were used to produce this contribution.
  • 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 / Assisted-By trailer.

License

  • I agree that my contribution is licensed under this project's license (GNU GPL v3.0 or later), and I have the right to contribute it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01U7CBg11prhLimL9iHSX1bP

## What & why Found by running the M11 on-device five-rung walk against a **live** shard (ServUO + sidecar + website + AVD) — the gate the `edge`→`main` cutover is waiting on. **The blocker:** `AtlasCreatureDto.places` was typed `List<String>`, but the server sends `{facet, label, spawners, maxAlive}` **objects**. The detail route answers `200` with ~49 KB, kotlinx throws on decode, and the screen renders *"Something went wrong on the server"* — so the whole Atlas creature page was dead, and the message blamed a server that was fine. Nullable-with-defaults protects against a *missing* field, never a *wrong element type*. Because this PR targets `feat/protocol-3-visibility`, the fix lands in #30 rather than letting a broken screen reach `main` first. ### Changes - **`AtlasPlaceDto`** added and `places` retyped — the actual fix. - **`art` field added** so a decode can't depend on the server not sending it. Neither client renders creature art today; not changing that here, since doing so app-only would break parity the other way. - **"Where it spawns" section.** `places` was never rendered at all, so the aggregate the atlas exists to give — *"Shrines, Isamu-Jima, Yew"*, resolved server-side by point-in-rect — was missing from the app while the web page leads with it. Placed above the individual spawners, matching web's ordering. - **Plural for the spawner count**, once single-spawner places appeared in bulk on screen ("1 spawners" on most rows). - **Placeholder row on an unscored leaderboard** — the instance name with an em dash where a score goes, deliberately *not* shaped like a real entry (no rank, no medal, no bar), because a placeholder that looked like a standing would be a fabricated one. The first real entry replaces it. Presentation only; the API still sends an empty `top`. ### The root cause worth fixing, not just the symptom **`ShardContentDtoTest` is the first decode test any of the four Protocol 3.0 DTOs has had.** The fakes in `data/api/fake/` construct DTOs directly in Kotlin, so no test in the suite could ever see a wire mismatch — `PLAN.md` §9 already required *"DTO decode for each new shape"* and it hadn't been done. The new tests feed payloads captured from a live server, and cover the projected/empty shapes the visibility framework produces as well. ## How it was tested - `./gradlew testDebugUnitTest lintDebug assembleDebug -Pksp.incremental=false` — **344 tests, 0 failures**, lint clean (was 336). - Rebuilt, reinstalled, and re-walked all four Protocol 3.0 screens on the emulator against a live shard: the Atlas detail now renders *SeaSerpent → Wilderness · Felucca · 91 spawners · up to 1194 at once*, and Rules / Market / Leaderboards re-verified unchanged. - Market visibility re-checked on-device at the `player` rung with `market.ownerName` gated to `staff`: the "Kept by …" line correctly disappears while the location stays. Docs for the `places` shape and the decode-test rule: RunicGateway/docs#76. ## 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) - [ ] No AI tools were used to produce this contribution. - [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` / `Assisted-By` trailer. ## License - [x] I agree that my contribution is licensed under this project's license (**GNU GPL v3.0 or later**), and I have the right to contribute it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01U7CBg11prhLimL9iHSX1bP
wtclaude added 1 commit 2026-08-01 05:59:53 +00:00
`AtlasCreatureDto.places` was typed `List<String>` while the server sends
`{facet, label, spawners, maxAlive}` objects. The detail route answers 200 with
~49 KB, kotlinx throws on decode, and the screen renders "Something went wrong
on the server" — so the whole Atlas creature page was dead, and the error
blamed a server that was fine. Nullable-with-defaults protects against a
missing field, never a wrong element type.

Adds AtlasPlaceDto, plus the `art` field the server also sends, so a decode
cannot depend on that staying absent (neither client renders art yet).

`places` was never rendered either, so the aggregate the atlas exists to give —
"Shrines, Isamu-Jima, Yew", resolved server-side by point-in-rect — was missing
from the app while the web page led with it. Adds a "Where it spawns" section
above the individual spawners, matching web's ordering, and a plural for the
spawner count now that single-spawner places are on screen in bulk.

Adds ShardContentDtoTest — the first decode test any of the four Protocol 3.0
DTOs has had, fed payloads captured from a live server. That absence is the
root cause: the fakes in data/api/fake/ construct DTOs in Kotlin, so no test in
the suite could see a wire mismatch, even though PLAN.md §9 already required
"DTO decode for each new shape".

Also renders a placeholder row on an unscored leaderboard (the instance name,
em dash where a score goes) rather than a blank card — deliberately not shaped
like a real entry, since a placeholder that looked like a standing would be a
fabricated one.

Found by the on-device five-rung walk against a live shard; all four screens
re-verified on the emulator afterwards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U7CBg11prhLimL9iHSX1bP
whitlocktech merged commit 12b2172731 into feat/protocol-3-visibility 2026-08-01 06:03:55 +00:00
whitlocktech deleted branch fix/atlas-places-decode 2026-08-01 06:03:56 +00:00
Sign in to join this conversation.
No description provided.