feat(world)!: dynamic world dimensions, refreshed world packs, and a tabletop world - #98
Merged
Merged
Conversation
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.
This was referenced Oct 1, 2026
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
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)
dimensionsaccepts 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 explicitx-i18nannotations rather than mistaking ordinary JSON dictionaries for translations.titleacceptsI18nText, and the newx-enumLabelskeyword gives scalar enum members localized display names while values stay stable IDs (labels are validated against the declaredenum). The session panel and editor render both.world-initis extended rather than adding a plugin:dimension-contextpublishes a read-only pre-turn snapshot, thedimension-trackerpost-turn runtime maintains state, and theedit-dimensionsmanual runtime handles player edits and manual settlement.world-irremains a fact-extraction pipeline and is optional supporting evidence. Worlds without anyupdateRulemake no extra model calls; with rules, all rule-bearing dimensions share one maintenance call per turn.plugin_data[_dimensions]and change only throughdimension.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 stable409 dimension-version-conflict. No database tables are added; browser sessions keep the BrowserVault/checkpoint path.world.dimensions@1, soctx.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.pending-settlement,settled,no-change,manual, orskipped. 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).dimensionSources, worldData import, and generation/export use the new format; duplicate writes of dimensions into constant lorebook entries are removed. Thecreate-worldskill documents the format (with validated examples and a tabletop-world recipe), andscripts/generate-scenes.mjs --scaffoldreadsgeography.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.
haruka-galgame: chat narrator, stage, cast, scene prompts, branching replies, presence, and now affinity with seeds for the five romanceable charactersmistport-investigation, plus thedirectornoteemberback-rescuepack: dice, quests, inventory, affinity, codex, relations, rules,directorclassic-tabletop:tabletop-rulespoint-buy and form checks,dice-check, quests, inventory, companion affinityWORLD.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.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.envariants 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
Verification
Passed
pnpm check(includingdeps:check), all unit tests, and e2e collection.pnpm lint(full workspacetsc --noEmit, 21/21 tasks)pnpm check:i18n(web + plugin locale coverage, includingru-RU)pnpm release:preflight(all 4 worlds pass manifest and worldData validation)pnpm validate:plugin plugins/world-initdimensions.yamlvalidates againstworldDimensionsSchema.zh-CNanden-USwith each world's preset pack: dimensions, affinity seeds, quests, items, characters, and tabletop rules import;.envariants are selected for English sessions.plugin-reloadwatcher,memory-vector-pg,server-startup) occasionally time out; each passes when rerun alone.dimension-trackersettled the turn asno-change; the panel showed localized titles and enum labels.packages/shared/tests/dimensions.test.ts: open IDs, definition/value validation, the supported schema subset, localized titles andx-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 asno-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
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.Related issue / context
This branch also carries two commits from local
mainthat were not yet pushed:test(server,store): deflake concurrent Postgres teardownandchore(deps): bump pnpm to 12.6.0.Relates to 建议:世界包作者可声明式自定义追踪数据(参考 SillyTavern 数据库/MVU 变量),避免新数据维度必须写插件 #96: world-package declarations for custom data, automatic maintenance, public reads, generic presentation, player corrections, and sync conflict protection. Close it after multi-turn real-model play confirms the maintenance quality.
Relates to 建议:按状态条件触发的隐藏事件(时间×地点×自定义数据),避免常驻注入的上下文占用与剧透风险 #97: provides the structured current-state reads it needs as a foundation. Conditional triggers, hidden payloads, and event scheduling are out of scope; 建议:按状态条件触发的隐藏事件(时间×地点×自定义数据),避免常驻注入的上下文占用与剧透风险 #97 remains open.
Design: the capability guide is
docs/reference/dynamic-dimensions.md; the reference pages it links are authoritative for fields and behavior.Docs sync
docs/reference/dynamic-dimensions.md; updatedworld-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, anddocs/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 thecreate-worldskill.CHANGELOG.md: add the breaking-change entry in the release PR.Known gaps
BREAKING CHANGE
dimensionsno longer accepts the old raw-data format for the nine fixed categories. All dimensions, including former categories such asgeographyandfactions, 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
.valueentries, not world-package initial values or the oldworld.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.