Skip to content

Next phases (as of 2026-08-26)

Status: working plan, not an ADR or a ladder amendment — informal coordination doc, revise freely as work lands.

Course correction (advisor-caught): everything in §0 below is real, tested, headless plumbing — and every bit of it is invisible in Skyrim. This doc had been sequencing off what the ladder had left unbuilt, not off what closes the distance to the actual goal (docs/vision-v2.2.md): a Skyrim world that visibly reacts to the player. Two concrete blockers for that, now promoted to the top of this doc: the seam's "out" direction (sim state → game, adapters/skyrim/README.md's charter) still doesn't exist in a visible form (writes land in save-relevant game state, but nothing has been confirmed to visibly change in a running game) — and the named-cast gap, real when this note was written, closed substantially two minutes later in the same session: IdentityMap.cpp's kNamedCast grew from 1 to 19 entries against 28 live-captured Whiterun NPCs (2f27cc8, see §0c below). Correction added 2026-08-27: don't cite "exactly one entry" as current status — it describes a state that existed for about two minutes. 19/28 is current as of edd6989.

0o. Landed: full 171-pair avoidance table, verified DevBench runbook automation with a real seeding recipe (48d827c, c32e307, 7d13012)

Closed the last two gaps in the live-testing prep. (1) AvoidanceGlobals. cpp's C++ lookup table was expanded from 4 hand-picked illustrative pairs to the full 171 the patcher generates, via a new, re-runnable tools/generate-avoidance-globals-table.py — previously any grudge pair other than the 4 would silently no-op on the game side even though the patcher had already authored real content for it. Verified entry-by-entry against the source JSON (zero missing/extra/mismatched), rebuilt clean, redeployed to ChronicleDev. (2) tools/ chronicle-devbench-runbook.py (a DevBench REST-API automation script for the verification runbook) initially believed hydration/vendor- markup/evidence's seeding preconditions were simply impossible via chronicle inject (only npc_died/crime_witnessed/rumor_heard are injectable; grudge_formed/belief_formed are trace-derived, not events). A follow-up pass found the real two-step recipe instead: inject a real crime_witnessed event, then reattach a Driver to the same run and call Driver.crime_witnessed() on it — the same rule-12 cascade a live tick loop uses. Verified end-to-end against fresh scratch runs for all three shapes (NPC-NPC grudge, NPC-player grudge, bystander-only belief). Independently re-verified myself and found a real bug in the id-generation scheme (a bystander seed's absence of a grudge left an id counter un-advanced, causing a claim-id collision on a later seed) — reproduced, fixed by keying ids on the injected event's own monotonic seq instead, and re-confirmed 4 sequential seed calls now succeed cleanly. The runbook (docs/design/ chronicle-bridge-verification-runbook.md) now has real, tested commands for every one of its 4 test paths — nothing left in it depends on an untested claim about how to seed state.

This is very likely the end of the safely-dispatchable, no-sign-off backlog for this pass. Every ChronicleBridge write path is built, deployed into ~/Games/ChronicleDev, and has a corrected, verified runbook plus a real automation script ready. What's left is either gated on the owner's sign-off (rule 11 hysteresis) or requires the owner's own hands (an actual live-game session — the one thing no amount of further research/dispatching can substitute for).

0n. Landed: EvidencePoller, whole-tree build, ChronicleDev deployment, home-location research (3f5489b, 5b61234, a34caa5, ChronicleDev deployed)

Closed the diegetic-evidence slice end to end and moved ChronicleBridge from "compiles" to "actually installed in a launchable dev environment" for the first time. In order: (1) the full 7-slice/24-file ChronicleBridge tree was synced and built together on the Windows build machine for the first time — previously only individual files had been spot-checked; zero errors/warnings, confirming no cross-slice integration issues. (2) EvidencePoller.h/.cpp written, wired into plugin.cpp, and built clean — the C++ consumer for diegetic evidence, resolving a believer's Actor* via IdentityMap and calling PlaceObjectAtMe. (3) An external AI's manual live-game test script was fact-checked against real project state and real console-command references; 2 of its 4 tests targeted the wrong mechanism entirely (vendor markup and evidence are driven by injected Chronicle events, not vanilla console state) — corrected into docs/design/ chronicle-bridge-verification-runbook.md. (4) The placeholder Gold001 evidence object was replaced with a real authored MiscItem record (tools/chronicle-patcher/src/EvidenceItemPatchBuilder.cs), verified via a real patcher run and Mutagen read-back. (5) docs/research/ 32-npc-home-location-data-spike.md closed report 31's deferred location-modeling question: real home-location data exists in Skyrim's package system but is unevenly covered across NPCs and not worth building — the already-shipped live-position proxy stays the answer. (6) The compiled ChronicleBridge.dll and the real 171-pair ChroniclePatcher.esp were deployed into ~/Games/ChronicleDev's mod folders and load order (verified: correct MO2 mod-folder shape, correct load order after all 4 real masters, DLL re-synced after the evidence- object rebuild to avoid deploying a stale binary). The game itself was NOT launched — that's the owner's own next step, with a corrected, verified runbook now waiting.

0m. Landed: diegetic evidence, Python-only cut (20c478b) — seventh ChronicleBridge slice

A Kimi-conversation architecture-delta proposal was audited (docs/design/kimi-architecture-delta-audit.md) against existing ADRs; most of it was already-decided or too expensive (Named Pipes IPC would rewrite the whole HTTP-based seam for no demonstrated problem), but "Diegetic Evidence" — belief/evidence state showing up as physical objects in the game world — was genuinely new and cheap. A follow-up research pass (docs/research/31-diegetic-evidence-object-placement- spike.md) verified [BUILD-ON]: RE::TESObjectREFR::PlaceObjectAtMe() and Enable()/Disable() are both documented, non-reverse-engineered API, confirmed against real .cpp sources and two real shipped plugins. The real constraint was Chronicle's own data model, not the engine — no NPC has home/incident-location data — so the design (docs/design/ chronicle-bridge-diegetic-evidence-out.md) scoped the cheapest path: spawn at an NPC's own already-tracked live position, gated on BeliefInstance.confidence (decayed via the existing chronicle.claims.decay(), not a new formula over Evidence.strength, which doesn't decay). Zero new rules, zero rule-budget spend — the budget stays at 19/20, one slot open for rule 11 hysteresis if it's ever approved. GET /whiterun/evidence + POST /whiterun/evidence/ack, single-keyed (not paired, unlike the other three slices), two-outcome ack, applied a true one-shot terminal state (no retraction/re-fire — a named limitation, not a bug). 344→349 chronicle/scenarios tests, 61→75 listener tests, zero regressions. C++ side (EvidencePoller) stays explicitly unscoped, like every other write path's live-game verification.

Also worth noting from this same work: an IdentityMap.cpp plugin- attribution "fix" was made, found wrong via a live-capture cross-check, and reverted same day (78dd0ec) — TESForm::GetFile(0) returns whichever plugin currently wins a record's override chain, not its static originating master, so the original HearthFires.esm/USSEP attributions were correct all along for the C++ side; the patcher's C# mirror legitimately keeps Skyrim.esm instead, since Mutagen's FormKey addresses records by originating master regardless of override. The two tables are intentionally not verbatim mirrors for those 5 rows now. Separately, avoidance's game-side is no longer inert: a real patcher run against ~/Games/ChronicleDev/'s data succeeded in full (171/171 pairs), and AvoidanceGlobals.cpp's 4 illustrative pairs carry real FormIDs instead of placeholders (bf0ae82).

0l. Landed: crime_witnessed cascade, Python-only cut (b644675)

crime_witnessed existed as a tier-0 canonical event kind (chronicle/events.py) with zero cascade wiring — same "objective log only" state npc_died was left in until the death-extraction slice. docs/research/29/30 established the C++ side needs real, separately- scoped R&D (no clean event sink, an open question about whether extraList polling yields real vanilla witness data); the design-prep pass (docs/design/chronicle-bridge-crime-witness-out.md) determined the Python cascade needs neither — Driver.crime_witnessed() dispatches to witness() (always) and suffer_harm()/rule 12 (only when the witness is the crime's own victim), reusing two already- registered primitives with zero new rules and zero rule-budget spend, so no owner sign-off was needed here (unlike rule 11 hysteresis, still parked). Extends the existing POST /whiterun/events contract with a crime_witnessed variant rather than a new route. 340→344 chronicle/scenarios tests, 57→61 listener tests, zero regressions. C++ side stays explicitly unscoped — report 30's two open questions (witness-set real-data uncertainty, event-detection R&D-spike tier) are not resolved by this cut.

0k. Landed: vendor-markup's price-write hook (d8dd01d) — sixth slice, first vtable/UI hook

docs/research/28's [BUILD-ON] finding turned into real code: VendorPriceHook.{h,cpp} swaps RE::VTABLE_BarterMenu[0]'s PostCreate vfunc, then (only for a vendor with a real player-directed markup entry) swaps the barter menu's "UpdateItemCardInfo" ActionScript callback to multiply each row's price. New VendorMarkupCache filters the poll to target_id == "the_player" and fixed a real race the implementing agent caught in review (the detection sink and this cache both hitting the listener's single- delivery delta feed independently would have starved each other) — deliberately does not ack applied pairs, unlike hydration/avoidance, since acking an in-process-only cache would permanently starve it across a plugin restart. Compiled clean. Two live-game caveats remain, stated plainly, not resolved by this slice: whether the multiplied value drives real transaction gold vs. only the displayed figure, and whether PostCreate fires before or after the vendor resolves. This closes vendor-markup out to the same "compiled, not live-tested" status every other ChronicleBridge write path carries.

0i. Landed: vendor-markup slice, Python-only cut (f1b1873, fifth ChronicleBridge slice)

docs/research/23-v03-hysteresis-and-action-verbs.md's ranked #2 action verb. chronicle/vendor_markup.py's markup_multiplier_for(): 1.0 below a severity floor of 0.2 or once cooled, linear ramp to a placeholder ceiling of 1.5 otherwise — never below 1.0, so it can never imply a price under fBarterBuyMin's real 1.05 floor (enforcing that floor is the eventual game-side consumer's job). GET /whiterun/vendor-markup + POST /whiterun/vendor-markup/ack mirror the poll/ack protocol, directed like hydration (not symmetric like avoidance), two-outcome like avoidance (no no_relationship-equivalent case — a vendor-markup write has no dependency on a pre-existing authored vanilla record). Independently re-verified before commit: both test suites re-run (340 + 57 passing), ruff re-checked against the pre-existing baseline (1 pre-existing finding, zero new). No C++ half yet — same split as hydration/avoidance, needs its own research pass on the barter-menu-open price-write hook.

0h. Research: CK-GUI avoidance blocker retracted; DevBench verified as a real test-automation path (d60cb95)

Two research docs dispatched in response to a standing instruction not to accept my own unverified "needs X, out of reach" claims without actually checking:

  • docs/research/24-programmatic-esp-authoring.md retracts §0g/chronicle- bridge-avoidance-out.md §2b's "needs Creation Kit GUI access" claim. Mutagen (MIT C#/.NET, the Synthesis-patcher library) creates brand-new PACK+CTDA records and links them to NPCs/factions entirely headlessly (dotnet run over SSH, no Bethesda tool). Avoidance's C++ half is reclassified from "blocked, needs owner/CK access" back to "real, unblocked, unbuilt" — same status as hydration's/vendor-markup's own game-side halves. The CK genuinely has no headless authoring mode (that half of the original claim held), it just was never the only route.
  • docs/research/25-devbench-skse-mcp-verification.md verifies a secondhand claim the owner surfaced from a separate conversation: alandtse/devbench is real, active, and does run console-command execution + save/load + state inspection over MCP/REST on 127.0.0.1:8920 (GPL-3, not MIT as claimed, aside from a small MIT-licensed C-ABI shim). Could drive most of chronicle-bridge- verification-runbook.md's steps from an agent session once a live game exists with both plugins loaded — doesn't eliminate launching the game, one-time install, or tunnel setup.

Neither doc changes any shipped code. Both close a "genuinely blocked" claim this project had been carrying without ever checking it.

0j. Landed: avoidance's game-side half, both sub-slices (3abf882, 126c625)

The C++ consumer (AvoidancePoller) and the Mutagen content generator (tools/chronicle-patcher/) were dispatched in parallel against docs/design/chronicle-bridge-avoidance-mutagen-out.md's original per-NPC/linked-ref plan. The C++ side found, against the real headers, that the plan's runtime linked-ref mechanism doesn't exist (RE::TESObjectREFR has no safe setter) and built a per-pair-global fallback instead. This left the two halves incompatible on delivery — a real mismatch, not a hypothetical one — caught in review before committing the patcher's original output, and fixed with one more dispatched pass that rewrote the patcher to the per-pair design (171 pairs, all 19-choose-2, two hardcoded-target Flee packages + one shared gating global per pair). Both sides' naming/canonicalization independently re-verified to match exactly. Design doc updated in place to record the correction rather than left stale.

Genuinely all that's left on avoidance now: running the patcher against the owner's real Skyrim data folder (no game data exists in this headless environment) and copying the resulting real FormIDs into AvoidanceGlobals.cpp's placeholder table — a mechanical one-time step, not a design or code gap. This closes the "needs Creation Kit access" blocker docs/research/24 retracted (0h below) all the way to a real, compiled, tested implementation on both language sides.

0. Landed

  • Rules 12 (grudge-creation) and 13 (grudge-decay) — the scenario ladder's last two stubbed rules — are now real (c6d047d). Driver.suffer_harm() is the first grudge cascade that fires without a scenario/console script explicitly calling form_grudge().
  • chronicle/sync.py (ADR-0005's RESOLVE table, epoch fencing, dedup) and chronicle sync-check <run_id> --manifest '<json>' (docs/design/chronicle-sync-cli-integration.md) — c5aa674, eea96c1.
  • chronicle fork <run_id> --at-tick T — on-disk fork support, copy-forward (docs/design/fork-on-disk-support.md, d3f2e6c). Caught a real bug in review: cli._branch_identity() used to trust a run's first record's envelope for its generation, which broke the moment a forked run's copied prefix legitimately carries the parent's generation on its earliest records. Fixed (registry-first, record fallback), regression test added.
  • sync-check --apply (c10c71a) now actually calls fork_run() for FORK/ADOPT instead of only reporting them.

This closes out the entire ADR-0005 sync-handshake thread as far as it can go headlessly. What's left there — the C++ shim side and the dashboard UI for triggering a fork (ui-spec.md §3.1) — needs the Windows build machine, a live game, or dashboard-lane work respectively.

0b2. Standing capability discovered: the Windows build machine is reachable

SSH access to the owner's Windows machine (geoff@192.168.0.211) works and has a fully set-up ChronicleBridge build toolchain (VS2022 Build Tools, CMake+Ninja, vcpkg already bootstrapped) — confirmed by a real clean build during this session. Connection details and the exact working build recipe (the MSVC dev environment isn't loaded by default over plain SSH — needs vcvars64.bat sourced via cmd.exe in the same invocation as the build) are in .claude/windows-build-machine.md (gitignored, local-only, per the owner's explicit request — re-read it at the start of any future C++ work rather than rediscovering this).

This retires every prior "needs the Windows build machine, not attemptable from this session" caveat this document and its predecessors have repeated about ChronicleBridge C++ work. Compiling and verifying C++ changes is now directly reachable from a headless session. What's still NOT reachable: actually running Skyrim/MO2 and observing live in-game behavior — that remains the owner's own interactive desktop session (see root HANDOFF-*.md files, tools/launch-ngvo-skse.sh). "Compiles cleanly" and "verified against a live game" are two different claims; keep them distinct in every future report on ChronicleBridge C++ work.

0g. Landed: avoidance slice, Python-only cut (282c678, fourth ChronicleBridge slice)

docs/research/23-v03-hysteresis-and-action-verbs.md's ranked #3 NPC action verb, chosen because it extends rule 18's already-built, already- tested avoidance mechanism rather than inventing new Chronicle state. chronicle/avoidance.py's is_avoiding() re-derives rule 18's own condition (imported constant, no duplication). GET /whiterun/avoidance + POST /whiterun/avoidance/ack mirror hydration's poll/ack protocol, adapted for being symmetric (canonicalized pair ordering) and two-outcome (no no_relationship-equivalent case exists for avoidance). Designed with the ack timeout from day one — no retrofit needed this time. No C++ work yet — the game-side AI-package consumer of this state is real, separate future work, its own design pass (which CommonLibSSE-NG package-condition mechanism to use).

0f. Landed: hydration-out's C++ poller (fad0d79) — first WRITE path, unverified

All three ChronicleBridge slices (spatial streamer, death extraction, hydration) are now built end to end. This one is different in kind from everything before it: it's the first code in the whole project that writes to a live game object (RE::BGSRelationship::level), not just observes one. Compiled cleanly, independently re-verified with two full clean rebuilds. Never run against a live game or a real save — that must stay true in every future summary of this work until someone confirms it manually in an actual play session. Real findings from the build: TESDataHandler::LookupForm<Actor> resolves a placed reference directly (simpler than guessed); BGSRelationship::AddChange is needed to mark the write dirty for save serialization (documented API, not confirmed sufficient). Ruled scope held: only updates an existing relationship record, never creates one.

The "delivered before confirmed" protocol gap named above is now closed (9e0b462). POST /whiterun/hydration/ack reports exactly what ApplyHydrationPair did (applied / no_relationship / retry); the listener's state machine only latches a pair as settled on an explicit ack, and a no_relationship latch is scoped to the exact rank it was recorded against — a later rank change always gets a fresh offer. Found and fixed in the same pass: a genuinely dropped ack (not an explicit retry) had no timeout and could leave a pair stuck forever on a long-running listener process — closed with a 60s _AWAITING_ACK_TIMEOUT_SECONDS. A first attempt at that fix broke an existing test by conflating "never offered" with "expired"; fixed and independently re-verified (both the Python state machine and a full C++ clean rebuild) before committing.

0e. Landed: hydration-out's Python-only slice (e3e4b20, 3044e14)

SocialStateStore.reputations(), the pure chronicle/hydration.py bucketing function (grudge severity → Skyrim relationship-rank scale, decay-aware, reputation deliberately deferred), and GET /whiterun/hydration on the listener — the one documented, narrowly- scoped exception to "never import chronicle/ directly" (justified because this route has no write path to protect). In-memory idempotency cache, explicitly doesn't survive a listener restart (named gap, not silent). 323 + 15 tests pass. Still nothing on the C++ side — no poller exists yet to call this endpoint or actually invoke SetRelationshipRank in-game; that's the natural next slice now that both this and death-extraction have proven the "In"/"Out" split works and the build machine is reachable.

0d. Landed: death-extraction's C++ half (6ec2406)

DeathEventSink, wired into plugin.cpp and OutboundClient, compiles cleanly (independently re-verified with a full clean rebuild, not just trusted from the report) against the real commonlibsse-ng@3.6.0 toolchain via the newly-discovered SSH build access. This is the second ChronicleBridge slice fully specified end to end (Python listener half already existed; the C++ half was the one thing left). gamets comes from RE::Calendar::GetHoursPassed(), matching a wire-contract field description that was already written before this code existed. Compiled, not live-tested — no death event has ever actually been observed or POSTed by this code; that needs the owner's own game session.

0c. Landed: named-cast growth (2f27cc8)

IdentityMap.cpp's kNamedCast grew from 1 entry (ysolda) to 19, using the exact plugin/FormID pairs observed in a real Whiterun snapshot (whiterun-positions.json), independently verified against that JSON programmatically — zero transcription errors. Matching Python fixture growth (chronicle/fixtures/whiterun_schedule.py/ whiterun_relationships.py): a schedule block per new NPC, and only 5 relationship edges for pairs with confident vanilla-lore backing (Carlotta/Lucia, Amren/Saffir, both feuding households, Sigurd/Adrianne) — deliberately schedule-only for the rest rather than inventing ties. A regression test keeps the two hand-synced sides (C++ table, Python fixture) from silently drifting apart. Still unverified beyond direct textual comparison — the C++ side needs the Windows build machine and a live game to confirm it actually compiles and resolves correctly.

1. Landed: trust-discounted retelling (rule 20, 472f3f8)

Every design question is ruled — via Kimi + advisor, code-verified, not owner opinion (session policy: a domain/tuning disagreement gets resolved by consulting them and verifying the discriminating fact in code, not bounced back to the owner — docs/loop-playbook.md):

  • No-relationship pairs get trust=0.5, not the undiscounted flat 0.8 — verified Relationship.strength has no distrust range at all ([0,1], hard-gated; distrust lives only in Grudge), so a weak tie and no tie are the same kind of signal, not neutral-vs-distrusted.
  • Trust discounts confidence only, never verbatim_strength/ gist_strength — the two axes are deliberately orthogonal (source credibility vs. memory precision).
  • colocation is excluded from the trust lookup (kinship/faction/ shared_employer only, max strength across bases) — verified colocation edges are hand-seeded fixture constants that never update, tracking no real signal.
  • The contested-resolution path (claims.py's T2.3 challenger-wins branch) inherits the same discount, applied consistently.

docs/scenario-ladder.md §8 is amended (c251f36): the O4 consolidation ruling it never absorbed (rules 9+10 are one rule, per chronicle/ rules.py's own docstring) is now recorded, and rule 20 lands exactly at the ~20 ceiling with no further consolidation or ceiling raise needed.

Reviewed and committed. claims.py's retell()/resolve() take an optional trust (confidence only, None byte-identical to before); driver.py gates the relationship lookup on the new rule, disabled by default reproducing exact prior behavior; T1.1's fixture explicitly disables rule 20, preserving its flat-0.8 assertion. The implementing agent also caught and fixed a real bug on its own: framelog.py's post-keyframe replay path wasn't forwarding trust_applied, which would have silently diverged live-vs-replayed confidence values. 312/312 tests pass. This is the ladder's 20th and last rule at the current ~20 ceiling — any future new mechanism needs a fresh consolidation ruling or an explicit ceiling raise.

2. Flagged, not scheduled: v0.3's real remaining gaps (updated 2026-08-26)

docs/vision-v2.2.md §6's "v0.3" is mostly already built — thresholds (rule 11), hysteresis (doctrine 3), grudges (12/13), obligations (14), and named relationships (social.Relationship) all exist and are ladder-tested. What's still genuinely open, if a new rung ever gets opened:

  • Rule 11's latch is one-directional — trips but never untrips. Fine for "four thefts escalate," not sufficient for CK-style relationship demotion, which needs separate entry/exit thresholds. docs/research/23-v03-hysteresis-and-action-verbs.md's Part A found no literature (CK, Dwarf Fortress, RimWorld, academic bounded-confidence models) with a purpose-built bidirectional model either — the transferable pattern is generic control-theory hysteresis (a separate, lower de-escalation threshold plus a dwell count) layered onto the existing rule-family idiom, not a new subsystem. Still the single hardest real open design problem left in the whole project — it needs a rule-budget consolidation ruling or ceiling raise (the ladder is at its full 20-rule ceiling), a real ADR-weight decision. Not implemented; per the narrowing below, researching and design-prepping it further is not blocked, only spending the ceiling is a heads-up.
  • NPC action verbs — all three now real, no longer a gap at all. #1 (dialogue-gating on GetRelationshipRank) is delivered by hydration's SetRelationshipRank writes (§0f), compiled, unverified in-game. #3 (avoidance) is now fully built end to end across all three layers (Python §0g, C++ poller + Mutagen-authored content §0j) — the Creation-Kit blocker below was retracted (docs/research/24). #2 (vendor markup) has its Python half (§0i) and C++ detection half (e41866d) built; the actual price WRITE is correctly scoped as its own R&D spike (docs/research/26 — needs a reverse-engineered function-detour hook, this project's first, a materially different risk tier than everything else built so far).
  • Named-cast identity gap — closed. Grown from 6 to 19 NPCs matching real live-observed Whiterun actors (§0c), independently verified against source data.

Two-way hysteresis would still need a fresh rule-budget consolidation ruling or ceiling raise before landing as a new Chronicle-side mechanism — researching it further is not blocked; implementing it is a heads-up-worthy scope spend, not a stop.

3. Genuinely stops the loop (narrow — most things don't belong here)

  • git push to the remote — always ask first, no exception.
  • Actually testing anything against a live Skyrim process/real save — the SSH build machine (§0b2) closes the "can't compile C++" gap, but running the game is still the owner's own interactive session, not reachable from here. docs/research/25's DevBench finding means this gap may be more automatable than previously thought, but that's future work, not a change to this stop-condition today.
  • Creation Kit content authoring — RETRACTED, was never a real stop. docs/research/24-programmatic-esp-authoring.md found Mutagen authors new PACK/CTDA/Global records entirely headlessly (no CK GUI, no Windows machine even — runs on this Linux box via a locally-installed .NET 8 SDK). Proven out end-to-end by avoidance's slice (§0j). Do not re-list this as a stop condition in any future summary of this project — it was an unresearched assumption, corrected and closed.
  • A genuine, different-in-kind risk category exists now: reverse-engineering an unmapped internal engine function (needed only for vendor-markup's price WRITE, docs/research/26). This is a real scoped spike (SigMaker/Address Library work), not a stop — but it's the first time this project has needed anything beyond a documented RE:: API or a plain event sink, worth a heads-up when picked up.
  • Spending a rule-budget slot on new scope (§2's hysteresis item) deserves a heads-up in the commit message, not a stop — but committing to it silently, with no note, would be the wrong kind of quiet.

Narrowed per repeated, explicit owner feedback (this session): researching whether a mechanism is feasible, what the literature/prior art says, or producing a design doc is NOT "opening new scope" and does not stop anything — only actually spending the rule-budget ceiling on implementation does, and even that only needs a heads-up, not a pause. When domain-expertise is missing (a modeling choice, an API's real behavior, a literature question) the answer is Kimi/advisor/a research agent followed by an executive decision, every time — not a question back to the owner. Kimi being temporarily out of quota (2026-08-26) is not a reason to fall back to asking; use a research agent instead.

Frozen documents (docs/ui-spec.md, docs/scenario-ladder.md, docs/ui-doctrines.md) are not automatically stop-and-ask either — see §1's rule-20 amendment for the standard: a reviewed design doc that rules cleanly on its own questions may amend a frozen doc's stale content, reported afterward rather than asked about first.