Content packs compose at campaign granularity, but dynamic content injection needs node granularity
A design handoff proposing "dynamic campaign content" — side quests injected into an existing scene, content composed across campaigns, campaigns published without an engine rebuild — was evaluated against this codebase. Most of what it proposed to prove is already decided and exported. One thing it wants is genuinely not expressible today, and that is the only open question worth spiking.
Already settled, and the proposal did not know it. resolvePacks, applyExperimentGates, computeResolutionId, ContentPack and PackRef are contract exports from the package root, specced in 11-content-packs.md, with src/engine/src/index.ts stating outright that the composition they serve is host-side. ContentRegistry.resolution is already a digest over the ordered pack set, and §6 of that spec is "Identity, and Why Determinism Needs It." The proposal's central thesis — content resolution belongs above the engine, the engine consumes resolved content — is the architecture this package already contracts for.
What is not expressible. resolvePacks replaces campaigns wholesale by id and strings per key. It cannot express "this scene gains one additional choice." Injecting a side quest into an existing node today means republishing an entire replacement campaign, which is not composition in any useful sense — the delta is the whole document. That is the real gap, and it is narrow enough to answer against the existing contract rather than by building anything.
Three things constrain any answer:
- Choice labels are
AuthoredText, resolved through registry.strings, which is built once when the registry is built. A node-granular delta has to carry loc keys and string-table entries, not raw display text — the shape composition already uses per key, but at a different level.
- Injected content is by definition outside the validated set. A choice whose target does not resolve reaches
requireNode, which throws — on the read path, not at build time. Whatever expresses a delta has to be validatable by the same tiered pipeline a folded registry already goes through, or it moves a build-time failure to runtime.
- Whatever a delta is, folding it must still produce a stable
ResolutionId. That identity is what makes replay reproducible across a composed content set; a composition mechanism that cannot be digested deterministically is not usable here regardless of how convenient its authoring shape is.
The narrow question this issue exists to answer: can a side quest be expressed within the current pack contract, and if not, what is the minimal extension that makes a node-granular delta expressible while keeping validation and ResolutionId intact?
If the answer is that packs already suffice via some composition of existing primitives, this is configuration and there is nothing to build. If it is not, the extension is a contract change and belongs in the design pipeline before any code.
Done when
Agent instructions
Origin: an external design handoff ("Dynamic Campaign Content — Revised Design Handoff & Spike Proposal") evaluated against this repository. That document is not authoritative here and several of its premises about this codebase are wrong — see #293 for the session-identity half. Only the question stated above survived evaluation.
Read first: design/11-content-packs.md (§3 resolution, §6 identity, §7 validation), src/engine/src/core/registry/packs.ts, and src/engine/src/index.ts's comment on why the pack exports are host-facing.
Do not design a plugin framework, a middleware pipeline, a runtime graph-merge subsystem, or a persistence model in response to this. The question is whether an existing contract can express one thing.
Stop if: answering this requires changing ContentRegistry, the pack fold, or the export surface — that is a contract change and goes through design/ (brief → design → contract → slices), not an unattended implementation.
Note on determinism: Engine.scene(state) and availableActions(state) are pure and synchronous by contract (04 §1). Any proposal that resolves content during a scene read, rather than before the engine is constructed, breaks that and should be rejected on sight.
Content packs compose at campaign granularity, but dynamic content injection needs node granularity
A design handoff proposing "dynamic campaign content" — side quests injected into an existing scene, content composed across campaigns, campaigns published without an engine rebuild — was evaluated against this codebase. Most of what it proposed to prove is already decided and exported. One thing it wants is genuinely not expressible today, and that is the only open question worth spiking.
Already settled, and the proposal did not know it.
resolvePacks,applyExperimentGates,computeResolutionId,ContentPackandPackRefare contract exports from the package root, specced in11-content-packs.md, withsrc/engine/src/index.tsstating outright that the composition they serve is host-side.ContentRegistry.resolutionis already a digest over the ordered pack set, and §6 of that spec is "Identity, and Why Determinism Needs It." The proposal's central thesis — content resolution belongs above the engine, the engine consumes resolved content — is the architecture this package already contracts for.What is not expressible.
resolvePacksreplaces campaigns wholesale by id and strings per key. It cannot express "this scene gains one additional choice." Injecting a side quest into an existing node today means republishing an entire replacement campaign, which is not composition in any useful sense — the delta is the whole document. That is the real gap, and it is narrow enough to answer against the existing contract rather than by building anything.Three things constrain any answer:
AuthoredText, resolved throughregistry.strings, which is built once when the registry is built. A node-granular delta has to carry loc keys and string-table entries, not raw display text — the shape composition already uses per key, but at a different level.requireNode, which throws — on the read path, not at build time. Whatever expresses a delta has to be validatable by the same tiered pipeline a folded registry already goes through, or it moves a build-time failure to runtime.ResolutionId. That identity is what makes replay reproducible across a composed content set; a composition mechanism that cannot be digested deterministically is not usable here regardless of how convenient its authoring shape is.The narrow question this issue exists to answer: can a side quest be expressed within the current pack contract, and if not, what is the minimal extension that makes a node-granular delta expressible while keeping validation and
ResolutionIdintact?If the answer is that packs already suffice via some composition of existing primitives, this is configuration and there is nothing to build. If it is not, the extension is a contract change and belongs in the design pipeline before any code.
Done when
11-content-packs.mdcontractResolutionIdAgent instructions
Origin: an external design handoff ("Dynamic Campaign Content — Revised Design Handoff & Spike Proposal") evaluated against this repository. That document is not authoritative here and several of its premises about this codebase are wrong — see #293 for the session-identity half. Only the question stated above survived evaluation.
Read first:
design/11-content-packs.md(§3 resolution, §6 identity, §7 validation),src/engine/src/core/registry/packs.ts, andsrc/engine/src/index.ts's comment on why the pack exports are host-facing.Do not design a plugin framework, a middleware pipeline, a runtime graph-merge subsystem, or a persistence model in response to this. The question is whether an existing contract can express one thing.
Stop if: answering this requires changing
ContentRegistry, the pack fold, or the export surface — that is a contract change and goes throughdesign/(brief → design → contract → slices), not an unattended implementation.Note on determinism:
Engine.scene(state)andavailableActions(state)are pure and synchronous by contract (04 §1). Any proposal that resolves content during a scene read, rather than before the engine is constructed, breaks that and should be rejected on sight.