docs(rust): phase 15 as built — the map and events on a phone (PLAN.md §31.5–§31.7)
- PLAN.md §31.5: what was built in website and Android-app, where the build departed from §31.2, and the seven-step emulator walk. - §31.6: what the walk found: the zoom lost twice (fixed), the tally a stand-in cannot feed, the off-grid row the frame clamps, the "wiped Dec 31, 1" header, the card that outlives its marker. - §31.7: what is not proven (a real player, a counted tally, a UO site). - Phase row 15 and the Android plan's M17 marked built and walked. - EVENTS.md § API surface: the public calendar's run entries carry runId (D125). BACKEND_DESIGN defers event routes to that table. - PLAYER_WALK.md: the map and events on a phone, for the person-parts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -1007,7 +1007,7 @@ Each phase ends with its findings written down, as every workstream here does.
|
||||
| 13a | **Events: the world verbs** (§9, R17, **split by D80**). ✅ **Built and walked 2026-09-24 on both rigs — plan in §28 (D80–D95), as built and findings in §28.5–28.8 (D96–D97).** A restart mid-run found that the reconcile asked a world that had not loaded, and the plugin pruned live crates on the empty answer; fixed with `worldReady` (§28.7). The owner fix was walked before and after. The placing verb became two (D97), because core infers cap boxes from examples. The phase-7 owner fix (D85); `rust.zone.open` and `rust.prefab.place` (crates and NPCs, D88) at a monument or raw coordinates (split into `rust.crate.place` and `rust.npc.place` by D97); the monument and prefab option sources; the plugin's ownership registry, keyed by the idempotency key; and **`reconcile()` with the boot-id and wipe watch calling `ctx.events.reconcile()`** (§11.1). Protocol 9 | all 3 + docs | A wipe reconciles the ledger instead of stranding it, and each world verb's teardown is observed in the game |
|
||||
| 13b | **Events: the rewards** (§9, R3, R16). ✅ **Built and walked 2026-09-24 on both rigs, without a player — plan in §29 (D98–D105), as built and findings in §29.6–29.9 (D106–D108).** Real core refused the module over one camelCase option-source id, which 310 green tests had not caught (§29.8). The steps that need a person are the rewards walk in `PLAYER_WALK.md`. The participation tally kept by the game (D81–D83), `rust.kit.entitle` through the site mirror's per-run rows (D84) with the kit option source flagging kits with no permission gate, and `rust.announce` with the announce leg (D90). Plan of record in §29, written before its code. Protocol 10 | all 3 + docs | A reward granted at 03:00 is waiting in the kit menu when the player next logs in, and a revert withdraws it |
|
||||
| 14 | **The live map** (R9). ✅ **Built and walked 2026-09-25 (§30.7), protocol 11, D119–D120 (§30.6); the person-parts are the map walk in `PLAYER_WALK.md`.** 📐 **Plan of record in §30 (D109–D118), 2026-09-24.** A rig probe found that the `.map` file is terrain, not a picture, and that Rust+ already caches the rendered JPEG; the game's own render stalls the main thread 8.5 s (§30.0). Protocol 11. *As first written:* the map image over the bridge — request/reply, two-stage, one in flight, its own derivation version, no import on boot — plus the live layers and a per-layer public/players/admin switch built on **our own** visibility layer (§11.2 — `shardVisibility` is `module-uo`'s, not core's) | all 3 + docs | The map renders for the current wipe, and a player layer is invisible until an operator deliberately opens it |
|
||||
| 15 | **Android leg D** (R10). Map and events. 📐 **Plan of record in §31 (D121–D125), 2026-09-25.** The map is a fifth tab drawn natively in Compose over the module's projected answer, so the app has no gate of its own to get wrong. "Events" is a site-event marker opening the app's own event page, plus M13 walked against a Rust run. That needs one field on core's public calendar (D125) | Android-app + website + docs | The map renders on a phone with the same layer gates |
|
||||
| 15 | **Android leg D** (R10). Map and events. ✅ **Built and walked on an emulator 2026-09-25 (§31.5); the counted tally and a real player are in `PLAYER_WALK.md`.** 📐 **Plan of record in §31 (D121–D125), 2026-09-25.** The map is a fifth tab drawn natively in Compose over the module's projected answer, so the app has no gate of its own to get wrong. "Events" is a site-event marker opening the app's own event page, plus M13 walked against a Rust run. That needs one field on core's public calendar (D125) | Android-app + website + docs | The map renders on a phone with the same layer gates |
|
||||
| 16 | **Discord slash commands** (R11). A small read-only set, every refusal deferred ephemeral | Module-Rust + docs | A refusal does not go public in the channel |
|
||||
| 17 | **Optional mod integrations** (R15). **BetterChat** first — leaderboard titles through `API_RegisterThirdPartyTitle`, a pull with no drift — then the uMod **Clans** adapter (alliances and clan chat, beside the provider rather than under it, R5), then others as they prove useful | Rust-Plugins + Module-Rust + docs | A server missing every optional mod still runs the module, Teams included |
|
||||
| 18 | **The installer** (R4) **and the Pterodactyl egg** (R20) — the two halves of "how the shard side reaches an operator", built together so one story is not told twice. `--game servuo\|rust`, the bundle payload as a variant, a **framework** prerequisite check in `doctor` (which one, not whether Oxide — R19), the protocol pairing refusal carried over; the egg derived from "Rust Autowipe" with the sidecar inside the game container, a second allocation for `[web].bind`, **the sidecar configured from egg variables** (R22), the sidecar store held out of `REMOVE_FILES`, and its install script fetching the same pinned pair the installer resolves | installer + Rust-Link + docs | An operator sets a Rust server up with the released binary and nothing hand-copied; **and** a second operator imports the egg, deploys, and reaches the same place — on either framework |
|
||||
@@ -5580,6 +5580,125 @@ None of these was asked. They are written down so that review can overrule them
|
||||
- **The admin Map card is not in the app.** It is operator configuration, the same exclusion as
|
||||
M14 made for the rest of the Rust admin surface.
|
||||
|
||||
### 31.5 As built, 2026-09-25
|
||||
|
||||
[Android-app][aa] on `feat/rust-phase-15-map`, into `edge`, and [website][web] on
|
||||
`feat/public-calendar-run-id`, into `main`. **Module-Rust did not change, and `MODULE_API` did not
|
||||
move.** None of §31.4's readings was overruled, and each is built as written.
|
||||
|
||||
| Repo | What |
|
||||
|---|---|
|
||||
| website | `runId` on a public calendar entry whose `kind` is `run` (`publicRunEntry`), the `PublicEventEntry` schema and the regenerated spec. The exact-keys test now lists the field. A new test pins that a run entry's id is its run's, that the event page publishes the same id, and that a projection has none (D125) |
|
||||
| Android-app | `RustTab.MAP` and `Capability.MAP`; `map` and `map/live` in `RustApi` with every layer nullable; `MapFrame` (§30.3 ported, with the web's fifteen probed grid cases) and `MapTransform` (fit, pinch, pan and resize as pure functions); `MapMarkers` (one flat list for drawing, tapping and the card); `EventRunResolver`; `RustMapViewModel` (keyed on the signed-in account); `RustMapPanel` (the canvas, the legend and the card); `runId` on `EventCalendarEntryDto` |
|
||||
|
||||
**Where the build departed from §31.2, and why:**
|
||||
|
||||
- **The run id is matched as text.** The plugin holds a run's id as a string and `map/live` sends
|
||||
`"41"`, while core's shapes send `41`. §31.1 said the item carries "core's run id", which is true
|
||||
of its value and not of its type. The resolver keys on `runId.toString()`, and a test decodes both.
|
||||
- **A viewer sent nothing that moves is not polled at all**, as the web does. §31.2 said "every
|
||||
`pollMs` … while RESUMED and on the Map tab"; with no moving layer there is nothing to ask for.
|
||||
- **An answer asked under one account is dropped when it lands under another.** Keying the state on
|
||||
the account (§31.2) clears what is drawn. A request already in flight when the account changes
|
||||
still answers, so the view model counts account changes and drops an answer from an earlier one.
|
||||
- **The reader's zoom survives leaving the screen and a change in the box's size** (§31.6).
|
||||
- **The tap card of a site-event marker always says "Site event"** under its title, linked or not.
|
||||
The title is a zone's name when the layer carried one. The link is the only difference between a
|
||||
listed run's marker and a rehearsal's.
|
||||
|
||||
**Walked on an emulator (`s22_ultra`, API 36) against real core** on the D125 branch, which is
|
||||
`main` plus the one field, with the `rustp12` database and this module as phase 14 shipped it. The
|
||||
Oxide rig ran its protocol-11 sidecar. There is no player on the rig, so phase 14's rig-only probe
|
||||
(`RgProbe14c`) made stand-ins again: sleepers for walkp1 (`…01`, *Brannock*), their clan mate
|
||||
walkp2 (`…02`, *Wren*, moved into the game's online list) and walkp4 (`…04`, outside the clan), and
|
||||
a vending machine. Positions and gates were read on the phone and, for each step, from the API as
|
||||
the same viewer.
|
||||
|
||||
1. **The picture (step 1).** The Map tab rendered the rig's picture with the game's grid, the 85
|
||||
monuments on the monuments, the locked crates and a supply drop. Double tap and pan work, and grid
|
||||
labels appear once a cell is 30 dp across. Tapping the Outpost gave *Outpost · Grid I8*, drawn in
|
||||
the I8 cell. **The frame's label equals the game's for 83 of the 85 monuments.** The other two are
|
||||
the oil rigs, north of the grid, where the game writes `I-3` and `A-3` and the frame clamps to row
|
||||
0 (§31.6). The web has the same clamp.
|
||||
2. **The gates (step 2).** Signed out: world and site events drawn, *Players* and *Bases* listed
|
||||
disabled as *Shown to staff only*. walkp1, signed in and not staff: the same, plus their own dot
|
||||
and their mate's. walkadmin: all four switches, the three sleepers and the vending machine, and
|
||||
the mates row offering *Link your Steam account* to this unlinked admin. With the players layer
|
||||
widened to `public` and presence at `staff`, a signed-out viewer saw *Shown to staff only. Limited
|
||||
by who may see who is online.* and no players. An override opening bases on the Oxide rig alone
|
||||
drew its vending machine signed out, while the Carbon rig listed bases as staff only. The settings
|
||||
were put back afterwards.
|
||||
3. **Own dot and a mate (step 3).** walkp1 saw their own sleeper (*You · Asleep, offline · Grid I9*)
|
||||
and Wren (*Wren · Sleeping · Grid I8*), and nobody else. Signed out and in as walkp4: only their
|
||||
own dot. **On a phone the server screen cannot outlive an account switch.** It has no drawer, so
|
||||
signing out means leaving it, and the next account opens a new screen. The retained-view-model
|
||||
case events phase 14b found is therefore covered by the unit tests, which switch the account
|
||||
under a live view model and under an answer still in flight.
|
||||
4. **Live (step 4).** A cargo ship and a patrol helicopter spawned by console were on the map at the
|
||||
next poll, and moved between frames 20 s apart. The cargo ship picked up its locked crate. Core's
|
||||
request log showed **three asks in 25 s on the Map tab, none in 30 s on another tab, none in 30 s
|
||||
with the app in the background, and one immediately on return**. The picture was fetched once
|
||||
across six openings of the tab. Coil's disk cache served the rest.
|
||||
5. **Events (step 5).** A run of a listed definition (13a's walk 1) placed a zone, three elite
|
||||
crates and two NPCs, and a rehearsal of 13b's walk 6 placed a second zone beside it. The public
|
||||
calendar listed the first run with its `runId` and not the rehearsal. The first zone's card read
|
||||
*Walk 1 zone · Site event · Grid G12* with **Open event**, which opened the app's event page with
|
||||
`?run=23` (core's log). The rehearsal's card had no button. For the results half, a new listed
|
||||
definition opened a zone and a participation tally, then collected and ran
|
||||
`core.results.publish`. **The stand-ins cannot be counted** (§31.6), so the tally was seeded by
|
||||
phase 13b's recipe with fixed seconds and one kill, scored `both`. The published table read
|
||||
*Brannock 17.57, Wren 6.67, Outsider 1.5*: names from the module's meta and fractional scores.
|
||||
*My events* for walkp1 showed the run with *Rank 1 · Score 17.57*, and its page the same table.
|
||||
6. **A new map (step 6).** The rig restarted on seed 1235 with the phone on the Map tab. While it was
|
||||
down, the picture stayed and the line read *The server is offline, so nothing is moving on its
|
||||
map.* When the live answer named `3000.1235.1`, the app re-read `/map` on each poll until the
|
||||
module had fetched the new picture. It then drew the new map in place, refitted, within one poll.
|
||||
The rig is back on seed 1234.
|
||||
7. **Nothing else moved (step 7).** A site whose module does not declare `map` shows no Map tab, and
|
||||
`?tab=map` there opens the feed: both are tests. The website change adds one field, which the web
|
||||
calendar ignores. The app's other screens are untouched apart from one nullable field on the
|
||||
calendar entry. **A UO core was not walked**: none was running here. The UO paths do not touch the
|
||||
changed files.
|
||||
|
||||
Tests: Android-app **732 unit tests** (72 new or changed in the Rust package), `lintDebug` clean,
|
||||
debug APK built. website: `eventPublic.test.js` 27/27. The full server suite has two local failures,
|
||||
both route-manifest checks caused by this machine's git-ignored module installs; a clean worktree
|
||||
passes them.
|
||||
|
||||
### 31.6 What the walk found that the plan did not say
|
||||
|
||||
- **The reader's zoom was thrown away twice.** First on the way back from **Open event**: the
|
||||
canvas's transform lived in `remember`, which leaving the screen discards. The card, held in the
|
||||
view model, came back over a whole-world view. Then **whenever the status line under the map
|
||||
changed length**, because the map's box grew or shrank by a line and the canvas treated every new
|
||||
size as a rotation. The transform is now `rememberSaveable`, and a new size keeps the picture
|
||||
point at the centre (`MapTransform.resized`, tested). Both were invisible to the unit tests, which
|
||||
never render.
|
||||
- **The participation tally cannot be fed by a stand-in.** It counts a player who is connected,
|
||||
alive and awake (`RunicGateway.cs`, the tally's presence test), and a probe-made sleeper is none of
|
||||
those. `rgq.online` puts a sleeper in the online list for the players layer, but it is still asleep
|
||||
and has no connection. The results half of step 5 was walked on a seeded tally, as 13b's step 5
|
||||
was. A counted tally needs a person, in [`PLAYER_WALK.md`](../../rust-link/PLAYER_WALK.md).
|
||||
- **Off the grid the game writes a negative row, and the frame writes row 0.** The oil rigs are
|
||||
`I-3` and `A-3` to `MapHelper`, and the frame (the web's and the app's alike) clamps to `I0` and
|
||||
`A0`. A monument's card uses the game's own string, so no monument is mislabelled. A world event
|
||||
north of the grid would be labelled as row 0. Nothing was changed: the two maps agree with each
|
||||
other, and following the game means changing the web's tested function first.
|
||||
- **The server header read "wiped Dec 31, 1"** for the minute after the rig came up on a new seed,
|
||||
before the new wipe's time arrived. It is the M14 header formatting a placeholder date. It is
|
||||
outside this phase and is recorded here only.
|
||||
- **A marker's card outlives the marker.** When the server goes offline the live layers go and the
|
||||
card stays up, describing a marker no longer drawn. It is a snapshot of what was tapped. Nothing
|
||||
was changed.
|
||||
|
||||
### 31.7 What is not proven here
|
||||
|
||||
- **A real player** on the map (a moving own dot, a mate logging off) and **a counted tally** both
|
||||
need a person. They are in [`PLAYER_WALK.md`](../../rust-link/PLAYER_WALK.md).
|
||||
- **A UO site**, for want of a running UO core. The change does not reach its screens.
|
||||
- **Carbon.** As §31.3 said, the app reads the module, and phase 14 proved the module's answer on
|
||||
both frameworks.
|
||||
|
||||
---
|
||||
|
||||
[aa]: https://gitea.whitlocktech.com/RunicGateway/Android-app
|
||||
|
||||
Reference in New Issue
Block a user