Pouchy World — size limits, and why each one exists

Written against world API 1.48.0. Every number below is enforced by the server; the machine-readable copy is the maxItems / maxLength on the world OpenAPI (components.schemas.StoryPackageContent, plus x-pouchy-max-bytes for the whole-package ceiling). Content you SEND over a limit is refused with a 400 that names the limit (roles exceeds 24, story package is 1043211 bytes; the limit is 900000 …), never silently truncated. The bounds in §5 are of another kind — what the runtime holds, shows or reports per beat — and the note beside each says how it behaves.

中文版:world-limits-zh.md


1. What changed in 1.48.0

Limit Before Now Constant
Roles in a story 12 24 STORY_MAX_ROLES
Roles in a world definition 12 24 WORLD_MAX_ROLES (pinned equal to the above)
Scenes 50 100 STORY_MAX_SCENES
Nodes 100 200 STORY_MAX_NODES
Branches 100 200 STORY_MAX_BRANCHES
Whole package size (unchecked) 900,000 bytes STORY_PACKAGE_MAX_BYTES

This is a loosening, with one new refusal: a package over 900,000 bytes. Over 1 MiB such a package could never be stored, and now gets a 400 that explains itself instead of a storage error. Between 900,000 bytes and 1 MiB it used to publish and is now refused. Revisions already published are not affected: content is validated when it is created or published, never when it is read, so a pinned revision keeps running exactly as before. Only publishing it again (or a new revision of the same size) meets the new ceiling.

2. The cast

Limit Value Why
Roles per story / world 24 A product choice, not a technical one. Twelve was "small on purpose"; nothing measured depends on it (see Cost below).
Agents per role exactly 1, distinct A world refuses one agent bound to two roles, so a 24-role world needs 24 agents.
Roles that speak in one beat 3 (WORLD_TURN_MAX_ROLES) Dramatic, and deliberately NOT raised: three speakers is a scene; more is a crowd an audience cannot follow. It also bounds each beat to at most 3 model calls.
Roles one event may wake 5 (WORLD_MAX_ROLES_PER_EVENT) Equal to the event router's fan-out cap (MAX_EVENT_FANOUT). A world wired so one event needs more roles than the router will ever deliver to is refused at publish, not truncated at runtime.
Secrets / goals per role 20 each Capacity. Each is rendered only into that role's own prompt.

Cost does not grow with the cast. A role that is not speaking never enters another role's prompt: not its description, not its goals, not its secrets. The only per-other-role content is the lines already spoken earlier in the same beat (at most 2) and two capped id lists in the effect brief. A 24-role story and a 3-role story produce byte-identical prompts for the roles that speak; world-envelope-composition.test.ts proves this at the maximum cast size on every build.

A bigger cast needs the beat to choose who is in the room. The 3 seats are filled in roles declaration order. Without direction, roles 4–24 are skipped on every beat, forever: they appear in skippedRoles with code: "role_cap_reached". Choose the scene's cast per beat with trigger.focusRoles (turn door, since 1.29.0), or route with event subscriptions. If a character "never says anything", check skippedRoles before anything else.

3. The story package

Item Max Note
Scenes 100
Nodes 200 each names a declared scene
Branches 200 each connects two declared nodes
Prerequisites per node 10
Endings 60 raised from 20 earlier, for multi-episode serials
Established facts 200
Canon lines 100
Constraints 50
Flags 50
Initial relations 50 pinned to what runtime state can hold (WORLD_STATE_MAX_RELATIONS)
Episodes (series) 24
Ending rules per episode / conditions per rule 20 / 10
Turn budget per episode 200 beats the evidence window: a longer episode cannot be turned into a script
Text per item 500 characters canon, facts, secrets, goals, objectives, conditions, descriptions
Whole package 900,000 bytes see §4

4. The size ceiling — the limit you will actually meet

A published revision is stored as one Firestore document, and Firestore refuses any document over 1 MiB (1,048,576 bytes). The package ceiling is 900,000 bytes, measured as the UTF-8 bytes of the normalized content's JSON. The remaining ~140 KiB covers the revision row's own fields.

Why you will meet it before the item limits: the item limits multiply. Chinese, Japanese and Korean text is 3 bytes a character, so one maximally-filled role (20 secrets + 20 goals × 500 characters) is about 60 KB. Twenty-four such roles would be 1.44 MB. A real script is far smaller, but a long-text Chinese package reaches the byte ceiling long before 24 roles or 200 nodes.

When you hit it (400 story package is N bytes; the limit is 900000 …):

  • Shorten the long text fields first: role secrets / goals, canon, establishedFacts.
  • Keep reference material (full script, lore bible) out of the package. The package carries what the runtime needs to decide and project state; the original script is referenced by source: { title, version }, never embedded.
  • POST …/story-packages/import-candidate/validate runs the same validator without publishing and returns the same message, so you can check a package before you publish it.

5. Other runtime bounds you may see

Bound Value How it behaves
State patch ops per beat 20 a larger batch is refused whole (patches are all-or-nothing)
Entities / relations in world state 50 / 50 a patch that would add the 51st is refused, with the whole batch. A big cast with a dense relationship graph reaches 50 relations long before 24 roles do: 24 roles make 276 possible pairs
Private notes per role in state 20 same: the op that would add the 21st is refused
Branch options a turn reports 5 the first 5 reachable, unfinished branches in declaration order; the rest stay in the story and appear as earlier ones complete
Relation ids in one role's effect brief 8 other roles ids only; the brief reports how many it cut
Deliberation 2 candidates, 3 roles server constants; a request may ask for fewer, never more
Story import (model-assisted) 16,000 characters of script a longer script is refused; the extracted package obeys every limit above
Script draft 500 beats one draft covers at most 500 committed beats; draft a longer worldline one episode at a time (episodeRunId, 1.27.0)
Enabled worlds / story packages per project 1,000 / 1,000 creation is refused at the ceiling

6. Where the numbers live

src/lib/world/story-package.ts (story limits and the byte ceiling), src/lib/world/definition.ts (world definition), and src/lib/server/platform/world-coordinator.ts (WORLD_TURN_MAX_ROLES). The dashboard's world wizard imports the same constants and shows "n / 24 roles", so it cannot drift from the contract.