Skip to content

fix(platform,book): 2026-06-23 book QA triage (#152–#164)#165

Merged
ChicagoDave merged 12 commits into
mainfrom
fix/platform-issues-book-qa
Jun 23, 2026
Merged

fix(platform,book): 2026-06-23 book QA triage (#152–#164)#165
ChicagoDave merged 12 commits into
mainfrom
fix/platform-issues-book-qa

Conversation

@ChicagoDave

Copy link
Copy Markdown
Owner

Summary

Platform and book-text fixes from the 2026-06-23 book QA triage. Bundles fixes for issues #152#164.

Platform fixes

Book / docs

🤖 Generated with Claude Code

ChicagoDave and others added 12 commits June 23, 2026 15:48
The command validator excluded the player entity from all three
candidate-finders (getEntitiesByName/BySynonym/ByAdjective) via
`entity.id === getPlayer()?.id`, so `examine me`/`myself`/`self`/
`yourself` always failed with "You can't see any such thing." even
though the player has a full IdentityTrait with those aliases.

Remove the player exclusion (keep the room exclusion) so the player
resolves through normal name/alias/adjective matching. The AWARE
broad-search (hearing/smelling) path is left untouched.

Adds a reproduction test verifying examine me/myself/self/yourself
all resolve to the player entity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…155)

EventProcessor.processSingleEvent invoked each registered handler through
two paths: world.applyEvent() (WorldEventSystem dispatched the eventHandlers
Map directly) AND invokeEntityHandlers() (the same handler, wired into the
processor's storyHandlers by connectEventProcessor per ADR-086). So every
handler registered via world.registerEventHandler fired twice — e.g. region
boundary events (region_entered/region_exited) double-executed per crossing.

ADR-086 designates the EventProcessor as the single dispatch source and
explicitly rejected the "make applyEvent work" alternative for causing exactly
this double-processing. Make applyEvent only validate + record history; the
EventProcessor's invokeEntityHandlers is now the sole dispatcher.

chainEvent handlers were already single-dispatch (applyEvent never invoked
them), which matched the field report that Ch.13 chains worked but the Ch.9
region registerEventHandler probe doubled.

- WorldEventSystem.applyEvent: no longer invokes eventHandlers.
- Updated world-model event-sourcing tests to the new contract (applyEvent
  records but does not dispatch; register/unregister observed via wiring).
- New event-processor reproduction test asserting single dispatch through the
  real WorldModel<->EventProcessor wiring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#158)

Room-contents lists rendered comma-only ("A, B, C here") with no
conjunction, even though the lang layer has a list formatter that builds
"A, B, and C". Cause: three stdlib emitters of the contents_list message
(looking, going auto-look, switching_on) pre-joined item names with
", " into a string, so the {items} placeholder substituted that string
verbatim and no list formatter ever ran. Pre-joining also put English
list grammar in stdlib, violating the language-layer separation.

- stdlib: emit `items` as an array of names (drop the .join), so the lang
  layer owns the conjunction.
- lang-en-us: contents_list (looking + going) and looking.you_see now use
  the {list:items} formatter token, routing through listFormatter for the
  locale-appropriate "A, B, and C" output (matches book Ch.19 §19.6).

Adds a formatter test rendering the contents_list template from an array.

Note: the dead eventMessageFunctions.formatRoomDescription helper (no
runtime caller) is left untouched; the runtime path is the message template.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…69/070 amendments

Replaces the rejected "degrade visual line to sound" model with per-sense
rendering selection: each sense renders only the event descriptions keyed to
it, so a blind witness sees the sound description and a deaf witness sees
nothing, with no lossy downgrade between sense types.

ADR-069 amendment (2026-06-23): adds per-sense rendering selection as the
normative mechanism for witnessable event output; supersedes type-matching;
adds a normative Contract section pinning the @sharpee/core shared wire-type.

ADR-070 amendment (2026-06-23): corrects the stale responsibility table —
NPC action execution and npc.* event emission live in stdlib NpcService, not
the engine; witnessable movement event is sense-neutral (no sense field on
the event shape).

Both amendments passed devarch adr-review with the shared wire-type contract
pinned. Plan revised to include §3.0 shared wire-type, §4 Behavior Statements,
§5 state-mutation test, and npc.moved.witnessed rename.

Co-Authored-By: Claude <noreply@anthropic.com>
…announcements (#159)

NPC room crossings were silent; now an NPC with `NpcTrait.announcesMovement: true`
announces when it crosses the player's room via a `npc.moved.witnessed` event.

Implements the per-sense rendering model from plan-159-npc-movement-announce.md:

- packages/if-services: add shared wire-types `Rendering`, `PerSenseRenderings`,
  `SENSE_PRECEDENCE` alongside `Sense` (deviation from plan: placed here where
  `Sense` lives, not in @sharpee/core; plan doc updated accordingly)
- packages/world-model: `NpcTrait.announcesMovement` (default false) +
  `movementMessages` override map
- packages/stdlib/npc-service: thread `playerLocation` through
  executeActions/executeAction/executeMove/executeMoveTo; new `announceMovement`
  helper emitting `npc.moved.witnessed` with per-sense renderings map; use
  `getOppositeDirection` for arrival direction
- packages/stdlib/PerceptionService: generic rendering-selection branch in
  filterEvents (selects renderings[sense] by SENSE_PRECEDENCE)
- packages/stdlib/npc-messages: `NPC_HEARD_ARRIVES` / `NPC_HEARD_DEPARTS`
- packages/lang-en-us: `npc.heard_arrives` / `npc.heard_departs` templates
- packages/stdlib/vitest.config: add @sharpee/if-services → src alias
- Tests: 7 new npc-service movement tests (incl. world-state mutation assertion);
  7 new perception-service rendering-selection tests; stdlib 1190 passed

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On a sparse/replace channel, a `produce` returning `undefined` means
"no change this turn" — so the previous room's mood line lingered when
the player entered a room with no mood. Emit '' to clear instead (a real
value transition the renderer paints as blank, then sparse stays quiet).
Also document the undefined (no-change) vs '' (clear) distinction in
§24.6. Not a platform defect — the channel system works as designed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Captures the full session: opt-in NPC movement announcements (#159),
ambience channel doc fix (#162), and closure of issues #154/#155/#158.

Co-Authored-By: Claude <noreply@anthropic.com>
#157,#160,#161)

- §1.5/§31: sharpee init is an interactive wizard — add -y and document prompts (#152)
- §2.3: note the scaffolded stub (@sharpee/sharpee barrel + object literal) vs the
  book's class form; both satisfy Story (#153)
- §17.6: annotate (scope: any) on .where so it compiles under strict tsconfig (#156;
  cleaner platform overload tracked in #163)
- §18.1/§18.3: if.action.taking.success -> if.action.taking.taken (real ID) (#157)
- §22.6: correct Try-it — bleating stops on the daemon's own countdown, not feed goats (#160)
- §23.3: show the 4 missing awards so the 12 awards sum to 75 and victory is reachable (#161)

Documentation-only; no platform code changed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `examined_self` lang template is `{description}`, but the self path
in examining-data never supplied a `description` param: buildExaminingData
returned early for self without setting hasDescription, and the param
assignment in buildExaminingMessageParams was gated behind the
`!eventData.self` guard. Self-examine therefore rendered the literal
`{description}`. Newly exposed by the #154 scope fix, which let
`examine me` reach the report path for the first time.

Description is a universal property of any examinable entity (the player
included), so lift the params.description assignment ahead of the
self-guard and set hasDescription on the self branch. Trait dispatch
(container/supporter/etc.) stays self-exclusive.

Adds a regression test asserting the description param flows into the
examined_self event (verified to fail without the fix).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Republish stdlib carrying the examine-self description fix (#164); 1.1.2
was already published to npm. Sibling packages stay at 1.1.2 — unchanged
since that publish.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Zifmia is currently incomplete and not relevant to building stories in
Sharpee. Removes Chapter 32 ("Multi-User with Zifmia"), its book.yaml
entry, and the passing Zifmia mention in the architecture map appendix.

Co-Authored-By: Claude <noreply@anthropic.com>
@ChicagoDave ChicagoDave merged commit 2bb0791 into main Jun 23, 2026
1 check failed
@sonarqubecloud

Copy link
Copy Markdown

❌ The last analysis has failed.

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant