feat(web): the world verbs on the wire (protocol 7, Phase 12a) #38

Merged
whitlocktech merged 1 commits from feature/events-p12a-world-verbs into edge 2026-09-07 06:58:23 +00:00
Member

Events Phase 12a, sidecar half. One of four: servuo-plugins (servuo-plugins#23), link, Module-uo (#—), docs (docs#221) — all onto edge. Spec of record: docs/link/v7.md.

Routes

POST /world places count of what — creature, boss, npc, gate, decor
GET /world/:runId what the run still owns
POST /world/:runId/despawn gives it back; no serials means everything

One route family for five author-facing verbs, because each of them ends in "an object exists and this run owns it". The differences between a boss's multipliers, an oracle's lines and a gate's destination are fields on one command rather than five commands, so there is one ledger shape and one teardown path instead of five near-identical ones across three repos.

PROTOCOL_VERSION → 7. The overlay's overlay.toml is bumped in the same window. 12b amends 7 in place rather than bumping again, so an overlay and a sidecar both declaring 7 are interchangeable only within one side of that merge — tolerable for the same single reason 6 was, and no other: nothing is released from edge.

Two shapes worth the review

world.owned is a GET, unlike participation.snapshot. It carries no idempotency key and the shard answers it in one pass, pruning rows whose object the world has already lost as it walks. A read that cannot legitimately be refused as a repeat in flight has no reason to be a POST.

A run the shard has no rows for answers with an empty hand, not a 404, and the distinction is load-bearing for the website's reconcile(). "This run owns nothing" and "I have never heard of this run" are the same fact once the registry is the only record of ownership — and they stay the same fact across a restart, because the registry is written by the same world save as the objects it describes. A 404 here would make the website treat a run that legitimately owns nothing as a shard it could not reach.

Tests

Two new, both pinning what the world verbs depend on from respond_event rather than trusting that its reason-sniffing keeps covering a kind it predates:

  • a ceiling refusal is a 400, and permanently so — retrying "you asked for 80 creatures and this shard places 30" gets the same answer forever, so a module that classified it as transient would put a run in a loop against a limit that will never move. The event gate being off is still a 403.
  • an empty owned list is a 200, with the reasoning above written where someone would otherwise reach for a 404.

cargo fmt --check, cargo clippy --all-targets -- -D warnings and cargo test all clean: 49 passed (was 47).

Also exercised end to end against the real local ServUO 57.4 world, driving this binary's REST — see servuo-plugins#23 for the walk.

🤖 Generated with Claude Code

https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4

Events Phase 12a, sidecar half. One of four: `servuo-plugins` (servuo-plugins#23), `link`, `Module-uo` (#—), `docs` (docs#221) — all onto `edge`. Spec of record: [`docs/link/v7.md`](https://gitea.whitlocktech.com/RunicGateway/docs/pulls/221). ## Routes | | | |---|---| | `POST /world` | places `count` of `what` — creature, boss, npc, gate, decor | | `GET /world/:runId` | what the run still owns | | `POST /world/:runId/despawn` | gives it back; no `serials` means everything | **One route family for five author-facing verbs**, because each of them ends in *"an object exists and this run owns it"*. The differences between a boss's multipliers, an oracle's lines and a gate's destination are fields on one command rather than five commands, so there is one ledger shape and one teardown path instead of five near-identical ones across three repos. **`PROTOCOL_VERSION` → 7.** The overlay's `overlay.toml` is bumped in the same window. 12b amends 7 **in place** rather than bumping again, so an overlay and a sidecar both declaring 7 are interchangeable only within one side of that merge — tolerable for the same single reason 6 was, and no other: nothing is released from `edge`. ## Two shapes worth the review **`world.owned` is a GET**, unlike `participation.snapshot`. It carries no idempotency key and the shard answers it in one pass, pruning rows whose object the world has already lost as it walks. A read that cannot legitimately be refused as a repeat in flight has no reason to be a POST. **A run the shard has no rows for answers with an empty hand, not a 404**, and the distinction is load-bearing for the website's `reconcile()`. *"This run owns nothing"* and *"I have never heard of this run"* are the same fact once the registry is the only record of ownership — and they stay the same fact across a restart, because the registry is written by the same world save as the objects it describes. A 404 here would make the website treat a run that legitimately owns nothing as a shard it could not reach. ## Tests Two new, both pinning what the world verbs depend on from `respond_event` rather than trusting that its reason-sniffing keeps covering a kind it predates: - **a ceiling refusal is a 400**, and permanently so — retrying *"you asked for 80 creatures and this shard places 30"* gets the same answer forever, so a module that classified it as transient would put a run in a loop against a limit that will never move. The event gate being off is still a 403. - **an empty owned list is a 200**, with the reasoning above written where someone would otherwise reach for a 404. `cargo fmt --check`, `cargo clippy --all-targets -- -D warnings` and `cargo test` all clean: **49 passed** (was 47). Also exercised end to end against the real local ServUO 57.4 world, driving this binary's REST — see servuo-plugins#23 for the walk. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
wtclaude added 1 commit 2026-09-07 06:54:20 +00:00
feat(web): the world verbs on the wire (protocol 7, Phase 12a)
Some checks failed
PR Checks / rust-gates (pull_request) Failing after 9s
5d909ca0a3
`POST /world` places, `GET /world/:runId` says what a run still owns, and
`POST /world/:runId/despawn` gives it back. One route family for five
author-facing verbs, because each of them ends in "an object exists and this run
owns it" -- the differences between a boss's multipliers, an oracle's lines and
a gate's destination are fields on one command, not five commands.

`PROTOCOL_VERSION` -> 7. The overlay's `overlay.toml` is bumped in the same
window; 12b amends 7 in place rather than bumping again, so an overlay and a
sidecar both declaring 7 are interchangeable only within one side of that merge
-- tolerable for the same single reason 6 was, and no other: nothing is released
from `edge`.

`world.owned` is a GET, unlike `participation.snapshot`: it carries no
idempotency key and the shard answers it in one pass. A run the shard has no
rows for answers with an EMPTY hand rather than a 404, and the distinction is
load-bearing for reconcile -- "owns nothing" and "never heard of it" are the
same fact once the registry is the only record of ownership, and they stay the
same fact across a restart, because the registry is written by the same world
save as the objects it describes.

Two tests pin what the world verbs depend on from `respond_event`, rather than
trusting that its reason-sniffing keeps covering a kind it predates: a ceiling
refusal is a 400 (permanent -- retrying "you asked for 80 and this shard places
30" gets the same answer forever), the event gate being off is still a 403, and
an empty owned list is a 200.

Refs: docs/link/v7.md, docs/website/EVENTS_PLAN.md Phase 12a

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
whitlocktech merged commit 5cdd80e694 into edge 2026-09-07 06:58:23 +00:00
whitlocktech deleted branch feature/events-p12a-world-verbs 2026-09-07 06:58:24 +00:00
Sign in to join this conversation.
No description provided.