fix(viewer): A-dialogue Beta-gap — #751 + #615 - #923
Conversation
The Parley header named the lead PC ("Speaking with <PLAYER>") instead of the
NPC the party is talking TO — a conversation that named the wrong speaker — and
carried no live attitude read. Bind the parley surface to the conversation TARGET
and surface its disposition.
#751 (header names the NPC, pinned):
- build_parley_surface gains an additive `npc_id` param (threaded from `?npc=<id>`).
- New `_parley_npc_block` resolves the target: explicit npc_id wins (the
interlocutor the player opened the conversation with — fixes the "switch
mid-interaction" half), else the live Event's anchor_npc_id (the engine already
chose WHO a stumble-into is about). Absent/unknown id -> no `npc` key (degrades
exactly like a bad event_id; today's freeform parley is byte-identical).
- screen-dialogue.jsx renders `surface.npc.name` in the header (pinned to the
bound id) instead of `surface.actor`. The actor's sheet still drives the slots.
#615 (disposition meter on the Dialogue screen):
- The same `npc` block carries the canonical disposition bucket (reusing
`_attitude_disposition`, the predicate the Relations screen already uses) + the
raw attitude_value (fine at 0 -> "neutral").
- screen-dialogue.jsx renders a DispositionDot meter (reused from
screen-relations.jsx via window.DispositionDot) so the player sees their standing
WHILE talking.
Invariants: viewer stays READ-ONLY (pure projection; no engine-state writes); the
new field is additive (old snapshots round-trip — no npc key without a target); the
wire contract is unchanged when npc is unbound; reuses canonical predicates
(_attitude_disposition, DispositionDot) rather than forking.
TDD: RED tests first (7 surface tests in test_readmodel_surfaces.py for the npc
block + binding precedence + graceful degrade + disposition band/value; 7
component tests in new test_dialogue_npc_header.py rendering the REAL ParleyMenu
under the babel-vm Node harness to assert the header names the NPC not the player
and the DispositionDot meter renders), then GREEN.
Refs #751, #615.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe Parley screen gains NPC-binding support. The backend adds ChangesParley NPC Binding and Disposition Meter
Sequence Diagram(s)sequenceDiagram
participant Client
participant ParleySurfaceRoute
participant build_parley_surface
participant _parley_npc_block
participant ParleyMenu
Client->>ParleySurfaceRoute: GET /parley-surface?npc=npc_42
ParleySurfaceRoute->>build_parley_surface: npc_id="npc_42"
build_parley_surface->>_parley_npc_block: snapshot, npc_id, live_event
_parley_npc_block-->>build_parley_surface: {id, name, attitude, attitude_value, met, disposition}
build_parley_surface-->>ParleySurfaceRoute: {actor, npc: {...}, skills, ...}
ParleySurfaceRoute-->>Client: JSON with npc block
Client->>ParleyMenu: surface.npc = {name, disposition, attitude_value}
ParleyMenu-->>Client: header "Parley · Speaking with Aldric" + DispositionDot
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Comment |
Dialogue/Parley felt cluster — v1.0.4 Player-Ready-Beta gap
The Parley header named the lead PC ("Speaking with Cassian") instead of the NPC the party is talking TO — a conversation that named the wrong speaker — and gave no live attitude read. This binds the parley surface to the conversation TARGET and surfaces its disposition. Viewer-side, additive, read-only.
#751 — header shows the PLAYER not the NPC; NPC can switch mid-interaction
build_parley_surfacegains an additivenpc_idparam (threaded from?npc=<id>on/parley-surface)._parley_npc_blockresolves the conversation target, in precedence order:npc_id— the interlocutor the player opened the conversation with (authoritative; fixes the "switch mid-interaction" half by pinning to one id), thenanchor_npc_id— the engine already chose WHO a stumble-into is about.npckey, degrading exactly like a badevent_id. Today's freeform parley payload is byte-identical.screen-dialogue.jsxrenderssurface.npc.namein the header (pinned to the bound id) instead ofsurface.actor. The actor's sheet still drives the skill slots — the header names the target, the slots/portrait stay the speaking PC.#615 — Disposition meter on the Dialogue screen
npcblock carries the canonical disposition bucket (reusing_attitude_disposition, the predicate the Relations screen already uses) + the rawattitude_value(fine even at 0 →"neutral").screen-dialogue.jsxrenders a DispositionDot meter — reused fromscreen-relations.jsxviawindow.DispositionDot, no fork — so the player sees their standing while talking.Invariants honored
npckey without a bound target → old snapshots and the unbound parley are wire-identical._attitude_disposition(server) +DispositionDot(relations screen) — not forked.The engine half this binds to already landed (
generate_parley_optionsemits annpcblock — seeservers/engine/server.pygenerate_parley_options, teststest_parley.py); this PR is the viewer read + presentation side.TDD (RED → GREEN)
viewer/tests/test_readmodel_surfaces.py): npc block shape, explicit-id binding + pin, event-anchor fallback, explicit-wins-over-anchor, unknown-id graceful degrade, disposition band/value (incl. attitude_value 0 → "neutral").viewer/tests/test_dialogue_npc_header.py, new): render the REALParleyMenuunder the babel-vm Node harness (mirrorstest_chronicle_hygiene) — header names the NPC not the player, name is pinned independent of actor, no-npc keeps the actor header, DispositionDot renders when bound (incl. at 0) and not when unbound.Verification
bash qa/fast_gate.sh→ PASS (221 deterministic engine tests).viewer/tests/test_readmodel_surfaces.py -k parley(14) +test_dialogue_npc_header.py(7) + enginetests/test_parley.py(47)._load_engine_server()→ None in the local viewer env; bestiary/build_options/save_export/director). They fail identically on the clean baseline and pass under CI's installed engine — untouched here.Refs #751, #615
Summary by CodeRabbit
New Features
Tests