Open Questions & Known Concerns
Document status: Living register. Captures unknowns, gaps, and deferred decisions so they are planned, not rediscovered as bugs — the project's working convention.
Scope. A single place to see what is not settled. Full entries for concerns first surfaced here; pointers for items that already live in another doc — this register indexes, it does not duplicate (duplication is itself a drift surface).
- The finalized MVP contracts:
03-story-graph-kind.md·04-core.md- The task list:
TODO.md· the MVP target:MVP.md
1. MVP-Relevant Gaps — All Resolved
Every gap that blocked the story-graph MVP has been decided and written into the contracts. Kept here as a decision log: what the question was, what won, and where the answer now lives — so a later reader finds the reasoning without re-opening the argument.
| # | The gap | Resolved as | Lives in |
|---|---|---|---|
| 1.1 | PlayerProfile was defined only in the simulation kind, but the MVP DoD requires cross-session achievements | A ProfileStore beside the session store. profileId on CreateSessionConfig, never on NewGameConfig or GameState; records keyed campaignId + achievementId; the store upserts after a successful action; no profileId → anonymous, no read or write; missing/corrupt loads empty with a warning; a failed write never rolls back the game action | 04-core.md §7.1 · 03 §7 |
| 1.2 | Base reason codes had no player-facing strings | The core ships default-English messages under a reserved core.reason.* namespace. Registry merge rejects overrides — a campaign cannot restyle an engine-level error. Validation fails if any registered code lacks a message | 04-core.md §12 |
| 1.3 | The authoring → registry build step was prose, not a type | A typed source/runtime split: AuthoredText, per-kind …CampaignSource, a pure builder returning BuiltCampaign. Parsing and file I/O live in an outer adapter. One locale (English) for the MVP | 04-core.md §10.1 · 03 §1 |
| 1.4 | Could a campaign settle straight to an ending at turn 0? | Valid, and it plays. Validation emits a Tier 2 no_reachable_choice — warns the author without banning vignettes or single-scene fixtures | 04-core.md §11 · 03 §11 |
| 1.5 | Kind.initialState returned a bare KState, so a start that settled to an ending was recorded active | initialState returns InitialStateResult<KState> — AdvanceResult minus error, since a pre-validated campaign cannot fail to start. The core takes status from it and never inspects kindState | 04-core.md §3, §4 |
| 1.6 | params were written to the replay log but never handed to the kind | Kind.advance receives params. The story-graph kind declares none and rejects a non-empty object with unexpected_params — never silently ignored | 04-core.md §3 · 03 §8.2 |
| 1.7 | The story-graph kind declared no reason codes, and hidden-choice rejection was undefined | Three added codes (not_a_choice_node, unexpected_params, settle_guard_tripped) plus base reuse. A showWhen-hidden choice returns unknown_action — identical to a nonexistent id, so a probing client cannot confirm a secret path exists | 03 §8.3 |
| 1.8 | GameState.formatVersion and SaveEnvelope.saveFormatVersion both versioned "the format" | Both kept, distinction documented. Engine.serialize/deserialize round-trip a bare envelope with no wrapper — the golden files compare exactly that string — so the envelope needs its own stamp | 04-core.md §2, §10.2 |
Nothing MVP-blocking is currently open. When the next gap appears, add it here as a full entry, and move it into this table once decided.
2. Deferred by Decision — Post-MVP (Indexed; Live Elsewhere)
Settled as out of MVP scope. Listed so they resurface deliberately, not by accident.
- Provisional simulation numbers — drift rates, scenario economics,
demandBandthresholds, housing-quality formula, travel costs. Need a balancing pass once the sim harness runs. (TODO.md→ Known open items; simulation kind.) wisdomattribute has no consumer — needs one to earn its place (games/04-engine-specification.md§8.4).packages/vssrc/engine/naming — the simulation docs (games/05-text-client.mdheader,games/04§20) describe an aspirationalpackages/monorepo; the code issrc/engine/(Engine Package). The resort draft proposedpackages/a third time (12 §17 declines to restate it), so this is now three documents against one layout. Reconcile when the layout is actually built out.historyin the simulation kind's state — the upstream model carrieshistory: HistoryEntry[], a narrative record of what happened. That overlapsStateChange[], whichadvancealready returns (04 §12), and the event stream (05-observability.md). Three records of the same events is the duplication rule10-simulation-kind.md§2 exists to prevent, sohistoryis not adopted until it is established what it holds thatStateChangedoes not — most likely player-facing narrative framing, which would make it a projection concern rather than state. Revisit when the simulation kind's field detail is ported (10 §14). The same question arises forworld-graph, which declineshistoryon identical grounds (12-world-graph-kind.md§3) — resolve both together or not at all.previewAction, and the tenth API operation it implies — a spatial kind must be able to check a parameterized action before committing it, and the only check available today is to submit and rely on rejection leaving state unchanged (04 §4). That routes a read through a write path, and clients hold projections rather than state (09 §1), so they cannot call the pure engine themselves. 12 §7 specifiespreviewActionas the sameadvancecall with the result discarded — it cannot drift, which is why a separate validator was rejected. The consequence is that09-clients.md§4's coverage checklist becomes ten operations and ten MCP tools rather than nine and nine. That checklist is an MVP Definition-of-Done item and this kind is post-MVP, so 09 is deliberately not amended yet. Revisit when theworld-graphkind is built — and amend 09,MVP.md§5 and the MCP surface (04 §13) in one change, not three.- A shared simulation substrate for tick-driven kinds —
simulationandworld-graphare the same archetype: mutate pending configuration, then resolve a block of simulated time through an ordered system pipeline (12 §2). Both hand-roll that pipeline, and it is where determinism defects concentrate — the two-phase time ordering in 10 §3 is exactly the class of bug a shared, tested runner would stop recurring per kind. ASystemPipelinein the core (ordered registration, deterministic per-system stream keying, stable iteration, derived entity ids) would make kind N+1 cheaper. Not extracted now: one built instance is not a pattern, andsimulationis not built. Revisit when the second tick-driven kind is actually implemented, so the abstraction is drawn from two real cases rather than one and a specification. - Third-party kinds, and the sandbox they would require — architecture §1 N2
rejected downloadable code kinds as a security and reproducibility hazard, and
06-extensibility.md§7 leaves that standing. It is a rejected mechanism, not a closed question: a WASM host with a deterministic ABI — no clock, no ambient float nondeterminism, fuel-metered — could satisfy 06 §2's rule. Revisit when there is a concrete demand for kinds the engine team did not write; the conventions in 06 §8 are chosen so that revisiting costs no rework. - Observability beyond the event channel — the OpenTelemetry exporter, sampling, and
inbound trace-context propagation; metrics as a channel separate from events; per-kind
log-level configuration; author-facing presentation of
kind.story-graph.*events. The event contract itself is MVP scope and specified; these four are deliberately not (05-observability.md§13). The first belongs with the hosting layer, which is itself deferred (MVP §4). - Doc-tree numbering merge — the engine specs and the game specs both start at
01-. This was a live problem when they shared one tree; the repo split (02-architecture.md§12) has largely dissolved it. Open: confirm nothing depends on a merged numbering and close the item.
3. Judgement Calls to Revisit (Settled for the MVP)
Decided deliberately, each with a documented "revisit when." Listed here only as a pointer so they are not forgotten.
- Story-graph kind — dropped the
stringvariable type; nounlockconsequence;autovs a one-transitionrandom;SETTLE_STEPS= 64;visitedcounts every entry. See03-story-graph-kind.md§13. - Core — the
Conditionoperator set is frozen; additions require a concrete campaign need. See04-core.md§18. - Core — randomness is derived, never carried: streams are a pure function of
(seed, streamId), soGameStateholds no generator state and theStreamId→ string encoding is normative. Revisit only if a kind needs a generator that outlives one resolution. See04-core.md§8. - Story-graph —
StoryGraphViewcarries only what the genericScene/PlayerViewdo not (turn, visible stats, achievements, ending); scene text and the choice list are the core's. Revisit if a client proves it needs a self-contained kind payload. See03-story-graph-kind.md§9.
Add to this register whenever a decision is deferred or an assumption is made — rather than leaving it in a commit message or a chat, where the next person will not find it.