neon-sprawl/docs/plans/NEO-110-implementation-plan.md

11 KiB
Raw Blame History

NEO-110 — Implementation plan

Story reference

Field Value
Key NEO-110
Title E5M3-11: Client encounter progress + loot feedback HUD
Linear https://linear.app/neon-sprawl/issue/NEO-110/e5m3-11-client-encounter-progress-loot-feedback-hud
Module E5.M3 — EncounterAndRewardTables · Epic 5 Slice 3 · backlog E5M3-11
Branch NEO-110-client-encounter-progress-loot-feedback-hud
Server deps NEO-108GET /game/players/{id}/encounter-progress (Done on main); NEO-107 — complete event + grant summary (Done on main)
Pattern NEO-72 / NEO-86 — thin HTTP client, main.gd HUD render, defeat-triggered refresh; NEO-73 — inventory refresh after server-side success
Blocks NEO-111 — playable encounter clear capstone (Godot manual QA)
Server counterpart NEO-108 — authoritative GET; Bruno is not prototype-complete per full-stack epic decomposition

Kickoff clarifications

Topic Question Agent recommendation Answer
Refresh strategy When to call encounter-progress GET? Boot hydrate + refresh after each cast with targetDefeated — mirrors NEO-86 gig refresh; no periodic poll (NEO-108 deferred cadence to this story). Adopted — boot + after targetDefeated
Progress denominator How to show 2/3 targets? Hardcode 3 for frozen prototype_combat_pocket — single prototype encounter; avoids new encounter-definitions client (NEO-103 server-only). Adopted — constant 3
Grant display names Loot lines on completion label? Reuse ItemDefinitionsClient.display_name_for — NEO-72 precedent; fallback raw itemId. Adopted — reuse item defs
HUD placement Where do the two labels live? UICanvas/HudRoot after combat HP labels — visible during Tab/cast loop (not buried in economy collapse). Adopted — HudRoot combat area
Manual refresh key Dedicated input action? Skip — defeat-driven refresh + boot suffice for AC; NEO-111 capstone is the full Godot script. Adopted (implicit — no new key)

Goal, scope, and out-of-scope

Goal: Godot shows server-owned encounter progress and completion loot using GET /game/players/{id}/encounter-progress — player-visible N/3 defeat progress after each NPC kill and a completion grant summary matching the GET body.

In scope (from Linear + E5M3-11):

  • encounter_progress_client.gd: GET snapshot; parse v1 envelope; encounter_row(encounter_id) helper; signals encounter_progress_received / encounter_sync_failed; public request_sync_from_server(); push_warning on failures (NEO-72 pattern).
  • EncounterProgressLabel + EncounterCompleteLabel under UICanvas/HudRoot (after PlayerCombatHpLabel).
  • main.gd: boot hydrate; on accepted cast with targetDefeated: true → encounter-progress GET (alongside existing gig/combat refreshes); render progress defeatedTargetIds.size() / 3 for prototype_combat_pocket when state is inactive or active; on completed paint grant summary from rewardGrantSummary and _request_inventory_refresh() (NEO-72 inventory HUD).
  • GdUnit tests with HTTP doubles (testing-expectations.md).
  • docs/manual-qa/NEO-110.md — Godot steps (server + client; no Bruno).
  • client/README.md encounter HUD subsection.

Out of scope (from Linear + backlog):

  • Final loot VFX art; encounter picker UI beyond frozen pocket.
  • GET /game/world/encounter-definitions client (denominator hardcoded).
  • Periodic encounter poll timer; dedicated manual-refresh key.
  • Server route/DTO changes.
  • docs/manual-qa/NEO-111.md (capstone story).

Acceptance criteria checklist

  • Player-visible progress updates after each NPC defeat (e.g. 2/3 targets).
  • Completion label shows grant summary matching server GET.
  • Manual QA checklist exercisable without Bruno.

Implementation reconciliation (shipped)

  • encounter_progress_client.gd: GET parse v1; encounter_row; encounter_progress_received / encounter_sync_failed.
  • main.gd: boot + targetDefeated refresh; HUD render; inventory refresh on completed; item-defs re-paint when defs arrive.
  • Scene: EncounterProgressClient, EncounterProgressLabel, EncounterCompleteLabel in HudRoot after PlayerCombatHpLabel.
  • Tests: client/test/encounter_progress_client_test.gd (inactive/active/completed parse + 404).
  • Docs: client/README.md encounter section; docs/manual-qa/NEO-110.md.

Technical approach

Server contract (landed — NEO-108)

  • GET /game/players/{id}/encounter-progressschemaVersion 1, encounters[] rows with encounterId, state (inactive | active | completed), defeatedTargetIds, optional completedAt, optional rewardGrantSummary ({ itemId, quantity }[]).
  • Prototype row prototype_combat_pocket: inactive → 0/3; after defeats → active with growing defeatedTargetIds; all three E5.M2 NPCs defeated → completed with scrap_metal_bulk ×10, contract_handoff_token ×1.

Client constants (main.gd)

const PROTOTYPE_ENCOUNTER_ID := "prototype_combat_pocket"
const PROTOTYPE_ENCOUNTER_REQUIRED_TARGETS := 3

1. encounter_progress_client.gd

  • Mirror gig_progression_client.gd: injectable HTTP, _busy guard, GET …/encounter-progress.
  • parse_encounter_progress_json(text) static: require schemaVersion 1, encounters array.
  • encounter_row(encounter_id, snapshot) → row dict or {}.
  • Signals: encounter_progress_received(snapshot), encounter_sync_failed(reason).

2. Boot + refresh wiring (main.gd)

  • _setup_encounter_progress_sync() from _ready() after authority HTTP config (same as gig/inventory).
  • EncounterProgressClient node in main.tscn; _apply_authority_http_config_to_client.
  • Boot: request_sync_from_server() once catalogs/inventory chain has started (parallel with gig boot is fine).
  • _on_cast_result_received: when accepted and resolution.targetDefeated_request_encounter_progress_refresh() (in addition to existing cooldown/combat/gig refreshes).

3. HUD rendering

EncounterProgressLabel (header Encounter:):

state Copy
inactive prototype_combat_pocket: not started (0/3)
active prototype_combat_pocket: {n}/3 where n = defeatedTargetIds.size()
completed prototype_combat_pocket: completed (3/3)
sync error Encounter: error — {reason}

EncounterCompleteLabel (header Loot:):

state Copy
not completed Loot: —
completed One line per rewardGrantSummary grant: {displayName or itemId} ×{quantity}; optional completedAt suffix on header line
sync error Loot: error — {reason} (or leave progress label as primary error — prefer single error on progress label, complete shows )

Inventory: on encounter_progress_received, if prototype row state == "completed"_request_inventory_refresh() so InventoryLabel reflects grants without manual I (backlog + NEO-73 precedent).

4. Scene

  • main.tscn: add EncounterProgressClient node; EncounterProgressLabel + EncounterCompleteLabel as HudRoot children after PlayerCombatHpLabel, before NpcStateLabel (VBox order).

5. Tests (GdUnit, AAA)

  • Happy path: v1 JSON with one active row, two defeats → encounter_row returns defeatedTargetIds.size() == 2.
  • completed row includes rewardGrantSummary array parse.
  • 404 → encounter_sync_failed + push_warning.
  • Optional: static/format helper test for main render strings (only if extracted to encounter_progress_hud_format.gd — prefer keeping format in main.gd unless tests need it; client test focuses on HTTP client).

6. Docs on land

Files to add

Path Purpose
docs/plans/NEO-110-implementation-plan.md This plan.
client/scripts/encounter_progress_client.gd GET encounter-progress; parse v1; encounter_row helper; signals.
client/scripts/encounter_progress_client.gd.uid Godot uid companion (tracked).
client/test/encounter_progress_client_test.gd GdUnit: parse active/completed rows; 404 failure signal.
docs/manual-qa/NEO-110.md Godot manual QA (server + client; defeat chain + HUD assertions).

Files to modify

Path Rationale
client/scenes/main.tscn Add EncounterProgressClient, EncounterProgressLabel, EncounterCompleteLabel nodes.
client/scripts/main.gd Boot/refresh wiring, defeat-triggered GET, HUD render, inventory refresh on completed.
client/README.md Encounter progress + loot HUD subsection: server dep NEO-108, refresh triggers, label format.

Tests

Test file What it covers
client/test/encounter_progress_client_test.gd Mock 200: inactive row parse; active with two defeatedTargetIds; completed + rewardGrantSummary; 404 → encounter_sync_failed. AAA layout (# Arrange / # Act / # Assert).

No new C# tests (client-only; server covered by EncounterProgressApiTests). No Bruno changes.

Open questions / risks

Item Agent recommendation Status
Concurrent GETs on triple-defeat frame _busy guard drops overlap — last completed response wins; acceptable for prototype. adopted
Item defs not loaded before completion render Re-render encounter labels when definitions_ready fires if snapshot already completed (mirror NEO-72 inventory re-paint). adopted_on_item_definitions_ready re-paints encounter HUD
NEO-111 capstone overlap NEO-110 manual QA is HUD-focused; NEO-111 adds full loop + idempotency — keep scopes separate. deferred