ReviewOS

stacks/wildloop

Implement defend/contest flow (defended history, territories_defended, contested status)

#941
Closed glennmichael123 opened this 9 days ago ·
9 days ago

Problem

The defense side of the game is entirely unimplemented. TerritoryHistory has a defended event type that nothing writes, TerritoryStats.territories_defended is only ever initialized to 0, and Territory.status='contested' is never set by any code path (only seed data is 'contested'). The UI filters for contested (useTerritoryExplorer.ts) but it never happens live.

Acceptance

  • Define the state machine: when does a territory become 'contested' and how is it resolved (conquered vs defended)?
  • Add a defend/contest path that writes defended history rows and increments territories_defended.
  • Surface 'under attack'/contested state from real data, not seed.
9 days ago

Implemented in 52d561c. The state machine (documented in ProcessActivityConquestAction's header):

Becoming contested — an enemy attack that intersects but can't take land:

  • graze in/out with no clean cut, or
  • a sliver cut below MIN_TERRITORY_SIZE on a big territory.

This replaces the old behavior where ANY unsplittable intersection handed over the whole territory (the 'nick a corner, steal an empire' exploit). A clean cut through a minimum-scale territory (< 2×MIN, can't be subdivided) is now the explicit full-takeover path — a finishing blow.

Resolution:

  • Defended: the owner runs through their own contested land → status back to active, a defended history row, territories_defended++ — all from the same process-conquest pass every recorded run already triggers.
  • Conquered: a successful split/takeover resolves the contest (pieces end active).

Surfaced from real data: the map endpoint now includes contested territories (it filtered them out!) plus per-territory defendCount from the history log; the run toast reports defended/attacked territories; the owner gets conquest_attack/conquest_defend notifications that land on the existing notifications UI (swords/shield icons already there). Repeated grazes don't spam events.

New enum values: TerritoryHistory.event_type += contested, UserNotification.type += the conquest types (migrations regenerated).

Verified end-to-end by scripts/verify-territory-battles.ts — 28 checks across graze→contest, dedupe, defend, sliver, finishing blow, replay idempotency, and map surfacing, all green.

Sign in to comment on this issue.