Merge pull request 'docs(rust): phase 14 as built — the live map (PLAN.md §30.6–§30.9, PROTOCOL.md §17)' (#272) from docs/rust-phase-14-built into main
Reviewed-on: #272
This commit is contained in:
@@ -1006,7 +1006,7 @@ Each phase ends with its findings written down, as every workstream here does.
|
||||
| 12 | **Events: option sources and the leases** (§9, **as corrected by §27**). ✅ **Built and walked 2026-09-24 on both rigs — as built and findings in §27.5–27.7.** 21 keys walked live, two `spawn.max_*` left out; two defects of its own fixed (the Oxide grant owner, `EventsEnabled` not applying); four found outside it and raised. [kit][kit] ch. 5's own ordering — leases before actions — and every key verified live before it is advertised. **Plan of record in §27 (D73–D79)**: the target names the server, game convars only (vanilla Rust has no gather/craft/smelt rate), the weekend-VIP lease is a group-wide permission, an `EventsEnabled` switch off by default, seven-day holds, and no budget dimension until phase 13; protocol 8 | Module-Rust + 2 + docs | A leased value is observed changing in the running game and restored, per key; `rust.group.permission` expires without core asking |
|
||||
| 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). 📐 **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 |
|
||||
| 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 | Android-app | 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 |
|
||||
@@ -5284,6 +5284,122 @@ them before code.
|
||||
- **Locked crates, supply drops and the helicopters are in "world events"**, not their own layers.
|
||||
D112 grouped them, and one switch per group is what an operator reasons about.
|
||||
|
||||
Review merged this section (docs#271) with no comment, so all six readings stand.
|
||||
|
||||
### 30.6 Two more decisions the build needed
|
||||
|
||||
Both came from the org lead on 2026-09-25, asked mid-build.
|
||||
|
||||
| # | Decision |
|
||||
|---|---|
|
||||
| **D119** | **The plugin's grid label is the game's own.** A second probe on the rig asked `MapHelper.PositionToString` for fifteen positions. The game fits a whole number of cells to the world: a 3000 map is **20 × 20 cells of exactly 150 m** (A0 to T19, the origin K10). Phase 3's `Grid()` used a fixed 146.3 m cell, the number community tools quote, and put a position up to a row too far south (the ferry terminal read O17 where the game says O16). It was fixed in this phase: `Grid()` now calls `MapHelper.PositionToString`, and the map's grid comes from `MapHelper` as well. Stored history keeps its old labels. §30.3's "150 m cells" was right for this map by luck of arithmetic. The page never assumes a cell size; it draws `gridCells` cells of `gridCellSize` metres, both from `map.info`. |
|
||||
| **D120** | **Leaflet is a lazy second chunk, not part of `entry.js`.** D116 said "bundled into the chunk". But `entry.js` loads on every page of the site, and Leaflet touches `document` the moment it is evaluated, which breaks the registration test that evaluates the chunk in Node. The Map tab does `import('../lib/leaflet.js')`, and Vite emits `dist/leaflet-<hash>.js` beside `entry.js`. Core serves that directory (MODULE_API §3.1). `release.yml` now copies every `dist/*.js`, and `checkExternals.js` and `build.test.js` hold both ends: every chunk `entry.js` imports exists and is clean, Leaflet is not in `entry.js`, and the release copies them all. |
|
||||
|
||||
### 30.7 As built, 2026-09-25
|
||||
|
||||
[Rust-Plugins][rp], [Rust-Link][rl] and [Module-Rust][mr] on `feat/phase-14-map`, into `edge`, as protocol 11
|
||||
([`PROTOCOL.md`](../../rust-link/PROTOCOL.md) §17). **Core is unchanged and `MODULE_API` did not move.**
|
||||
|
||||
| Repo | What |
|
||||
|---|---|
|
||||
| Rust-Plugins | `map.info`, `map.fetch`, `map.render` and `map.live`; the Rust+ cache read by name; hashing and slice encoding on the thread pool; the render kept in `map.jpg` and `map.json`; world and base sets kept by `OnEntitySpawned` / `OnEntityKill` and seeded at `OnServerInitialized`; `MapMaxSleepers` and `MapMaxBases`; `rg.map`; `Grid()` from `MapHelper` (D119) |
|
||||
| Rust-Link | Four forwards and `PROTOCOL_VERSION` 11, with a test that a base64 slice fits the line cap |
|
||||
| Module-Rust | `rust_map_images` and `rust_map_overrides`; `mapImages.js` (the D110 fetch, Render now); `mapLive.js` (the D111 cache); `model/map` (layers, D113, D115, D117, D118, the per-viewer projection, `DERIVATION_VERSION` 1); the three public routes and two admin routes; the Map tab and the Map card; capability `map`; Leaflet as a lazy chunk (D120) |
|
||||
|
||||
**Where the build departed from §30.2, and why:**
|
||||
|
||||
- **`map.info` also carries `gridCells` and `gridCellSize`**, from the game's `MapHelper` (D119), and
|
||||
`rust_map_images` stores them. §30.2 had the page derive the grid from a constant.
|
||||
- **A server whose game has no picture still gets a row.** `bytes` and `sha256` are NULL and
|
||||
`source` is `none`, and the row keeps the geometry and monuments, so the page draws the layers on a
|
||||
plain background. That background is sized as the Rust+ cache would be (half scale plus the margin),
|
||||
so a picture that arrives later fills the same frame. The same map with a picture already stored
|
||||
keeps it when the game later has none.
|
||||
- **"One fetch in flight" is the module's rule, not the plugin's.** A slice is answered within one
|
||||
frame on the main thread, so two can never overlap in the plugin. The plugin refuses slices with
|
||||
`busy` only while a render runs. The module holds one fetch per server.
|
||||
- **The live answer names who is asking.** `GET …/map` returns `mates.signedIn`, so the page offers
|
||||
"link your Steam account" only to someone who has an account (a walk finding, §30.8).
|
||||
- **Grid labels are shown only when a cell is at least 30 px wide on screen.** At the fitted zoom a
|
||||
20-cell map's labels overlapped into a wall of text.
|
||||
- **The Leaflet chunk is 76 KB gzipped**, not the ~40 KB D116 was weighed on. Vite's library build
|
||||
mangles names but never strips whitespace, for the entry chunk too. It loads only on the Map tab
|
||||
(D120), so it was left alone rather than changing how every chunk is built.
|
||||
- **Positions are read from `userID`**, never `UserIDString` (§30.8).
|
||||
|
||||
**Walked against real core on `main`** (the `rustp12` database, this module staged from the branch)
|
||||
and the two rigs, 2026-09-25. There is no player on either rig, so a rig-only probe plugin made
|
||||
stand-ins: sleepers under Steam ids linked to five throwaway website users, one of them moved into
|
||||
the game's online list for a minute; a vending machine; a patrol helicopter and a cargo ship. The
|
||||
first-party clan *Northwatch* already on the Oxide rig (phase 9) holds two of those Steam ids.
|
||||
|
||||
1. **The cache, and its absence (step 1).** At boot the site fetched the Rust+ picture by itself on
|
||||
first sight (D110): `companion`, 360 341 bytes, 2500 × 2500, stored at the first board poll.
|
||||
**The rig could not be booted without a picture** (§30.8), so the probe emptied the cache field
|
||||
by name and `map.info` answered `none`. Fetch again answered `current` and kept the stored picture,
|
||||
because it is the same map. Render now answered 202 with an estimate of 9 s. The plugin drew the
|
||||
map in **8 141 ms of stall**, byte-identical to the cache (the same SHA-256), and the site moved the
|
||||
row to `rendered`. A second Render now while the first ran was refused 409. After a restart on the same map, with the cache emptied again, `map.info` answered **`rendered` from the file on disk**, and the boot drew nothing.
|
||||
2. **The geometry (step 2).** In the browser, monument markers sit on the monuments in the picture.
|
||||
The page's grid label equals the game's at all fifteen positions the probe asked
|
||||
`MapHelper.PositionToString` for, and the client suite holds those fifteen answers.
|
||||
3. **A wipe (step 3).** The rig restarted on seed 1235. `map.info` gave a new key, `3000.1235.1`, and
|
||||
at the next board poll the site fetched the new picture (398 896 bytes) and replaced the old row.
|
||||
A slice asked for with the old hash is refused `stale`, on the plugin and in the module's suite.
|
||||
4. **The switches (step 4).** At the defaults, an anonymous viewer's `map/live` carries `world` and
|
||||
`events` and **no `players` or `bases` key at all**, and a signed-in viewer gets the same. Staff
|
||||
get all four. Widening the players layer to `public` with presence at `staff` showed an anonymous
|
||||
viewer nothing more, and `/map` said `cappedByPresence`. Widening both showed the three sleepers.
|
||||
A per-server override opened the Oxide rig alone; the Carbon rig stayed at staff.
|
||||
5. **Own dot and mates (step 5).** walkp1, who is linked but not staff, with the players layer at
|
||||
`staff`: with everybody asleep they saw only their own sleeper, marked `self`. With the clan mate
|
||||
WalkTwo and the stranger WalkFour moved online, walkp1 saw WalkTwo and not WalkFour, while staff
|
||||
saw all three. **With the clan roster widened to `public`** nothing about positions changed:
|
||||
walkp4, outside the clan, still saw only their own dot, and an anonymous viewer saw none (D117).
|
||||
**With the Oxide rig's mates switch off**, walkp1's own dot and their mate's both went.
|
||||
6. **Live things (step 6).** A patrol helicopter and a cargo ship spawned on the rig were on the next
|
||||
anonymous poll and moved across three polls ten seconds apart. A 13a zone and an elite crate for a
|
||||
run appeared under `events`, and after the run's revert `events` was empty.
|
||||
7. **Cost (step 7).** Twenty viewers polling once a second for thirty seconds made 600 requests. The
|
||||
plugin's own counter moved from 13 to 19: **one `map.live` per five seconds**. The slowest ask took
|
||||
1 ms.
|
||||
8. **Carbon (step 8).** On the Carbon rig at protocol 11, the site fetched the picture by itself.
|
||||
It is byte-identical to the Oxide rig's for the same seed (SHA-256 `28da6e8a…`). `map.live`
|
||||
answered for anonymous and staff viewers, and `rg.hooks` showed `OnEntitySpawned` and
|
||||
`OnEntityKill` firing on Carbon.
|
||||
|
||||
### 30.8 What the walk found that the plan did not say
|
||||
|
||||
- **`UserIDString` is empty for a player the game did not see connect.** Every stand-in sleeper came
|
||||
back with `steamId: null`, which on a real server would be an own dot and a clan mate the site can
|
||||
never match. `map.live` reads the id from `userID`. The players board (phase 3) still uses
|
||||
`UserIDString` for connected players, where it has always been set.
|
||||
- **The legend told an anonymous visitor to link a Steam account.** `/map` now says `signedIn`.
|
||||
- **The game now draws its map at every boot, whatever Rust+ is doing.** The boot log reads
|
||||
`[Rust.MapCache-Images] Image uploaded to backend: …/86b01198b36d…jpg`, and that is exactly the
|
||||
SHA-256 `map.info` reported for the cache. Facepunch renders the map for its own map-image service,
|
||||
and the Rust+ cache holds the same bytes. So **the render fallback (D109) is for a server that
|
||||
somehow has no picture, and the walk could not make one by configuration**: `+app.port -1` on the
|
||||
command line reached the game as `1`, and so did `app.port -1` and `app.port "-1"` in `server.cfg`.
|
||||
The value parser drops the minus sign; at the console the convar takes `-1`. §30.0's open question
|
||||
("is the cache empty when `app.port` is off?") is therefore unanswered, and on a current server it
|
||||
probably does not arise. Render now stays, as the admin's way out of a server that answers `none`,
|
||||
and phase 18's egg should not promise that `app.port -1` switches Rust+ off.
|
||||
- **A tool cupboard placed by code is destroyed by the game within two seconds**, on terrain and on a
|
||||
foundation, so the rig could not show a real TC on the Bases layer. The layer's tracking was walked
|
||||
with a vending machine. A TC is in the map walk in [`PLAYER_WALK.md`](../../rust-link/PLAYER_WALK.md).
|
||||
- **The cargo ship and its crates were outside the picture**, past the world and the ocean margin. The
|
||||
page places them anyway, and the view may pan half a map past the picture's edge.
|
||||
|
||||
### 30.9 What is not proven here
|
||||
|
||||
- **A real player.** A moving own dot, a clan mate dropping off the map when they leave the clan or
|
||||
log off, a real base on the Bases layer and a crate being hacked all need people in the game. They
|
||||
are in [`PLAYER_WALK.md`](../../rust-link/PLAYER_WALK.md) as the map walk.
|
||||
- **The admin Map card in a browser.** Its data was walked through the API: switches saved, the
|
||||
picture's state, Fetch again. The page itself was built and its tests pass, but signing in as an
|
||||
admin in the walk browser means typing a password, which was left to the org lead's review.
|
||||
|
||||
---
|
||||
|
||||
[aa]: https://gitea.whitlocktech.com/RunicGateway/Android-app
|
||||
|
||||
@@ -327,3 +327,35 @@ landing a kill, and opening the Kits menu.
|
||||
of it; the kill lands on the tally; a reward granted while you were offline is in the menu when you
|
||||
come back; its extra use works exactly once; and a revert takes away the right to redeem but never
|
||||
what you already redeemed.
|
||||
|
||||
## The map walk (phase 14, protocol 11)
|
||||
|
||||
The parts of [`PLAN.md`](../modules/rust/PLAN.md) §30.4 that need somebody in the game. Everything
|
||||
else was walked with stand-ins: sleepers made by a rig-only probe plugin, one of them moved into the
|
||||
game's online list for a minute (§30.8). What a stand-in cannot be is a person who moves, joins a
|
||||
clan and builds a base. This walk needs two accounts.
|
||||
|
||||
**Before you start:**
|
||||
|
||||
1. **Two Steam accounts, A and B, both linked on the site**, each signed in to its own browser
|
||||
session. Neither is staff.
|
||||
2. **A and B in the same first-party clan** on the server (the in-game clan menu; D115 reads the
|
||||
game's own clans, not the uMod Clans plugin).
|
||||
3. The map switches at their defaults (Admin → Rust visibility → *The live map*): players and bases
|
||||
staff only, *You and your clan* on.
|
||||
|
||||
| # | Do this | You should see |
|
||||
|---|---|---|
|
||||
| 1 | Both A and B in game. A opens the server's **Map** tab | A's own dot (cyan) where A stands, and B's dot (green) where B stands. **No other player**, and no Players layer. Walk A somewhere; within ten seconds the dot follows |
|
||||
| 2 | B leaves the clan | Within one clan board (a minute) B's dot is gone from A's map. A's own dot stays |
|
||||
| 3 | B rejoins the clan and logs off | A sees only A's own dot: a mate appears only while online (D115). B, signed in to the site while offline, sees B's own **sleeper** |
|
||||
| 4 | Staff widen the clan roster to *Everyone* | Nothing about positions changes for anybody (D117) |
|
||||
| 5 | Staff turn *You and your clan* off for this server | A's own dot and B's both disappear from A's map |
|
||||
| 6 | A places a tool cupboard on a foundation, and a vending machine | A staff viewer sees both on the Bases layer within ten seconds. A does not |
|
||||
| 7 | A player hacks a locked crate | The crate's tooltip on the map counts the hack down |
|
||||
|
||||
**Run steps 1 and 6 on Carbon as well.**
|
||||
|
||||
**What counts as a pass:** a player sees themselves and their online clan mates and nobody else; a mate
|
||||
drops off the map when they leave the clan or the server; switching the view off removes it; and a
|
||||
real base shows to staff only.
|
||||
|
||||
@@ -473,6 +473,12 @@ Every kind protocol 2 defines, and the hook behind it. **`class` is not a field
|
||||
and every community site shows it; a **structure's** grid is where somebody lives, which is why
|
||||
`entity.destroyed` is staff-class here and why R9 makes the same distinction for map layers.
|
||||
|
||||
**From protocol 11 the label is the game's own** (`MapHelper.PositionToString`, PLAN.md D119).
|
||||
Protocols 2 to 10 computed it in the plugin from a 146.3 m cell, the number community tools quote.
|
||||
That was wrong: the game fits a whole number of cells to the world, so a 3000 map has 20 cells of
|
||||
exactly 150 m, and the constant put a position up to a row too far south (the rig's ferry terminal read
|
||||
O17 where the in-game map says O16). Rows already stored keep the label they arrived with.
|
||||
|
||||
**Three hooks are deliberately not in this wave, and none of them is an oversight:** `OnEntityTakeDamage`
|
||||
and `OnFrame`/`OnTick` fire at a rate that makes a bridge a performance regression, and nothing in
|
||||
phases 3–19 needs per-hit or per-frame fidelity. R17's warning about chatty zone transitions is the
|
||||
@@ -1721,3 +1727,109 @@ the game is down:
|
||||
| `POST /chat` | `chat.say` | Opaque object |
|
||||
|
||||
`perm.sync`'s `credits` field passes through untouched, like the rest of that body.
|
||||
|
||||
## 17. Protocol 11 — the map
|
||||
|
||||
Added in phase 14 ([`PLAN.md`](../modules/rust/PLAN.md) §30). The picture of this wipe's map, and
|
||||
everything that moves on it. **Four commands, two hooks, two plugin config keys and one console
|
||||
command.** The plugin reads; nothing here changes the world, so **no map command is behind
|
||||
`EventsEnabled`**. The website decides which layer a viewer may see. The plugin answers every layer
|
||||
to anyone who asks, because the sidecar and the website are the only things that ask (§8.5).
|
||||
|
||||
### 17.1 The commands
|
||||
|
||||
| Command | Answers | |
|
||||
|---|---|---|
|
||||
| `map.info` | `map.info` or `map.error` | What this map is and where its picture comes from. See below |
|
||||
| `map.fetch` | `map.chunk` or `map.error` | One slice of the picture: `mapKey`, `sha256` and `chunk` (from 0) in; `data` (base64), `chunk` and `chunks` out |
|
||||
| `map.render` | `map.render` or `map.error` | The admin fallback: `accepted: true` at once, and the render on a later frame |
|
||||
| `map.live` | `map.live` or `map.error` | What moves, every layer at once. See §17.3 |
|
||||
|
||||
**`map.info`** carries:
|
||||
|
||||
- `mapKey`: which map. `<worldSize>.<seed>.<salt>` for a procedural map, `custom.<sha256 of the level URL>` for a custom one.
|
||||
- `source`: `companion` (the Rust+ cache), `rendered` (a render the plugin kept, §17.2) or `none`.
|
||||
- For a picture: `bytes`, `width`, `height`, `chunks`, `chunkBytes` (524 288), `background` and `sha256`.
|
||||
- `worldSize`, and `oceanMargin` (500, in **pixels** and never scaled).
|
||||
- `gridCells` and `gridCellSize`: the game's own grid, from `MapHelper`. On the rig's 3000 map that is 20 cells of 150 m.
|
||||
- `monuments`: those with a display phrase, which is what the in-game map shows. Each has `value` (the same `kind#n` token as `world.monuments`), `kind`, `label`, `x`, `z` and `grid`.
|
||||
- `rendering`: whether a render is under way.
|
||||
|
||||
**The hash is computed on the thread pool** the first time a picture is seen. Until it is ready the
|
||||
answer carries `hashing: true` and no `sha256`, and the caller asks again in a few seconds.
|
||||
|
||||
**`map.fetch` is refused `stale`** when `mapKey` or `sha256` no longer names the picture being served,
|
||||
so a fetch that straddles a map change cannot splice two maps. The slice is base64-encoded and
|
||||
serialized on the thread pool. A base64 slice of 512 KiB plus its envelope fits the game link's 1 MiB
|
||||
line cap, and the sidecar has a test that holds that sum.
|
||||
|
||||
**`map.error` reasons**:
|
||||
|
||||
| `reason` | Means | Worth retrying |
|
||||
|---|---|---|
|
||||
| `not-ready` | the world has not finished loading (§15.5) | yes |
|
||||
| `stale` | the map or picture moved since `map.info` | yes, from `map.info` |
|
||||
| `no-picture` | this server has no picture to slice | no |
|
||||
| `malformed` | a slice number outside the picture | no |
|
||||
| `busy` | a render is under way, or a second render was asked for | yes, later |
|
||||
| `has-picture` | `map.render` on a server that already has a picture | no |
|
||||
|
||||
### 17.2 Where the picture comes from
|
||||
|
||||
**Rust+ already keeps the picture.** When `app.port` is set, the game renders the map at boot and
|
||||
holds the JPEG in `CompanionServer.Handlers.Map.ImageData`. The plugin reads that property and the
|
||||
`_background` field **by name, by reflection, and nothing else of that type**: `CompanionServer`
|
||||
types hold the server's Rust+ secret, and a probe that enumerated one printed it into the log (PLAN.md
|
||||
§30.0). The picture's size is read from the JPEG's own header.
|
||||
|
||||
**Without Rust+ there is no picture until an admin asks for one** (D109). `map.render` calls
|
||||
`MapImageRenderer.Render` at half scale with a 500-pixel margin, which is the cache's geometry. **It
|
||||
holds the main thread for about 8.5 seconds on a 3000 map.** The answer goes out first, and the render
|
||||
runs half a second later. The picture is written to `map.jpg` beside `map.json` (its key) in the
|
||||
framework's data directory, so a restart on the same map serves it again as `rendered` without
|
||||
drawing a second time. A new map key makes the file stale, and it is ignored.
|
||||
|
||||
### 17.3 `map.live`
|
||||
|
||||
| Field | Rows |
|
||||
|---|---|
|
||||
| `world` | `kind` (`cargo`, `heli`, `chinook`, `bradley`, `supply`, `crate`), `x`, `z`; a locked crate being hacked adds `hackLeftSec`, a hacked one `hacked: true` |
|
||||
| `events` | what this site's events placed (§15.2): `kind` (`zone`, `crate`, `npc`), `runId`, `x`, `z`, and a zone's `radius` and `name` or a thing's `prefab` |
|
||||
| `players` | `steamId`, `name`, `x`, `z`, `sleeping`, `online`. Connected players, then offline sleepers |
|
||||
| `bases` | `kind` (`tc`, `vending`), `x`, `z`. **Positions only**: no owner, no authorised list, no shop name |
|
||||
|
||||
It also carries `mapKey` and `tookMs`, the plugin's own time for the ask. `playersTruncated` and
|
||||
`basesTruncated` are set when a bound cut a layer short.
|
||||
|
||||
**There is no sweep per ask.** The world and base sets are kept by `OnEntitySpawned` and
|
||||
`OnEntityKill`, which do two type tests per spawn, and are seeded by one sweep at
|
||||
`OnServerInitialized`, because a hotload hears no spawns. A player's vending machine is followed, a
|
||||
monument's (`NPCVendingMachine`) is not. On the rig the sweep of about 42 000 entities took 5 ms, and
|
||||
an ask took under 1 ms.
|
||||
|
||||
The Steam id comes from `userID`, not `UserIDString`. The phase 14 walk read the latter back empty from
|
||||
a sleeper made on the server.
|
||||
|
||||
### 17.4 Two bounds, and the console
|
||||
|
||||
Two config keys are written into an existing config the first time protocol 11 loads: **`MapMaxSleepers`**
|
||||
and **`MapMaxBases`** (2 000 each). Over a bound, the layer answers the first that many and sets its
|
||||
`…Truncated` flag. **This is not D95's refuse-never-clamp rule**, which is about a value an author
|
||||
typed. A read has nobody to refuse.
|
||||
|
||||
**`rg.map`** prints the key, the source, the picture's size and hash, whether a render is running, the
|
||||
game's grid, the size of each set, and how many `map.live` asks there have been, with the last and
|
||||
slowest.
|
||||
|
||||
### 17.5 The sidecar
|
||||
|
||||
`PROTOCOL_VERSION` becomes 11. There are four routes, each a correlated round trip that fails when the
|
||||
game is down, and **nothing is stored**: the picture passes through, and positions never touch the
|
||||
database (D111).
|
||||
|
||||
| Route | Command | |
|
||||
|---|---|---|
|
||||
| `GET /map` | `map.info` | |
|
||||
| `GET /map/chunk?mapKey=&sha256=&n=` | `map.fetch` | All three required |
|
||||
| `POST /map/render` | `map.render` | Opaque object; `cmd` and `reqId` written over the caller's |
|
||||
| `GET /map/live` | `map.live` | |
|
||||
|
||||
Reference in New Issue
Block a user