11 KiB
11 KiB
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-108 — GET /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; signalsencounter_progress_received/encounter_sync_failed; publicrequest_sync_from_server();push_warningon failures (NEO-72 pattern).EncounterProgressLabel+EncounterCompleteLabelunderUICanvas/HudRoot(afterPlayerCombatHpLabel).main.gd: boot hydrate; on accepted cast withtargetDefeated: true→ encounter-progress GET (alongside existing gig/combat refreshes); render progressdefeatedTargetIds.size() / 3forprototype_combat_pocketwhenstateisinactiveoractive; oncompletedpaint grant summary fromrewardGrantSummaryand_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.mdencounter HUD subsection.
Out of scope (from Linear + backlog):
- Final loot VFX art; encounter picker UI beyond frozen pocket.
GET /game/world/encounter-definitionsclient (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.
Technical approach
Server contract (landed — NEO-108)
GET /game/players/{id}/encounter-progress→schemaVersion1,encounters[]rows withencounterId,state(inactive|active|completed),defeatedTargetIds, optionalcompletedAt, optionalrewardGrantSummary({ itemId, quantity }[]).- Prototype row
prototype_combat_pocket: inactive → 0/3; after defeats →activewith growingdefeatedTargetIds; all three E5.M2 NPCs defeated →completedwithscrap_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,_busyguard,GET …/encounter-progress. parse_encounter_progress_json(text)static: requireschemaVersion1,encountersarray.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).EncounterProgressClientnode inmain.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: whenacceptedandresolution.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: addEncounterProgressClientnode;EncounterProgressLabel+EncounterCompleteLabelasHudRootchildren afterPlayerCombatHpLabel, beforeNpcStateLabel(VBox order).
5. Tests (GdUnit, AAA)
- Happy path: v1 JSON with one
activerow, two defeats →encounter_rowreturnsdefeatedTargetIds.size() == 2. completedrow includesrewardGrantSummaryarray parse.- 404 →
encounter_sync_failed+push_warning. - Optional: static/format helper test for
mainrender strings (only if extracted toencounter_progress_hud_format.gd— prefer keeping format inmain.gdunless tests need it; client test focuses on HTTP client).
6. Docs on land
docs/manual-qa/NEO-110.md: fresh server; Godot F5; Tab + cast three prototype NPCs; assert progress 1/3 → 2/3 → 3/3; completion loot lines match GET; inventory shows grants without I.- Update E5_M3 client slice note, E5M3-prototype-backlog.md E5M3-11 checkboxes, documentation_and_implementation_alignment.md when implementation completes.
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). |
pending (implement with inventory pattern) |
| NEO-111 capstone overlap | NEO-110 manual QA is HUD-focused; NEO-111 adds full loop + idempotency — keep scopes separate. | deferred |