6.3 KiB
Code review — NEO-97 (E5M2-11)
Date: 2026-05-29
Scope: Branch NEO-97-e5m2-11-client-telegraph-hud vs origin/main — commits b0d0c42 … cfda0ac
Base: origin/main
Verdict
Approve with nits
Summary
NEO-97 adds the Epic 5 Slice 2 client half: thin HTTP clients for GET /game/world/npc-runtime-snapshot and GET /game/players/{id}/combat-health, a RefCounted windup display mirror between polls, ~1 Hz combat-active polling wired from main.gd, and three HUD labels (PlayerCombatHpLabel, NpcStateLabel, TelegraphLabel). The implementation follows NEO-85/NEO-32 patterns (injectable HTTP, _busy guard, local tick-down smoothing) and matches kickoff decisions (combat-active gate, locked + incoming rows, interpolated windup). Four GdUnit suites cover parse/URL helpers, windup reconciliation, and combat-active / NPC state label logic via a test harness. Docs (plan, manual QA, README, E5M2 backlog, module + alignment register) are updated. Primary risk is logic duplication between main.gd and the test harness (drift over time) and a stale module risk bullet that still says “no client windup math.” No blocking correctness issues for the documented manual QA flows (lock → cast → telegraph → player HP drop).
Documentation checked
| Path | Result |
|---|---|
docs/plans/NEO-97-implementation-plan.md |
Matches — kickoff decisions (combat-active gate, locked + incoming HUD, interpolate windup, keep prototype_target_markers.gd) reflected in code; reconciliation section accurate; AC checked. |
docs/plans/E5M2-prototype-backlog.md (E5M2-11) |
Matches — AC checked, landed note present; script name in backlog still says npc_archetype_markers.gd but plan kickoff explicitly adopted prototype_target_markers.gd (documented divergence). |
docs/decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md |
Matches — NEO-97 landed line correct; Risks + NPC instance row updated for windup display + NEO-91 constants migration. |
docs/decomposition/modules/documentation_and_implementation_alignment.md |
Matches — E5.M2 row includes NEO-97 landed with README/plan/manual QA links. |
docs/manual-qa/NEO-97.md |
Matches — Godot steps cover lock, cast, telegraph countdown, player HP drop, incoming threat row, regressions. |
client/README.md |
Matches — NPC runtime + telegraph HUD section documents poll gate, labels, tab order, script paths. |
docs/decomposition/modules/client_server_authority.md |
Matches — read-only GET polling; no client authority over combat/NPC state; poll advances server lazy tick (accepted per plan). |
Blocking issues
None.
Suggestions
-
E5.M2 Risks bullet — Update “Telegraph desync: … no client windup math” to “display-only windup tick-down between polls; reconciled on eachDone.npc-runtime-snapshotGET” so module docs align with the NEO-97 kickoff decision andnpc_runtime_hud_state.gd. -
E5.M2 NPC instance row — Change “Breaking: Godot constants migrate in NEO-97” to note migration landed in NEO-91; NEO-97 adds HUD poll clients only.Done. -
Test coverage vs plan —Done. Telegraph fallback + local windup expiry + timer mirror tests added.npc_combat_hud_refresh_test.gdcovers combat-active bool +NpcStateLabelcopy but notTelegraphLabelformatting or timer start/stop (called out in the implementation plan test table). Add at least one telegraph line assertion (locked vs incoming fallback) and, if feasible without heavy scene wiring, a timer-gate test mirroring_update_combat_poll_gate(). -
Shared HUD helpers — Combat-active gate, row resolution, and label formatters are duplicated inDone.main.gd(~180 lines) andNpcCombatHudHarnessin tests. Extract to a small RefCounted (e.g.npc_combat_hud_helpers.gd) used by both to prevent drift; optional follow-up given NEO-85 precedent of wiring inmain.gd.npc_combat_hud_helpers.gdshared bymain.gdand tests. -
Telegraph at zero —Done._format_telegraph_linecan show· 0.0swhile the snapshot row still carriesactiveTelegraphuntil the next poll. Consider returning empty whendisplay_windup_remaining≤ 0 soTelegraphLabelshows—between local expiry and server reconcile.format_telegraph_linereturns empty when remaining ≤ 0. -
Runtime sync failure —Done._on_npc_runtime_sync_failedre-renders from the last good snapshot without surfacing failure onNpcStateLabel/TelegraphLabel(player HP already shows error). Acceptable for prototype; optionally dash NPC/telegraph lines or append a sync-failed hint for QA clarity._npc_runtime_sync_errorappended to NPC/telegraph labels when empty or alongside stale rows.
Nits
-
Nit:Done. Instancenpc_runtime_client.gdstores_last_snapshotbut staticnpc_row()does not fall back to it (unlikecombat_targets_client.target_row()); field is write-only today.npc_row()falls back to_last_snapshot. -
Nit: Duplicate
MockHttpTransportinner classes innpc_runtime_client_test.gdandplayer_combat_health_client_test.gd— fine for now; a shared test helper would reduce copy-paste. -
Nit:
main.gdgrows ~250 lines for NEO-97; repo policy prefers thinmain.gd— track for a future extract when the next HUD story lands. Partially addressed — helpers extracted; poll wiring remains inmain.gd. -
Nit: Plan text referencesDone.serverTimeUtcanchor innpc_runtime_hud_state.gd; implementation uses receive tick + serverwindupRemainingSecondsonly — sufficient for display smoothing; plan wording could be tightened.
Verification
# GdUnit (CI uses Godot 4.6 headless — not available locally in review env)
cd client && ~/godot/Godot_v4.6-stable_linux.x86_64 --headless --path . \
-s res://addons/gdUnit4/bin/GdUnitCmdTool.gd --ignoreHeadlessMode -a res://test
# Lint / format (CI)
pip install "gdtoolkit==4.5.0"
gdlint client/scripts client/test
gdformat --check client/scripts client/test
Manual (required before merge): docs/manual-qa/NEO-97.md — server + Godot F5; elite lock → cast → ~7.5 s telegraph window → player HP 75/100; incoming threat row when locking a different NPC while elite holds aggro.