Files
vanity-delve/spike/SPIKE-REPORT.md
T
tevansandClaude Opus 5 decb3bf930 Replace the footers; the repo is public now
Eleven files carried "Private VANITY development material — not for sale or
distribution", which contradicted the repo the moment it went public.

Two replacements, because the files do two different jobs. core/render.mjs
*emits* its footer into every generated delve, so that one now says where the
document came from rather than restricting what can be done with it — a
generated dungeon belongs to whoever generated it. The project's own documents
— DESIGN.md, the paper delve, both playtests, the spike report — read as
development notes in a public repo.

Samples regenerated so they carry the new line. The worksheet and play sheet
never had a footer and still do not; they are the GM's own working documents.

Module to 0.1.1: the v0.1.0 zip vendors the old render.mjs and would stamp the
stale footer on everything it generated. Cutting a version rather than swapping
assets under the existing one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 12:38:32 +01:00

108 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SEAM SPIKE — measured results
**Build-order step 2. 2026-08-04.**
**Method:** VANITY's Forge run *outside Foundry* against an instrumented stub harness
(`harness.mjs`), driven by `spike.mjs`. Every Foundry global is faked; the side-effecting calls
(`ChatMessage.create`, `Folder.create`, `Actor.create`, `Scene.create`, `FilePicker.upload`) are
recorders, and `Math.random` is counted. **Nothing real was created** — no world was touched.
Booted successfully: `game.vanity.forge = { hero, monster, face, hoard, encounter, stage, adventure }`.
---
## 1. Measurements
| Call | returns | chat | folders | actors | scenes | uploads | `Math.random` |
|---|---|---|---|---|---|---|---|
| `forgeHoard({size:'cache'})` | **undefined** ⚠ | 1 | 0 | 0 | 0 | 0 | 9 |
| `forgeEncounter({heat:'fight'})` | object ✅ | **2** | 1 | 5 | 0 | 0 | 58 |
| `forgeStage({populate:false})` | Scene ✅ | 1 | 0 | 0 | 1 | 1 | 360 |
| `forgeStage({populate:true,heat:'fight'})` | Scene ✅ | **3** | 1 | 3 | 1 | 1 | 552 |
| **3 linked areas** | — | **9** | **3** | **16** | 3 | 3 | **1472** |
**Per area: 3 chat cards · 1 folder · 5.3 actors · 1 scene · 1 upload · ~490 `Math.random` calls.**
---
## 2. Seam list, corrected against measurement
| # | Seam | Verdict |
|---|---|---|
| 1 | `hoard: false` | **Confirmed needed.** `forgeEncounter` posts 2 chat cards — its own and a forced hoard. |
| 2 | `populate: false` | **ALREADY EXISTS — remove from the list.** `forgeStage({populate:false})` runs clean: 0 actors, 0 folders, scene created. It is already the escape hatch. |
| 3 | `post: false` | **Confirmed, and it is the big one.** 3 cards per area, 9 for a three-area delve, none suppressible. |
| 4 | Structured returns | **Confirmed, but narrower than drafted.** `forgeHoard` returns `undefined`. `forgeEncounter` *already* returns `{actors, situation, terrain, complication, mood, folder}` — only the hoard needs work. |
| 5 | Caller-supplied folder | **Confirmed.** 1 folder + 5.3 actors per area → 3 folders and 16 loose Actors for three areas. |
| 6 | Live Reaction as a `Roll` | **Confirmed.** No `Roll` was ever constructed during `forgeEncounter`; mood comes from `rollDice()` over `Math.random`. |
| 7 | Injectable RNG | **Confirmed large.** ~490 calls per area, 1472 for three. Draft 4 called this "small"; it is not. |
**Net: draft 5's seven seams become six.** Seam 2 was already solved and I did not check before writing it down.
---
## 3. Two findings the spike produced that no review did
**A. `forgeStage` cannot run outside a browser.** It needs `Image`, `document.createElement('canvas')`,
`canvas.toBlob`, `XMLSerializer` and `URL.createObjectURL` to rasterise its SVG. Draft 5 says "the
CLI is map-free" as a design choice — it is not a choice, it is a **hard constraint**. Any
non-Foundry surface for DELVE can never produce maps without reimplementing rasterisation.
**B. An unguarded `rnd()` on a possibly-empty array — `vanity.mjs:1969`.**
```js
const consumables = gear.filter(g => g.system.consumable);
if (Math.random() < tier.consum) found.push(rnd(consumables)); // rnd([]) → undefined
...found.map(d => `@UUID[${d.uuid}]...`) // throws
```
`rnd()` returns `undefined` on an empty array and `.uuid` is unread-guarded. My harness triggered
this with unfiltered fake gear. **In the shipped system the gear pack has consumables, so it
almost certainly never fires** — but it is a one-line guard worth taking while the seams are open.
---
## 4. What the spike cannot tell you
**Latency.** The harness stubs rasterisation and upload, so everything ran in 1–2 ms. The real
`forgeStage` encodes a PNG and uploads it before the scene is usable. **The prewarm requirement in
draft 5 §10 remains unmeasured** and must be timed inside real Foundry before it is designed
around.
**Table feel.** Nothing here says whether the output is *good* — only how much of it there is.
---
## 5. The economic question
Codex's draft-5 verdict set the right gate: *the next test is economic, not conceptual — does
DELVE as a module beat DELVE as a well-made document?* The spike gives the first hard number
towards it.
**Unmodified, the Forge produces per three-area delve: 9 chat cards, 3 Actor folders, 16 loose
Actors — none of which the caller can suppress, redirect or own.**
- A **document** cannot fix this. It has no way to intervene between the Forge and the table.
- A **module without seams 1, 3, 4, 5** cannot fix it either. It would be a wrapper watching the
Forge talk over it.
- A **module with them** can: one folder per delve, output DELVE decides, hoards on its own terms.
So the module's economic case rests entirely on landing four small seams. That is a genuinely
cheap, genuinely testable bet — and if the seams are refused, the honest answer is to ship the
document.
---
## 6. Recommendation
1. **Update draft 5 §5**: seven seams → six; seam 2 already exists; seam 4 narrows to `forgeHoard`.
2. **Add the browser-only constraint** to §13 as a stated non-goal, not a design preference.
3. **Land seams 1, 3, 4, 5** — all small, all in `vanity.mjs`, and together they *are* the module's
reason to exist. Defer 6 and 7.
4. **Time `forgeStage` in real Foundry** before designing prewarm.
5. Take the `rnd()` guard while in there.
Step 2 is complete. The thesis survived: DELVE is a sequencing layer, and the Forge can be
sequenced — but only if it stops talking to the table on its own.
---
*VANITY development notes — part of [DELVE](https://github.com/slaguru666/vanity-delve).*