feat(rust): a player's own Rust account on the phone — M15 (module-rust phase 8, Android leg B) #48

Merged
whitlocktech merged 2 commits from feat/rust-p8-android-b into edge 2026-09-23 01:52:13 +00:00
Member

R10's leg B: identity (module-rust phase 6) and the half of site-owned permissions (phase 7) a player is allowed to see. Design of record: docs/modules/rust/PLAN.md §22 (D39–D41); docs/android/PLAN.md gains M15.

Every route existed and answered before a line of Kotlin was written except one — GET /player/rust/permissions, added for this screen in Module-Rust#10 and rendered on the website there too.

It is CharactersScreen for a different game, deliberately. One drawer row under the player group, code card at the top of the screen it opens — the shape UO has had since M4. A tab under one Rust server was rejected (a link is fleet-wide: one Steam account is one person on every server, while stats are per server and per wipe), and a section inside core's Account screen was rejected (the app has no slot mechanism, so the module's data would be hard-wired into a core screen).

Gated on rust, not on identity. D16's rule stands: a capability answers is the module there, and core flattens every module's capabilities into one list, so a surface word is not something a row may hang on. MenuAccess.PLAYER is isPlayer || isStaff, which is right — /player/rust/* is requireAuth with no role above it, and staff play too.

Two reads, neither blocking the other. An entitlement is authored against the website account, so it exists before a Steam id does; the person who has just been given something and has not linked yet is exactly the one who needs both halves at once.

The four refusals stay four pieces of advice. 400 a spent code, 409 a Steam account another website account holds (/unlink in game releases it — it is never moved silently), 429 the limiter, 503 a server that could not be reached — where the code is still good, so it may not say "get a new one".

The app does no scope arithmetic: * never reaches a screen. Each entry arrives with its servers resolved and marked, because a second implementation of that rule is a second thing to keep true.

Verified

657 tests, 0 failures, lintDebug, assembleDebug, and an emulator walk against a core with the module installed and a live Oxide rig behind it — the first Rust leg whose backend was a real game server rather than a stand-in.

Walked: the row absent signed-out and absent on a UO site, present for a signed-in player; both reads; a code the live plugin genuinely refused, rendering beside the button; a rank marked has it and a grant marked waiting, matching what the pushed ledger actually said; and the release.

The walk found one thing 657 green tests did not — the row said who, and not when or where. The website's row has always read "linked just now on rust-oxide"; which server minted the code is where a support conversation starts. Fixed in the second commit.

The acceptance line needs a person in game: a /link code reaches a player and nobody else. The walk is written down in docs/rust-link/PLAYER_WALK.md.

AI disclosure

Written with Claude Code (Opus 5).

🤖 Generated with Claude Code

https://claude.ai/code/session_01PMH6bw1jXMgbyF3ZWGEzSM

R10's leg B: identity (module-rust phase 6) and the half of site-owned permissions (phase 7) a player is allowed to see. Design of record: `docs/modules/rust/PLAN.md` §22 (D39–D41); `docs/android/PLAN.md` gains M15. Every route existed and answered before a line of Kotlin was written except one — `GET /player/rust/permissions`, added for this screen in Module-Rust#10 and rendered on the website there too. **It is `CharactersScreen` for a different game, deliberately.** One drawer row under the player group, code card at the top of the screen it opens — the shape UO has had since M4. A tab under one Rust server was rejected (a link is fleet-wide: one Steam account is one person on every server, while stats are per server and per wipe), and a section inside core's Account screen was rejected (the app has no slot mechanism, so the module's data would be hard-wired into a core screen). **Gated on `rust`, not on `identity`.** D16's rule stands: a capability answers *is the module there*, and core flattens every module's capabilities into one list, so a surface word is not something a row may hang on. `MenuAccess.PLAYER` is `isPlayer || isStaff`, which is right — `/player/rust/*` is `requireAuth` with no role above it, and staff play too. **Two reads, neither blocking the other.** An entitlement is authored against the website account, so it exists before a Steam id does; the person who has just been given something and has not linked yet is exactly the one who needs both halves at once. **The four refusals stay four pieces of advice.** 400 a spent code, 409 a Steam account another website account holds (`/unlink` in game releases it — it is never moved silently), 429 the limiter, 503 a server that could not be reached — where the code is still good, so it may not say "get a new one". The app does no scope arithmetic: `*` never reaches a screen. Each entry arrives with its servers resolved and marked, because a second implementation of that rule is a second thing to keep true. ### Verified **657 tests, 0 failures**, `lintDebug`, `assembleDebug`, and an emulator walk against a core with the module installed and a **live Oxide rig** behind it — the first Rust leg whose backend was a real game server rather than a stand-in. Walked: the row absent signed-out and absent on a UO site, present for a signed-in player; both reads; a code the live plugin genuinely refused, rendering **beside the button**; a rank marked *has it* and a grant marked *waiting*, matching what the pushed ledger actually said; and the release. **The walk found one thing 657 green tests did not** — the row said who, and not when or where. The website's row has always read "linked just now on rust-oxide"; which server minted the code is where a support conversation starts. Fixed in the second commit. **The acceptance line needs a person in game**: a `/link` code reaches a player and nobody else. The walk is written down in `docs/rust-link/PLAYER_WALK.md`. ### AI disclosure Written with Claude Code (Opus 5). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01PMH6bw1jXMgbyF3ZWGEzSM
wtclaude added 2 commits 2026-09-23 01:36:53 +00:00
R10's leg B: identity, and the half of R2 a player is allowed to see. Every
route this calls existed and answered before a line of Kotlin was written,
except the entitlement read, which is this phase's one website change.

**One drawer row under the player group, shaped like `CharactersScreen` one
game along**: the code card first, then what the code got them. A link is
fleet-wide, so it is not a tab under one server — a Steam account is one
person wherever they play, while stats are per server and per wipe.

**Gated on `rust`, not on `identity`.** The module declares a surface word
per feature, and D16's rule is that a capability answers one question — *is
the module there*. `MenuAccess.PLAYER` is `isPlayer || isStaff`, which is
right here: `/player/rust/*` is `requireAuth` with no role above it, and
staff play the game too.

**Two reads, neither blocking the other.** An entitlement is authored
against the website account, so it exists before a Steam id does — the
person who has just been given something and has not linked yet is exactly
the one who needs both halves at once, and a failure on either leaves the
other standing.

**The four refusals stay four pieces of advice.** 400 is a spent code, 409
is a Steam account another account holds (`/unlink` in game releases it),
429 is the limiter, and 503 is a server that could not be reached — where
the code is still good, so it may not say "get a new one". A player told
otherwise goes back to the same unreachable server for another code.

The app does no scope arithmetic: `*` never reaches a screen. Each entry
arrives with its servers already resolved and each marked, because a second
implementation of `inScope` is a second thing to keep true.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMH6bw1jXMgbyF3ZWGEzSM
fix(rust): say when and where an account was linked, as the website's row does
Some checks failed
PR Checks / android-build (pull_request) Failing after 22m6s
a079bd481e
Found by the emulator walk, not by the suite: the app's row carried the
name and the Steam id and stopped there, while the website's says "linked
just now on rust-oxide". Neither fact is part of the identity — a link is
fleet-wide — but which server minted the code is where a support
conversation starts, and the module's own schema comment says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMH6bw1jXMgbyF3ZWGEzSM
whitlocktech merged commit 59d955a11d into edge 2026-09-23 01:52:13 +00:00
whitlocktech deleted branch feat/rust-p8-android-b 2026-09-23 01:52:14 +00:00
Sign in to join this conversation.
No description provided.