Marks M4 done in android/PLAN.md §9 build-progress and flips the top
status to "M0–M4 landed; M5 (design pass) next." Documents the account
self-service (/auth/me/account*), game-account linking, and text-only
own game-data screens shipped in RunicGateway/Android-app#9 — a pure
consumer of the existing bearer API, no backend/protocol change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
Records the M3 functional auth pass in docs/android/PLAN.md: native
username/password (+TOTP) login, EncryptedSharedPreferences token storage,
the refresh-on-401 authenticator, /auth/me resume re-validation, the
declarative access-level menu + My Account, and the Custom-Tab hand-offs for
register / forgot-password / SSO. Also records the decision to defer the
optional biometric app-lock to M6 (tokens are already encrypted at rest).
No backend/API change accompanied M3 — the app is a pure consumer of the
existing mobile bearer + /auth/me surface — so no Swagger regeneration.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
Mark M2 done in the build-progress section: the public shard widgets over
/public/shard/* (hub + champ/guild/governor/house boards) and the live SSE
feed with self-driven reconnect/backoff (Android-app#7). Update the status
line to "M0–M2 landed; M3 next". No API change — the app consumes the
existing public shard surface.
Co-Authored-By: Claude <noreply@anthropic.com>
Mark M0+M1 done in the build-progress header and next up M2. Summarize
the M1 functional pass (first-run connect, runtime base URL + host
interceptor, layered ApiResult stack, brand-seeded theming, public
content/wiki/pages/contact screens) and record the deliberate
hand-written-vs-openapi-generated API-client deviation from §2 and its
rationale (swagger-autogen schemas are meta-descriptive, not
codegen-clean). Tracks RunicGateway/Android-app#6.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
Update the PLAN.md status header now that the Android-app repo scaffold
(RunicGateway/Android-app#2) is in: Gradle+Compose+Hilt skeleton, version
catalog, and CI. Records M0 done and points at M1 (functional Kotlin pass).
Co-Authored-By: Claude <noreply@anthropic.com>
Counterpart to RunicGateway/website#77.
- BACKEND_DESIGN.md: /public table now documents the new GET /public/version
(DB-free identity/version), the version block on /public/status, and the brand
block on /public/settings (per-shard theming: name/accent/logo/hero/favicon).
- android/PLAN.md: mark §8 item 4 (version/health) and item 6 (branding) DONE and
update the prerequisite-progress summary — all v1 prerequisites are now done;
only push notifications (item 3) remains and is post-v1 (M7). App M0–M4 unblocked.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
Counterpart to RunicGateway/website#76 (role-agnostic /auth/me/* self surface).
- BACKEND_DESIGN.md: add the /auth/me/account* rows to the /auth API contract and
a note that the surface reuses account.controller behind requireAuth (any role),
so a client manages its own account without touching /admin.
- android/PLAN.md: mark §8 item 1 (role-agnostic self-service) DONE and update the
prerequisite-progress summary; version/health (item 4) and branding (item 6)
remain open, push (item 3) is post-v1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
§8 item 2 (password reset — the "build FIRST before app work" prerequisite)
shipped in RunicGateway/website#75 + docs#8. Mark it done, note the shipped
design (opaque token stored as a sha256 hash in password_resets, mirroring
user_invites, rather than a signed JWT), and record which §8 items remain open.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
Add /auth/password/forgot and /auth/password/reset/:token to the API
contract and the password_resets table to the schema section, matching the
website change (RunicGateway/website feat/password-reset). Notes the
no-enumeration behaviour, single-use hashed-token model, and that the Android
app hands off to the web reset page (PLAN.md §4.2).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
Record the two-pass build process: functional Kotlin first (M0-M4), then a Claude Design pass producing the front-end design that Claude Code implements as the final UI (new M5). Add §2.1 documenting the workflow and reconcile §9 milestones (insert M5 design pass, renumber Polish/Push/Play to M6-M8) plus the milestone cross-references in §8, §11, §12, and §13.
Co-Authored-By: Claude <noreply@anthropic.com>
Add docs/android/PLAN.md — the design contract for the RunicGateway/Android-app
repo (planning only, no app code yet).
Scope: native Kotlin + Jetpack Compose client of the website v1 API. Public
content + public shard widgets (incl. SSE), native username/password + TOTP
login, player self-service via a new role-agnostic /auth/me/* surface, and a
player's own shard/game data. Excludes every admin/management console (hero
editor, auth/provider admin, Discord bot, shard/uo-link ops).
Key decisions captured: stay on v1 (all additions are additive, no v2);
registration/invite/reset/SSO are website-handled hand-offs, not native screens;
password reset is built on the backend + web front end first; single shard per
install; minSdk 29; no telemetry and no offline cache in v1; text-only game data
(paperdoll is future); strings externalized from day one; push via a self-hosted
ntfy/UnifiedPush service with content-free tickles that keep the relay untrusted;
Gitea Actions build on ubuntu:latest with a signed-APK release.
Co-Authored-By: Claude <noreply@anthropic.com>
The "What each audience sees" table said the public "Staff online" list
is shown "with name + map location". Location is now privileged: the
server includes map/coords only for admin/moderator callers and strips
them from the payload for players and the public. Update the wording to
match (RunicGateway/website#72).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XmHdsbnLzDMAVQkAoTQSBe
Sync the extracted website README with the current repo README after the
org rename + BRAND_* branding work: UOMysticmoon → Runic Gateway as the
default identity (UOMysticmoon retained only as the example first instance),
UOM/link → RunicGateway/link, DB/cookie/TOTP defaults updated, and the new
Branding section added. Doc-local BACKEND_DESIGN.md links kept relative;
the .env.uomysticmoon.example link points at the website repo where it lives.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XmHdsbnLzDMAVQkAoTQSBe
- Repoint doc-to-doc references from the old docs/ prefix to the co-located
sibling filenames (docs live under link/ here now).
- Replace the personal ServUO checkout path (C:\Users\...\servuo) with a
<servuo> placeholder throughout.
Website news articles now land in the modern Town Cryer News gump
(TownCryerSystem.NewsEntries), separate from the scrolling-crier lines.
Overlay BridgeNews (new): news.add / news.remove insert/remove a
TownCryerNewsEntry directly in the public NewsEntries list (no stock edit),
tracking our own id->entry map so stock uo.com news is left intact. Title,
HTML body, image, and URL are all supported (the stock gumps already branch on
TextDefinition.Number, so string content renders). On add the article title is
also proclaimed via GlobalTownCrierEntryList (announce defaults on; set
announce:false to suppress). Config caps: NewsMaxTitleLength/BodyLength/
External, NewsAnnounceDurationSec.
Sidecar: POST /news (add/replace, id-correlated), DELETE /news/{id}; news table
stores each article as its news.add command; on shard server.hello the sidecar
replays the stored set with announce:false (the shard rebuilds NewsEntries each
boot and does not persist ours, so the website is the source of truth).
Docs: PROTOCOL_2 §16 (design + verified), INTEGRATION.md /news endpoints.
Verified live: sidecar cargo check clean; overlay compiles in the full ServUO
Scripts tree (0 errors); booted shard + sidecar and exercised add/replace/
remove/error paths and the reconnect replay end-to-end.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds §16: sync website news articles into the modern Town Cryer News gump
(TownCryerSystem.NewsEntries), distinct from the Protocol 1.0 scrolling-crier
lines (GlobalTownCrierEntryList). Grounded in the shard's Town Cryer source.
Key findings / decisions:
- NewsEntries is a public mutable List and TownCryerNewsEntry's ctor is public,
and the display gumps already branch on Title/Body .Number>0 (cliloc) vs
string (AddLabelCropped / AddHtml with HTML support). So website content
needs NO gump changes and NO stock patch — the overlay inserts/removes
directly and tracks its own entries, leaving stock uo.com news intact.
(Refines the pasted guidance, which proposed adding methods to the stock
TownCryerSystem.cs = a patch.)
- Ties the two surfaces together: full article -> news gump; crier "says" just
the title via the existing GlobalTownCrierEntryList path.
- news.add/news.remove verbs (id-correlated, idempotent), POST /news +
DELETE /news/{id}; website is source of truth, re-synced on shard reconnect
since NewsEntries isn't persisted across reboot.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Booted ServUO + the real sidecar (protocol 2, plugin connected) and exercised
every Protocol 2.0 endpoint end-to-end. PROTOCOL_2.md §15 records the results:
- Part A: account.create 200 + link; duplicate 409; per-IP cap enforced at the
shard's real AccountsPerIp=3 (4th from one IP -> 429); loopback IP -> 400
(fail-closed); unlink 200 then lookup 404.
- Part B: /houses (28, full data), /governors (9 cities), /guilds ([]),
/online (count 0, headless), /char titles block present.
Not exercised (needs a live UO client): presence.online with players,
region.enter, real-time guild.join, char.vitals. Also documents the
Scripts.dll boot-recompile lock quirk (build offline with the server stopped).
World save left untouched; test accounts did not persist.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Overlay BridgeProfile: char.profile gains a titles block (selected index,
fameKarma, skill, and the raw reward-title list) read from PlayerMobile's
public title accessors. No new stream, no sidecar change — it rides the
existing char.profile served by GET /char. Reward entries may be a cliloc
number as a string or a literal; resolve numeric ones website-side like item
names.
Docs: INTEGRATION.md char.profile titles field; PROTOCOL_2 ph.4 built. Part B
phase 5 (Factions/VvV) remains deferred by owner decision.
Verified: overlay compiles in the full ServUO Scripts tree (0 errors, 0
warnings). Live run pending.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Overlay BridgeHousing (new): a diff sweep over BaseHouse.AllHouses ->
house.update / house.remove (owner, region, location, decay level, co-owners,
friends, placement price), complementing the existing house.decay transition
feed. HousingSweepSeconds (300s); wired into [bridge reload|sweepnow|status.
Stock ServUO has no "for sale" flag, so this is an owner->houses registry;
price is the placement value, not a listing.
Sidecar: houses board table with upsert/delete/all; main routes house.update/
remove into it; GET /houses served from the store.
Docs: INTEGRATION.md house.* events + /houses endpoint; PROTOCOL_2 ph.3 built.
Verified: sidecar cargo check clean; overlay compiles in the full ServUO
Scripts tree (0 errors, 0 warnings). Live run pending.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Overlay BridgePresence (new):
- presence.online sweep over online PlayerMobiles: total plus per-facet and
per-region counts, emitted only when the population changes.
- region.enter real-time from EventSink.OnEnterRegion (player-filtered), the
cheap location signal PLAN.md prefers over Movement.
- PresenceSweepSeconds (30s); wired into [bridge reload|sweepnow|status.
Sidecar:
- GET /online serves the latest presence.online snapshot from the event store
(survives restart); population time series via /history?kind=presence.online.
Docs: INTEGRATION.md presence events + /online endpoint; PROTOCOL_2 ph.2 built.
Verified: sidecar cargo check clean; overlay compiles in the full ServUO
Scripts tree (0 errors, 0 warnings). Live run pending.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the first Part B streams from docs/PROTOCOL_2.md: guild rosters and
town governors ("mayors"), both outbound diff-board sweeps mirroring the
existing champ board.
Overlay:
- BridgeSocial (new): guild sweep+diff over BaseGuild.List -> guild.update /
guild.remove (full-state upsert; disband detected via Disbanded), plus a
real-time guild.join from EventSink.JoinGuild. (EventSink.CreateGuild is only
the load-time factory, so creation is derived sidecar-side from a first-seen
id, as champs do.)
- BridgeGovernance (new): city sweep over CityLoyaltySystem.Cities -> city.update
(governor / governor-elect / election phase), gated on CityLoyaltySystem.Enabled.
- BridgeJson.Actor: shared serial/name/acct/webId/player writer used by both.
- BridgeConfig: GuildSweepSeconds (60s), CitySweepSeconds (300s).
- BridgeBoot: both wired into [bridge reload|sweepnow|status.
Sidecar:
- store: guilds + governors board tables with upsert/delete/all.
- main: route guild.update/remove and city.update into the boards.
- web: GET /guilds, GET /governors served from the store (snapshot-companion
rule, so a fresh page or a restarted sidecar hydrates without the shard).
Docs: INTEGRATION.md event catalog (guild.*, city.update) + board endpoints;
PROTOCOL_2.md Part B phase 1 marked built.
Verified: sidecar cargo check clean; overlay compiles in the full ServUO
Scripts tree (0 errors, 0 warnings). Live end-to-end run still pending.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the account-provisioning plane from docs/PROTOCOL_2.md Part A: the
website can create game accounts and unlink them, gated by a shard-wide
signup mode. The existing [link flow is unchanged.
Overlay:
- BridgeConfig: SignupMode (website|game|hybrid, default hybrid; unrecognized
falls back to game), AccountCreateEnabled (mode-following default),
RequireIpForCreate, name/password caps, and a boot warning when the core
Accounts.AutoCreateAccounts setting contradicts the mode.
- BridgeAccounts (new): account.create (mode gate, actor required, char-safety
mirrored from AccountHandler, collision check, per-IP cap via CanCreate/
LogAccess with fail-closed missing/loopback IP, create + WebsiteUserId link,
account.audit; password never logged or echoed) and account.unlink (Owner
floor via BridgeAdmin.Protected, clears the tag).
- BridgeAccountLink: in-game [unlink command, emits account.unlinked.
- BridgeAdmin: Protected / ResolveTargetAccount promoted to public for reuse.
Sidecar:
- POST /accounts/create, DELETE /link/:account, respond_account status mapping
(409 collision / 429 ip cap / 403 disabled|protected / 404 not-linked / 400).
- store.record_unlink drops the mirrored link row.
- PROTOCOL_VERSION -> 2 (outbound events additive; new endpoints need v2).
Docs: INTEGRATION.md protocol bump, account.* events, endpoints, 409/429;
PROTOCOL_2.md Part A marked built.
Verified: sidecar cargo check clean; overlay compiles in the full ServUO
Scripts tree (0 errors, 0 warnings). Live end-to-end run still pending.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Champion spawns have no ServUO EventSink, so add a fourth polled stream
(BridgeChamps) modeled on BridgeSweeps: enumerate every spawn each tick,
fold to a small record, and emit champ.update only on change. No core
patch — every field used is public.
Covers all three families via a `category` field:
- champion: ChampionSpawn (type/level/kills/boss/cooldown ETA)
- mini: MiniChamp (type/level; auto-restarts, no kill counter)
- sea: BaseSeaChampion (a High Seas world-boss mobile, alive only
while summoned; removed via champ.remove when slain)
Status folds to active/cooldown/dormant. A (re)connection clears the diff
cache so the next sweep re-emits the full board, rebuilding a sidecar that
restarted on its own. Transient entries leave via champ.remove.
Sidecar: a `champs` current-state table (one row per serial) fed by
champ.update (upsert) and champ.remove (delete), exposed at GET /champs as
the live board. New ChampSweepSeconds config (default 10s), wired into
[bridge reload/sweepnow/status. Documented in docs/INTEGRATION.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114TpmrNW4wNXsHq5CR72jQ