feat(world-data): hidden world data and story events - #101
Merged
Merged
Conversation
World packages can declare a worldData source as `visibility: hidden` (default `public`). Hidden sources may only feed a data contract and import into the receiver's reserved `_hidden.<namespace>` bucket; they cannot project into the lorebook, characters, world metadata, or media, and world projections skip them. Only the receiving plugin's runtimes can read hidden data. Public plugin-data APIs, the state panel, discovery, extension handlers, the LLM plugin-data tools, and `input.inject` all exclude or reject it, and generic plugin writes cannot rewrite it. Refs #97
Add the story-events plugin: a model-free pre-turn runtime that reads hidden events, the frozen dimension snapshot, and world time, and reveals at most one event per turn as a `story-event-cue@1` output. Conditions combine all/any/not over dimension paths and numeric time fields; events fire once by default or repeat after a cooldown, and a retried turn re-delivers the same cue. The public reveal record never stores the payload. Both narrators consume the cue through an optional `storyEvent` input. An end-to-end test proves the payload stays out of every prompt and public surface until its turn, then reaches only narration. Refs #97
Each bundled world now ships hidden events through story-events: heroine route moments in Haruka Academy, Frost's and Iron Meg's scenes in Mistport, a second echo and Eli's confession in Emberback, and three GM encounters in Lantern Barrow. Conditions only reference each world's own dimensions and world time; payloads are narrator briefs that do not resolve the main mysteries early. Refs #97
6 of 13 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add a framework-level way for world packages to ship content that must stay hidden until the story reaches it, and a plugin that reveals such content from world state. This is the first phase of #97.
Hidden world data (
visibility: hidden)visibility: public | hidden(defaultpublic, so existing packages are unchanged).to: contract:<id>). They import into the receiving plugin's reserved_hidden.<namespace>bucket. Lorebook projection (+lorebook), characters, world metadata, media, andindexToare rejected, and world projections skip hidden sources./statepanel, discoveryplugin-data-get/plugin-data-listinput.inject(kind: plugin-data)prompt.segment@1,ui.slot@1,session.world-context@1)ctx.pluginDatacannot read hidden namespaces_prefix)Only the receiving plugin's runtimes can read hidden data. This is spoiler protection, not encryption: the files remain in the local world package and in browser-local workspace checkpoints, which is documented.
story-eventspluginworld.dimensions@1), and world time (world-time-context@1).all/any/notover dimension paths and locale-independent numeric time fields withequals,notEquals,in,gte,gt,lte,lt,exists.cooldownTurns; a retried turn re-delivers the same cue; a rolled-back execution does not consume the event.story-event-cue@1output, consumed bynarratorandchat-mode-narratorthrough a new optionalstoryEventinput. A publicrevealedrecord stores only the event ID, optional public title, and turn numbers, never the payload.Bundled worlds
Every bundled world now enables
story-eventsand ships hidden events whose conditions use its own dimensions and world time:Payloads are narrator briefs that stage a moment without resolving the main mysteries early.
Type of change
Verification
pnpm lint,pnpm check:i18n,pnpm deps:check,pnpm release:preflight(4 worlds), prettier and oxlint on changed files.pnpm validate:pluginforstory-events,narrator, andchat-mode-narrator.apps/server/tests/api/story-events-hidden.test.ts) runs two real turns through/api/actionswith real plugins and a temporary world: before the condition holds, no model prompt and no public API contains the payload; on the turn it holds, only the narration call receives it, and the stored reveal record stays payload-free.input.inject, extension handlers, and the plugin's condition, selection, and retry logic.story.events@1, references only existing dimension paths (unmet with no diagnostics at the initial state, met when its conditions are applied), and imports into_hidden.eventsfor bothzh-CNanden-USsessions without appearing in public APIs.Related issue / context
Docs sync
docs/reference/world-data.md(visibilityfield, hidden data section, bundled worlds),plugins.md,tools.md,api.md,extension-points.md,prompt-structure.md,docs/architecture/storage.md.plugins/story-events/README.mdand thecreate-worldskill.