Writes that do not slow down as a show grows
On 2026-10-12, words-places measured every build_set and every approve validating the whole
show. With 38 places on one show, build_set went from 0.2 s to 0.9 s per set as the show
filled, and approve from 0.15 s to 0.8 s: about 18 ms for every set already in the show. So
the total cost grew with the square of the show's size, and a real show of about 40 sets paid
about 2 s for every set change.
Measured
tools/show-scale-bench.ts grows a show to 10, 40, 100 and 200 sets, with a character for
every four sets and an episode for every ten. Then it times each write Studio's daemon makes on
it, in-process, three times at each size. The writes are a set built from words and kept
waiting (build_set), that proposal approved, a set and a character renamed (revise_set,
revise_character), an episode's cut moved (edit_episode), and that edit undone. The
person's own edits are proposed and applied in one call, so they validate four times.
Before (commit 9865c48d, median wall time of three):
| sets | documents | build_set | approve | revise_set | revise_character | edit_episode | undo |
|---|---|---|---|---|---|---|---|
| 10 | 32 | 1.56 s | 0.45 s | 1.17 s | 0.95 s | 1.94 s | 0.93 s |
| 40 | 70 | 1.53 s | 1.13 s | 2.40 s | 2.01 s | 3.98 s | 1.98 s |
| 100 | 151 | 2.10 s | 2.75 s | 4.34 s | 4.89 s | 9.42 s | 5.43 s |
| 200 | 286 | 3.93 s | 4.11 s | 7.75 s | 7.94 s | 14.47 s | 7.48 s |
Every write grows with the show. At 200 sets an approve costs 4 s and renaming a set 8 s, though neither touches anything but its own documents.
After (commit 1880a003, the same bench):
| sets | documents | build_set | approve | revise_set | revise_character | edit_episode | undo |
|---|---|---|---|---|---|---|---|
| 10 | 32 | 1.21 s | 0.05 s | 0.23 s | 0.12 s | 0.24 s | 0.10 s |
| 40 | 70 | 0.50 s | 0.07 s | 0.26 s | 0.16 s | 0.34 s | 0.15 s |
| 100 | 151 | 0.87 s | 0.17 s | 0.48 s | 0.36 s | 0.86 s | 0.28 s |
| 200 | 286 | 1.34 s | 0.15 s | 0.48 s | 0.42 s | 0.86 s | 0.31 s |
At 200 sets an approve takes 0.15 s instead of 4.1 s, renaming a set 0.48 s instead of 7.7 s,
and moving a cut 0.86 s instead of 14.5 s. build_set's time is drawing the set, about the same
at any size (its fastest runs: 0.25 s at 10 sets, 0.35 s at 200).
What still grows is reading the show from disk: every tool call and decision reads each file again, about 0.4 ms a set (82 ms at 200 sets, the show's 13.8 MB of JSON). An approve reads it once, a person's own edit of a set twice and an episode edit six times (it compiles the episode). That cost is in the numbers above: renaming a set is 0.25 s more at 200 sets than at 10, where it was 6.6 s more. Reading only the files that changed needs documents shared between readers, which no reader may change; that is left for later.
What a write checks now
A write checks the documents it changes, and those whose checks read something it changed
(core/src/validate/incremental.ts).
- What a check reads. Checks see the rest of the show only through
ShowContext(core/src/validate/context.ts).doc(id)gives the document with an id,docsOfKindevery document of a kind,idsOfKindtheir ids, andhasFile,textandtextPathsa file or a screenplay.manifest,styleandthemegive the show's own. Each read is recorded as a key:id:set.bridge,docs:Bible,ids:SetInstance,file:assets/<hash>.png,text:episodes/…/x.fountain,texts,manifest,style,theme. The show's documents and files are private to the context, so a check cannot read around it. - Who refers to whom.
ShowValidationkeeps each document's findings and the keys its checks read. It also keeps the index the other way, from each key to the documents that read it. When a document is checked again, its old keys leave the index and its new ones go in. - A write.
validate(pack)compares the pack with the last one it saw. A document is unchanged when it is the same file object, or has the same text read from disk. A changed document is checked again, schema and all. Its keys change: its id, its kind's documents, its kind's ids when it is added or removed or its id changes. Then every document that read a changed key is checked again too: the ids that point at the changed document. A changed document's own checks resolve the ids it points at. Files and screenplays added, removed or rewritten change their keys, and so does a new manifest, style or theme. The findings are then put together in the ordervalidateShowputs them, and placed and sorted the same way. - Shared. One
ShowValidationper show serves everything in the process that validates it (showValidation(root)): the ChangeStore's proposals and decisions, the director's and the compiler's loads. A process keeps the last eight shows.
The first validation in a process checks everything, so a show opened in Studio is checked
whole first. reelwright validate, reelwright doctor and importing a show always check the
whole show (validateShow).
The same findings
The guarantee is that any show a full validation would refuse is refused at the write that broke it. In fact the findings are the same, message for message.
core/test/incremental-validation.test.tstakes four random walks of 90 edits each over the Marigold. After every step it compares the findings withvalidateShow's. The edits point references at other documents, at documents of the wrong kind and at none. They remove documents and bring them back, change ids, take an id twice, copy documents, and change any value in any document. They remove files and screenplays, break a file so it no longer parses, and read the show again from disk. They also step back to earlier states, as a proposal's check goes back to the show as it is.REELWRIGHT_VALIDATION=bothruns both on every write and throws if they ever differ.REELWRIGHT_VALIDATION=fullchecks the whole show on every write, as before.
Findings that read files from disk (a frozen asset's bytes, a LUT) are taken when their document is checked. A frozen asset changed on disk behind Reelwright's back is found when its document is next checked, not at once.
Reading the show again
Every tool call and decision reads the show from disk again. The strict JSON parse, which
notes the position of every value, cost about 3.5 ms a set (0.76 s at 200 sets). Now the text
of each document that passed it is kept, by path (core/src/show/load.ts). The same text
again takes its value from JSON.parse, which gives what the strict parse gave, and its
positions only when a finding points into it. A read of 200 sets takes 82 ms instead of 0.76
s. An approve no longer reads the show a second time unless it changes a character.