Repository navigation
Movers step 2: Movers panel, looping routes, encounter cadence - #184
Conversation
Step 2 of the movers plan. A DM-only Movers panel between Party and History lists every NPC token on every map (new DM-only `CampaignState.movers` from `runtime.moverSummaries()`): where it stands, its journey and ETA, mode and pace, encounter cadence and posting note, with Route / Halt / Loop / Shown actions. A row on another map switches the view and flies to the token. Retinues gain a `kind` (retinue, army, band, caravan, beast, other) so the same grouping serves armies and roaming encounters. `token.route` keeps its waypoints and a `loop` flag: a looping mover re-plans the circuit at the end and keeps walking on the same tick. `encounterEveryMiles` rolls on the terrain's encounter table every N miles of travel, logged DM-only; the mover is not halted. Undo restores both counters. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ecc87707f7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (remaining.length === 0 && token.loop && token.waypoints.length && !looped) { | ||
| const again = planRoute(runtime, map, { ...token, q: cur.q, r: cur.r }, token.waypoints); | ||
| if (again.length) { | ||
| remaining = again; | ||
| looped = true; | ||
| } |
There was a problem hiding this comment.
Replan every completed patrol circuit
When one clock advance contains enough travel time to finish both the original route and the first replanned circuit, !looped prevents another replan. The token is then persisted with an empty route, and future advances skip it at the token.route.length === 0 guard, permanently stopping a mover still marked as looping. Continue replanning completed circuits while credit remains, with a guard only for routes that cannot produce any steps.
Useful? React with 👍 / 👎.
| if (miles >= token.encounterEveryMiles) { | ||
| miles = 0; |
There was a problem hiding this comment.
Preserve excess distance after encounter checks
For cadences that are not exact multiples of milesPerHex, resetting miles to zero discards distance already traveled beyond the threshold. For example, 6-mile hexes with a 10-mile cadence check at mile 12 and then forget the extra 2 miles, producing checks every 12 miles rather than carrying progress toward the next 10-mile threshold; cadences smaller than one hex can also require multiple checks. Subtract the threshold and retain the remainder, repeating as needed.
Useful? React with 👍 / 👎.
| if (m.mapId !== activeMapId) send({ kind: 'view.map', mapId: m.mapId }); | ||
| focusToken({ id: m.tokenId, q: m.q, r: m.r }); |
There was a problem hiding this comment.
Defer focusing until the requested map arrives
When a mover is on another map, view.map only requests an asynchronous snapshot, while focusToken immediately emits a focus request against the currently loaded map. CanvasEngine consumes and clears that request synchronously, and the later map snapshot performs its normal recenter, so the advertised cross-map row action switches maps but does not fly to the mover. Retain the requested focus until the snapshot with m.mapId is active.
Useful? React with 👍 / 👎.
Step 2 of the movers plan.
CampaignState.moverssummary (runtime.moverSummaries()), empty for players.kind(retinue / army / band / caravan / beast / other); storage key unchanged.token.routestoreswaypoints+loop; at the end of a looping route the mover re-plans the circuit from where it stands and keeps walking on that tick.encounterEveryMilesrollsrollEncounteron the terrain table every N miles, logged DM-only (data.traveller); never halts the mover. Counters restored by undo.🤖 Generated with Claude Code