Skip to content

feat(world)!: dynamic world dimensions, refreshed world packs, and a tabletop world - #98

Merged
ackness merged 7 commits into
mainfrom
feat/dynamic-dimensions
Oct 1, 2026
Merged

ackness merged 7 commits into
mainfrom
feat/dynamic-dimensions

Conversation

@ackness

@ackness ackness commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Summary

Replace the nine fixed categories of static world settings with author-declared dynamic dimensions: structured data that world package authors can define freely and that evolves throughout a session. Authors can declare resources such as reputation, a city wall's durability, or a codex in YAML/JSON without writing a plugin for each new data type. All three bundled worlds are rebuilt on top of the feature for their target audiences, and a fourth world, Lantern Barrow, showcases the tabletop plugins.

Dynamic dimensions (framework)

  • One open declaration format: dimensions accepts custom IDs, each defined as {name, description?, schema, initialValue, updateRule?}. Scalars, nested objects, arrays, and dynamically keyed records share one model. The shared validator explicitly rejects unsupported JSON Schema keywords. Localization uses explicit x-i18n annotations rather than mistaking ordinary JSON dictionaries for translations.
  • Localized display metadata: schema title accepts I18nText, and the new x-enumLabels keyword gives scalar enum members localized display names while values stay stable IDs (labels are validated against the declared enum). The session panel and editor render both.
  • Clear lifecycles and ownership: World packages hold definitions and initial values; each session holds its own current values. The existing world-init is extended rather than adding a plugin: dimension-context publishes a read-only pre-turn snapshot, the dimension-tracker post-turn runtime maintains state, and the edit-dimensions manual runtime handles player edits and manual settlement.
  • Rule-driven evolution: The tracker updates data from the current turn's narrative and the author's natural-language rules. world-ir remains a fact-extraction pipeline and is optional supporting evidence. Worlds without any updateRule make no extra model calls; with rules, all rule-bearing dimensions share one maintenance call per turn.
  • Protected, versioned writes: Definitions, values, and versions live in plugin_data[_dimensions] and change only through dimension.initialize / dimension.update, validated at the commit boundary. Memory, SQLite, and PostgreSQL apply batch CAS, so partial writes and stale plans cannot overwrite player edits. A version conflict, including one lost in a commit-time race, returns the stable 409 dimension-version-conflict. No database tables are added; browser sessions keep the BrowserVault/checkpoint path.
  • Consistent public reads: Snapshots are published through world.dimensions@1, so ctx.world.dimensions, prompt templates, and clients read the same shape. Reads are frozen within a turn and republished after commit. Prompts use budgeted projections plus builtin read-only query tools instead of injecting large record sets. Update rules and initial values never appear in the public snapshot.
  • Visible failures and recovery: Settlement receipts persist pending-settlement, settled, no-change, manual, or skipped. A failure is never treated as "no change"; unresolved settlement blocks the next narrative. The tracker retries a failed model call once before leaving the settlement pending, and players can then retry, resolve manually, or skip. Receipts freeze only rule-bearing definitions (static lore is not copied into every receipt — about 12–20 KiB per turn instead of 40 KiB for the larger worlds).
  • Generic presentation and safe synchronization: World editor, session panel, player editing, recovery, and browser snapshot paths are updated. Sync preserves evolved or player-edited values; changing or deleting an adopted definition reports a conflict instead of silently resetting progress.
  • Producers and tooling: World loading, external dimensionSources, worldData import, and generation/export use the new format; duplicate writes of dimensions into constant lorebook entries are removed. The create-world skill documents the format (with validated examples and a tabletop-world recipe), and scripts/generate-scenes.mjs --scaffold reads geography.initialValue.regions.

World packs

Each world now tracks the stakes its own lore already described, through live panels instead of free-text memory. Memory blocks that duplicated the new dimensions were narrowed or removed, and static fields that competed with live state (frozen countdowns and resource numbers) were dropped. Every static dimension has localized names and field titles.

World Audience Plugin pack Evolving dimensions
Haruka Academy Galgame / visual-novel players (zh) haruka-galgame: chat narrator, stage, cast, scene prompts, branching replies, presence, and now affinity with seeds for the five romanceable characters Heart Routes (stage, trust, pressure per heroine), Festival Prep (four projects + countdown milestones), Literature Club Review, Promises, Campus Rumors
Mistport Story-driven investigation (zh/en) mistport-investigation, plus the director note Case Board (the four opening leads with unverified → corroborated / contaminated / resolved), Deep Withdrawal stage, Faction Standing (four factions with permits, favors, warrants), Key Fragments
Emberback English-speaking players New emberback-rescue pack: dice, quests, inventory, affinity, codex, relations, rules, director Crownfire Countdown, Relay Grid (reserve and five powered systems), Signal Log, Medical Convoy
Lantern Barrow (new) Classic tabletop players (zh/en) classic-tabletop: tabletop-rules point-buy and form checks, dice-check, quests, inventory, companion affinity Delve Map, Barrow Alarm (0–6 clock), The Barrow Lantern, Renown
  • Emberback also gets a rewritten WORLD.md (places, community, people, echo rules, story shape, how to play) and two new NPCs, Priya Nair (clinic lead) and Eli Varga (the operator who logged the call), across the blueprint cast, characters, lorebook, and affinity seeds. The Crownfire and power rules point at the authoritative panels.
  • Lantern Barrow is a bilingual dungeon crawl: a village lantern that kept a barrow asleep has gone out, its keeper is missing, and the party forms at the tavern. Six abilities start at 1 with 6 points to spend (max 4), declared through contract:tabletop-rules.rules.initial@1; checks are d20 + attribute against DC 8/12/16/20. It ships a cast of six (two recruitable companions), three quests, starting gear, five world rules, memory blocks, a five-watch time definition, and .en variants for every string-only data source.

This PR does not migrate state ownership for inventory, affinity, character attributes, or world time, and it does not implement hidden events, condition evaluation, or the story-trigger layer.

Type of change

  • New feature (feat)
  • Bug fix (fix)
  • Refactor (refactor)
  • Documentation (docs)
  • Infra / CI (chore)
  • Performance (perf)
  • Breaking change (BREAKING CHANGE)

Verification

Passed

  • Pre-push clean-checkout verification: frozen install, pnpm check (including deps:check), all unit tests, and e2e collection.
  • pnpm lint (full workspace tsc --noEmit, 21/21 tasks)
  • pnpm check:i18n (web + plugin locale coverage, including ru-RU)
  • pnpm release:preflight (all 4 worlds pass manifest and worldData validation)
  • pnpm validate:plugin plugins/world-init
  • prettier and oxlint on every changed file (no new warnings)
  • Every world's dimensions.yaml validates against worldDimensionsSchema.
  • Session import for all four worlds in both zh-CN and en-US with each world's preset pack: dimensions, affinity seeds, quests, items, characters, and tabletop rules import; .en variants are selected for English sessions.
  • Test suites: server 1686 passed / 4 skipped, web 1543, runtime 1490, store 1041, shared 234, tools 175, context 124, create 47, world-init 14, tabletop-rules 12. In full parallel runs, single load-sensitive tests (plugin-reload watcher, memory-vector-pg, server-startup) occasionally time out; each passes when rerun alone.
  • Live check in the dev app with a real model (DeepSeek): Lantern Barrow session → generated opening form from the world's character fields → point-buy form with the world's six attributes and budget → opening narration → dimension-tracker settled the turn as no-change; the panel showed localized titles and enum labels.
  • New and updated regression tests:
    • packages/shared/tests/dimensions.test.ts: open IDs, definition/value validation, the supported schema subset, localized titles and x-enumLabels, snapshots, and explicit localization.
    • packages/store/tests/plugin-data-batch-cas.test.ts: atomic batch writes, version conflicts, transaction rollback, and session/owner isolation.
    • packages/runtime/tests/dimension-finalization.test.ts: settlement receipts, failures retaining pending settlement, source-turn idempotency, and frozen reads.
    • packages/tools/tests/world-dimension-tools.test.js: public frozen-snapshot queries, invalid paths, and isolation of private information.
    • apps/server/tests/lib/dynamic-dimension-import.test.ts: declaration entry points, synchronization, and conflict protection for session progress.
    • apps/server/tests/api/chat-mode-http-e2e.test.ts: Haruka's three-round chat flow now settles each narrative as no-change.
    • apps/web/src/components/session/__tests__/world-dimensions-panel.test.tsx: schema-driven editing, captured versions, pending-settlement display, and localized titles/enum labels that still submit enum IDs.

Not run

  • Full pnpm e2e (Playwright UI flows). The CI browser-smoke subset passes locally (4/4) after updating the browser world lifecycle fixture to the definition format.
  • Multi-turn real-model play in each world (only Lantern Barrow's opening turn was played live).
  • PostgreSQL concurrency with two connections.

Related issue / context

Docs sync

  • New capability guide docs/reference/dynamic-dimensions.md; updated world-data.md (including the bundled-world table and the new keywords), world-model.md, plugins.md, tools.md, api.md, protocol.md, transactions.md, extension-points.md, prompt-structure.md, ui-panels.md, ui-components.md, docs/reference/README.md, docs/architecture/flow.md, and docs/architecture/storage.md.
  • plugins/world-init/README.md, CLAUDE.md (proposal types and documentation index), docs/guide/world-scenes.md, docs/guide/world-portraits.md, and the create-world skill.
  • CHANGELOG.md: add the breaking-change entry in the release PR.

Known gaps

  • Emberback's two new NPCs and the whole Lantern Barrow cast have no portraits yet; the stage falls back to placeholders. Lantern Barrow uses the default cover art.
  • Haruka's static world content (lore, regions, cast) is Chinese-only, as before; only the new dimensions are bilingual.

BREAKING CHANGE

dimensions no longer accepts the old raw-data format for the nine fixed categories. All dimensions, including former categories such as geography and factions, use the same {name, description?, schema, initialValue, updateRule?} definition format. Existing names may still be used as custom IDs, but they are no longer a framework allowlist.

External dimension files, worldData, generators, editors, and plugin consumers must adopt the new contract. During gameplay, consumers read versioned .value entries, not world-package initial values or the old world.entries. There are no old-format compatibility paths, migration utilities, or dual writes.

After upgrading, update development world packages to the new format and recreate affected development world/session data. The bundled world packages are updated in this PR.

Upgrade the workspace package manager from 11.22.0 to pnpm 12.6.0, the
newest 12.x release that has passed the workspace's seven-day
minimumReleaseAge window (12.7.0+ are still inside it and would trip
ERR_PNPM_NO_MATURE_MATCHING_VERSION).

Keep mise.toml, package.json packageManager, and the Docker corepack
install aligned per the repository's version-pinning contract. pnpm 12
records the pinned manager under packageManagerDependencies in the
lockfile, so pnpm-lock.yaml gains the @pnpm/exe platform entries.
Parallel vitest workers all create and drop real databases on the shared
localhost instance, but the afterAll budget was hit not by the DDL itself
(CREATE DATABASE ~43ms, DROP FORCE ~228ms measured) but by wall-clock
stalls while turbo saturates the CPU. Give the teardown room and stop
treating a transient drop failure as fatal.

- pg-test-db.ts (server + store): retry DROP DATABASE ... WITH (FORCE)
  with backoff. A sibling still holding a connection, or a catalog lock
  from a concurrent CREATE/DROP, surfaces as 55006 'being accessed' or a
  lock_timeout that clears within a few hundred ms. Each database name is
  pid+uuid unique, so retries only ever target the file's own database;
  the per-file isolation model is unchanged.
- vitest.base.ts: raise hookTimeout to 60s for the files that relied on
  the 10s default (pg-store, media-store, vector-store, schema-ddl-codegen).
  testTimeout is untouched.
- memory-vector-pg / commit-pipeline-pg / runtime-job-recovery-pg: their
  explicit afterAll timeouts override the global, so raise 30s to 90s to
  cover store.close() pool drain plus the retried drop under load.
- plugin-entry.test.ts: 'bounds a stalled factory' shared a 50ms budget
  between the stalled and the healthy entry, so a CPU-preempted module
  import made the healthy entry trip the deadline. Raise to 5s and assert
  the healthy entry first; the stalled factory never resolves, so it still
  fails only by hitting its deadline.
Replace the nine fixed static world categories with an open map of
author-declared dimensions, each `{name, description?, schema,
initialValue, updateRule?}`, that evolve per session.

- world-init publishes a frozen pre-turn snapshot (`world.dimensions@1`),
  settles authored rules post-turn, and accepts player edits through
  `dimension.initialize` / `dimension.update` with batch CAS on every
  store backend.
- Settlement receipts make failures visible: a failed or skipped
  settlement blocks the next narrative until it is retried, resolved
  manually, or explicitly skipped. The tracker retries one failed model
  call first, and receipts freeze only rule-bearing definitions.
- Schema `title` accepts I18nText and `x-enumLabels` adds localized
  display names for enum IDs; the session panel and editor render both.
- World sync reports conflicts instead of resetting evolved or edited
  values; dimensions are no longer copied into constant lorebook entries.

BREAKING CHANGE: `dimensions` no longer accepts the old raw format for
the nine fixed categories. World packages, external dimension files,
worldData, generators, and plugin consumers must use the definition
format and read versioned `.value` entries. Recreate development
worlds and sessions after upgrading.

Refs #96, #97
Each world now tracks the stakes its lore already described as live,
rule-maintained dimensions with localized names and field titles.

- Haruka Academy (galgame): new haruka-galgame pack with affinity seeds
  for the five romanceable characters; Heart Routes, Festival Prep,
  Literature Club Review, Promises, and Campus Rumors.
- Mistport (story): Case Board, Deep Withdrawal, Faction Standing, and
  Key Fragments; adds the director note.
- Emberback (English): rewritten world guide, two new NPCs, and an
  emberback-rescue pack; Crownfire Countdown, Relay Grid, Signal Log,
  and Medical Convoy.

Memory blocks that duplicated the new dimensions are narrowed or
removed, and static counters that competed with live state are dropped.
The scene scaffold reads regions from `geography.initialValue`.
A bilingual (zh-CN / en-US) classic dungeon crawl built around the
tabletop plugins: the lantern that kept a barrow asleep has gone out,
its keeper is missing, and the party forms at the village tavern.

- classic-tabletop pack: tabletop-rules point-buy (six abilities, 6
  points, max 4) and form checks, dice-check pools, quests, inventory,
  and companion affinity.
- Delve Map, Barrow Alarm, The Barrow Lantern, and Renown dimensions; a
  five-watch time definition; `.en` variants for every string-only
  data source.
- The create-world skill now documents the open dimension format and a
  tabletop-world recipe.
The dimension tools that imported it were replaced by framework
builtins, so the dependency check flags it as unused.
…ixture

The fixture still wrote the removed raw dimension format, which browser
checkpoint validation now rejects. The world editor also opens on the
JSON definitions tab, so select the Geography tab before editing.
@ackness
ackness merged commit 271ebc7 into main Oct 1, 2026
3 checks passed
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