docs(01-06): author Season 7 end-state principle doctrine + Vitest doc-lint test (PIPE-05)
- .planning/season-7-end-state.md answers principle-level the three questions per CONTEXT D-08: (a) what rest state means, (b) what the finite Roothold ceiling is tied to (count of authored fragments + Seasons), (c) the coda's tonal register (warm/quiet/specific/final). Cites SEAS-04, SEAS-09, SEAS-10, STRY-08; ROADMAP Phase 7; PITFALLS #1. - Includes the explicit 'What this document is NOT' boundary section so treatment-level scope creep is structurally rejected (binary-choice scene text, ending paragraph text, Lura's final line, credits screen — all authored Phase 7, not Phase 1). - scripts/doctrine.test.ts is the only automated enforcement of both Phase-1 doctrine docs (per CONTEXT D-07: no UX-string lint rule). Asserts file existence + required H2 sections + required source citations + boundary disclaimer. 8 assertions / 2 doc files. - vitest.config.ts include glob extended to scripts/**/*.test.ts so doctrine.test.ts is discovered by 'npm test'. - tsconfig.node.json include extended to scripts/**/*.ts so the strict-TS gate covers the new doc-lint test alongside the existing build configs. - 'npm test' green: 2 test files, 9 tests passing (sentinel + doctrine). - Per CONTEXT D-09: lives in .planning/, not docs/. Rule 3 [Blocking]: vitest.config.ts and tsconfig.node.json include globs extended to discover the new TypeScript test file (existing globs only covered .mjs scripts and src/ tests).
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
# Season 7 End-State Design (Principle-Level)
|
||||
|
||||
*Phase 1 deliverable per PIPE-05 + CONTEXT D-08. Principle-level only — treatment text is authored in Phase 7.*
|
||||
|
||||
This document answers the question that ends ROADMAP.md Phase 7's success criterion #4:
|
||||
|
||||
> *"the finite Roothold ceiling from Phase 4 has held the line, and the game has ended
|
||||
> the way A Dark Room and Universal Paperclips ended."*
|
||||
|
||||
Per .planning/research/PITFALLS.md #1, "the story ends but the idle loop doesn't"
|
||||
is the single most dangerous structural pitfall for this project. This document
|
||||
is the canonical answer the project has *before* any economy code lands in Phase 2.
|
||||
|
||||
Per CONTEXT D-08: this is **principle-level**, not treatment-level. It defines the
|
||||
contract Phase 7's authoring obeys, not the text of any final scene.
|
||||
|
||||
## What does *rest state* mean?
|
||||
|
||||
The rest state is the post-credits configuration the player can return to indefinitely
|
||||
without grinding. Concretely:
|
||||
|
||||
- **No new fragments are added to the pool.** All authored content has been delivered.
|
||||
Harvests after the final binary choice yield re-readable previously-collected
|
||||
fragments — nothing new.
|
||||
- **No new currency tiers unlock.** Roothold has reached its finite ceiling (see below)
|
||||
and stays there. There is no "Season 8" hidden behind a number.
|
||||
- **The garden continues to render and respond to clicks.** Plants can still be
|
||||
planted. Seasons (now in Return register) continue to crossfade. The world is
|
||||
not frozen — it is *finished*.
|
||||
- **The Pale has receded.** The Heartsoil expands beyond the garden walls. Lura's
|
||||
arc has resolved. The Archivist's question has been answered (in the player's
|
||||
Season 7 binary choice — STRY-08).
|
||||
- **The cello and ambient layers continue.** The audio is *quiet*, *finite*,
|
||||
*understood* — never crescendos again, never hard-cuts.
|
||||
|
||||
This is not "endgame content." It is **rest**. Lineage: *A Dark Room* fades to its
|
||||
ending screen and the player returns to it for the same reason they return to a
|
||||
finished album — not because there is more, but because there was *enough*.
|
||||
|
||||
## What is the finite Roothold ceiling tied to?
|
||||
|
||||
Roothold's ceiling is anchored in the **count of authored fragments and the count
|
||||
of Seasons** — not in an arbitrary number, not in a designer's intuition.
|
||||
|
||||
The principle:
|
||||
|
||||
> *One cannot accumulate more Roothold than the player has actually understood,
|
||||
> and what the player can understand is bounded by what the writer has actually written.*
|
||||
|
||||
Concrete tie:
|
||||
|
||||
- Roothold gain per Season is gated to a hard cap proportional to the fragment
|
||||
count of that Season + a small contribution from Roothold-relevant story beats
|
||||
(Lura conversations, the Nameless Man's arc, the Archivist's question, etc.).
|
||||
- Total Roothold ceiling = Σ(per-Season caps).
|
||||
- **Phase 4 enforces this cap** when it implements `migrate_v1_to_v2` and the
|
||||
prestige state machine (SEAS-04). Phase 7 verifies the ceiling holds through
|
||||
full play.
|
||||
- When Roothold reaches the ceiling, the UI displays "Roothold (full)" — never
|
||||
a hidden multiplier or "go again to overflow."
|
||||
|
||||
Implication for designers: when adding fragments in Phase 5+, the Roothold ceiling
|
||||
*moves* — adding 5 new Season-3 fragments adds proportional headroom. This is
|
||||
intentional. Roothold is bounded by content; content is bounded by the writer.
|
||||
|
||||
## What tonal register does the coda live in?
|
||||
|
||||
- **Warm**, not pyrrhic. The garden persists *because* you tended it; this is
|
||||
earned redemption, not survival. Lineage: the closing minutes of *Spiritfarer*,
|
||||
not the closing minutes of *A Dark Room* (which earned its bitterness; we earn
|
||||
our warmth).
|
||||
- **Quiet**, not climactic. The cello does not crescendo at the binary choice.
|
||||
It rests. The chosen ending paragraph displays softly; "The garden persists."
|
||||
lands without underscore.
|
||||
- **Specific**, not abstract. The final visible state is a *real* garden — the
|
||||
one this player built, with their actual planted ecosystems, their actual
|
||||
Roothold value, their actual collected fragments — viewed in soft dawn-silver
|
||||
light per AEST-06's Season-7 palette anchor.
|
||||
- **Final**, not infinite. There is no Season 8. There is no New Game+. The Pale
|
||||
receded **here**, in **this** garden. Future patches may add cosmetic items or
|
||||
additional fragments per CONT-01 (post-launch additive content), but they slot
|
||||
*between* authored beats; they never extend the arc.
|
||||
|
||||
## What this document is NOT
|
||||
|
||||
This document defines principles. It does **not** define:
|
||||
|
||||
- The text of the Season 7 binary-choice scene — *authored Phase 7*.
|
||||
- The text of either ending paragraph (`"They help us remember"` / `"They help us grow"`) — *authored Phase 7*.
|
||||
- The exact line "The garden persists." appears in both endings, but its surrounding
|
||||
paragraph and Lura's final line are *authored Phase 7*, not Phase 1.
|
||||
- The credits / coda screen visual treatment — *designed Phase 7*.
|
||||
- The exact tonal register or shape of individual final-Season fragments — *authored Phase 7*.
|
||||
- The numeric value of the Roothold ceiling — *computed Phase 4* from the
|
||||
content count at that point + ROADMAP-locked principle.
|
||||
|
||||
This document is **the principle the economy obeys, the writer obeys, and the
|
||||
Phase 7 designer obeys** — not the implementation of any of those.
|
||||
|
||||
## Source Documents
|
||||
|
||||
This doctrine consolidates constraints already locked in:
|
||||
|
||||
- **PROJECT.md** § "Core Value" — "every idle mechanic must function as a metaphor"; "what survives is what you understood"
|
||||
- **REQUIREMENTS.md** SEAS-04 (finite Roothold ceiling), SEAS-09 (Season 7 late-game shape), SEAS-10 (rest state, not infinite prestige tiers), STRY-08 (binary choice + "The garden persists.")
|
||||
- **ROADMAP.md** § "Phase 7: Season 7 (Return) & Final Choice" — the 4 success criteria
|
||||
- **.planning/research/PITFALLS.md** § "Pitfall 1: The Story Ends but the Idle Loop Doesn't" — the rationale this document directly addresses
|
||||
|
||||
---
|
||||
|
||||
*Authored: Phase 1 deliverable. Phase 4 enforces the Roothold ceiling. Phase 7 authors
|
||||
the treatment-level final scenes against the principles above.*
|
||||
@@ -0,0 +1,79 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { readFileSync, existsSync } from 'node:fs';
|
||||
|
||||
// PIPE-05 doctrine doc-lint test.
|
||||
//
|
||||
// Per RESEARCH § "Validation Architecture" PIPE-05 row, this is the only
|
||||
// automated enforcement of the Phase-1 doctrine documents. CONTEXT D-07
|
||||
// explicitly forbids a lint rule on UX strings, so this structural test
|
||||
// asserts (a) both docs exist on disk, (b) each contains its required H2
|
||||
// sections, (c) each cites its required source documents.
|
||||
//
|
||||
// If a future plan moves either doc, update PATH constants below.
|
||||
|
||||
describe('PIPE-05: doctrine documents exist with required H2 sections', () => {
|
||||
describe('.planning/anti-fomo-doctrine.md', () => {
|
||||
const PATH = '.planning/anti-fomo-doctrine.md';
|
||||
|
||||
it('exists', () => {
|
||||
expect(existsSync(PATH)).toBe(true);
|
||||
});
|
||||
|
||||
it('contains all 4 required H2 sections', () => {
|
||||
const md = readFileSync(PATH, 'utf8');
|
||||
expect(md).toMatch(/^## Banned Mechanics$/m);
|
||||
expect(md).toMatch(/^## Allowed Engagement$/m);
|
||||
expect(md).toMatch(/^## Review Checklist$/m);
|
||||
expect(md).toMatch(/^## Source Documents$/m);
|
||||
});
|
||||
|
||||
it('cites all 4 source documents (PROJECT, REQUIREMENTS, CLAUDE, PITFALLS)', () => {
|
||||
const md = readFileSync(PATH, 'utf8');
|
||||
expect(md).toMatch(/PROJECT\.md/);
|
||||
expect(md).toMatch(/REQUIREMENTS\.md/);
|
||||
expect(md).toMatch(/CLAUDE\.md/);
|
||||
expect(md).toMatch(/PITFALLS\.md/);
|
||||
});
|
||||
|
||||
it('does NOT propose a lint rule on UX strings (CONTEXT D-07 explicit rejection)', () => {
|
||||
const md = readFileSync(PATH, 'utf8');
|
||||
// The doc may *mention* that lint rules were rejected, but it must not
|
||||
// propose adding one. Allow "no lint rule" but reject "add a lint rule".
|
||||
expect(md).not.toMatch(/\b(add|implement|propose).{0,40}lint rule/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.planning/season-7-end-state.md', () => {
|
||||
const PATH = '.planning/season-7-end-state.md';
|
||||
|
||||
it('exists', () => {
|
||||
expect(existsSync(PATH)).toBe(true);
|
||||
});
|
||||
|
||||
it('contains all 5 required H2 sections (CONTEXT D-08)', () => {
|
||||
const md = readFileSync(PATH, 'utf8');
|
||||
expect(md).toMatch(/^## What does \*rest state\* mean\?$/m);
|
||||
expect(md).toMatch(/^## What is the finite Roothold ceiling tied to\?$/m);
|
||||
expect(md).toMatch(/^## What tonal register does the coda live in\?$/m);
|
||||
expect(md).toMatch(/^## What this document is NOT$/m);
|
||||
expect(md).toMatch(/^## Source Documents$/m);
|
||||
});
|
||||
|
||||
it('cites SEAS-04, SEAS-09, SEAS-10, STRY-08', () => {
|
||||
const md = readFileSync(PATH, 'utf8');
|
||||
expect(md).toMatch(/SEAS-04/);
|
||||
expect(md).toMatch(/SEAS-09/);
|
||||
expect(md).toMatch(/SEAS-10/);
|
||||
expect(md).toMatch(/STRY-08/);
|
||||
});
|
||||
|
||||
it('does NOT include treatment-level details forbidden by CONTEXT D-08', () => {
|
||||
const md = readFileSync(PATH, 'utf8');
|
||||
// Check the "What this document is NOT" section is present — this is the
|
||||
// structural guarantee against treatment-level scope creep.
|
||||
expect(md).toMatch(/## What this document is NOT/);
|
||||
// The doc must explicitly disclaim authoring the ending paragraphs.
|
||||
expect(md).toMatch(/authored Phase 7/);
|
||||
});
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -17,5 +17,5 @@
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts", "vitest.config.ts", "playwright.config.ts", "scripts/**/*.mjs"]
|
||||
"include": ["vite.config.ts", "vitest.config.ts", "playwright.config.ts", "scripts/**/*.mjs", "scripts/**/*.ts"]
|
||||
}
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import { defineConfig } from 'vitest/config';
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: 'happy-dom',
|
||||
include: ['src/**/*.test.ts', 'src/**/*.test.tsx', 'scripts/**/*.test.mjs'],
|
||||
include: ['src/**/*.test.ts', 'src/**/*.test.tsx', 'scripts/**/*.test.mjs', 'scripts/**/*.test.ts'],
|
||||
passWithNoTests: false,
|
||||
globals: false,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user