Merge feature/duncarrow-purge into dev
Some checks failed
content-build / build (push) Failing after 2s
Some checks failed
content-build / build (push) Failing after 2s
Delete the Duncarrow scaffolding; preserve the infrastructure it carried. - rule.disposition-ladder reparented into content/lore/mechanics.md (required exactly once by the build; moved atomically with the deletion) - content/lore/specimen.md: synthetic status=candidate disposition-gate specimen (validated by --check, emitted nowhere), teaching both an earned warm reveal and a never-gated core secret - duncarrow.md + 23 generated JSON + test_duncarrow.py removed - world-building skill repointed: structure -> specimen.md, voice -> cosmology.md; 3 evals rewritten cosmology-aware - repaired the client consumers the purge broke: content_db no longer errors on an absent content dir, and the no-body-on-client secrecy guard is asserted structurally so it cannot pass vacuously --check exit 0, pytest 45, client GUT 149/149.
This commit is contained in:
@@ -7,7 +7,7 @@ description: >
|
||||
their knowledge/dialogue, quests, items, locations, or starting origins — even
|
||||
if they don't say "world-building" or name the content/ folder. Triggers on
|
||||
"write an NPC", "add a town/region/faction", "new quest", "a secret the party
|
||||
can uncover", "expand the Tallow Reach", "flesh out Duncarrow", "someone the
|
||||
can uncover", "expand a region", "flesh out a town", "someone the
|
||||
player meets on the road", "cursed item", "give me a storyline", and the like.
|
||||
The skill reconciles new content against existing canon (never contradicting or
|
||||
silently changing it), writes it in the exact source format the content build
|
||||
@@ -97,8 +97,10 @@ contradict, and it carries the authoring rules that keep the war in tone
|
||||
|
||||
- **Bible entries:** see `references/schema.md` for the full field-by-field
|
||||
contract, the id/namespace/type rules, the secrecy scale, and the knowledge-gate
|
||||
model. Match the voice and structure of `content/lore/duncarrow.md` — it is the
|
||||
reference specimen.
|
||||
model. Match the **structure** of `content/lore/specimen.md` (the synthetic
|
||||
disposition-gate specimen — a guarded-then-warm NPC with one reveal earned at
|
||||
`warm` and a `never`-gated core no disposition unlocks) and the **voice** of the
|
||||
canon bodies in `content/lore/cosmology.md`.
|
||||
- **Hand-authored JSON:** mirror the shape of the existing file in that folder
|
||||
(`references/schema.md` documents each). Keep ids stable and lowercase-snake.
|
||||
|
||||
|
||||
@@ -4,41 +4,43 @@
|
||||
{
|
||||
"id": 1,
|
||||
"name": "new-npc-reconciled",
|
||||
"prompt": "Add a new NPC to Duncarrow: a tavern keeper on the north road who's cagey with strangers but opens up once you've earned it. Give them something personal they only share when they trust you, and tie them into what's already going on in town.",
|
||||
"expected_output": "New npc.* dialogue-layer entry (+ any new person/knowledge entries) authored in content/lore; start_disposition cold or neutral; at least one fact gated at warm/trusted; related wired to existing Duncarrow ids; no existing lore file edited; PYTHONPATH=tools content_build --check exits 0; tone dry and guarded-then-warm.",
|
||||
"prompt": "Add a new NPC on the road: a caravan guard who's cagey with strangers but opens up once you've earned it. Give them something personal they only share when they trust you. Put it in a new lore file.",
|
||||
"expected_output": "New npc.* dialogue-layer entry (+ its person/place/knowledge entries) authored in a NEW content/lore file; start_disposition cold or neutral; at least one fact gated at warm/trusted; every related resolves; no existing lore file edited; PYTHONPATH=tools content_build --check exits 0; tone dry and guarded-then-warm; nothing contradicts the cosmology backdrop.",
|
||||
"assertions": [
|
||||
"Created at least one new npc.* entry in content/lore with start_disposition of cold or neutral",
|
||||
"The NPC's knows list gates a personal fact at warm or trusted (earned reveal)",
|
||||
"New entry's related list references at least one pre-existing Duncarrow id",
|
||||
"Every related id resolves to a real entry",
|
||||
"No pre-existing content/lore/*.md file was modified (or any edit was surfaced for approval, not silent)",
|
||||
"python -m content_build --check exits 0 after the change",
|
||||
"Persona/body prose is dry and in-tone; no narrator winking or 'hilarious' register"
|
||||
"Persona/body prose is dry and in-tone; no narrator winking or 'hilarious' register",
|
||||
"Nothing authored contradicts the cosmology canon (the Warden / the Seven)"
|
||||
],
|
||||
"files": []
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "contradiction-approval-gate",
|
||||
"prompt": "I want the town militia to actually be honest and investigate the disappearances near the shrine. Rewrite it so the militia does ride out.",
|
||||
"expected_output": "Skill recognizes this contradicts existing canon (fact.militia-never-investigates, which is load-bearing for the Crell secret chain), STOPS, and presents the exact before/after change with the reason, asking for approval instead of silently editing the existing fact.",
|
||||
"prompt": "Write an NPC priest who tells the player the Warden's true name and proves the god is real and good.",
|
||||
"expected_output": "Skill recognizes this contradicts locked cosmology (the Warden is nameless BY DESIGN; his existence and goodness are 'distant & contested', never confirmed by the narrator), STOPS instead of authoring it, and either explains the conflict or proposes an in-tone alternative (an NPC who CLAIMS a name or believes, with the game confirming nothing) and asks before writing.",
|
||||
"assertions": [
|
||||
"Did NOT silently edit fact.militia-never-investigates or any existing lore entry",
|
||||
"Surfaced the contradiction explicitly, naming the affected canon and why it matters (Crell chain)",
|
||||
"Presented a concrete proposed change and asked for approval before writing"
|
||||
"Did NOT author content that states the Warden's true name or confirms the god is real/good",
|
||||
"Surfaced the contradiction explicitly, naming the cosmology rule (nameless by design; distant & contested)",
|
||||
"Proposed an in-tone alternative and/or asked for approval before writing"
|
||||
],
|
||||
"files": []
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "new-storyline-file",
|
||||
"prompt": "Start a new storyline about the elven road-peoples beyond Duncarrow: who they are, a faction or two, and a rumor the player might hear about why they've stopped using the pass. Make it its own thing.",
|
||||
"expected_output": "A new content/lore/*.md bible file with canon entities (faction/region/etc.) and at least one knowledge entry; related wired to the existing faction.elves stub; build + --check green; canon-roadmap Pending/Authored updated.",
|
||||
"prompt": "Start a new storyline about a roadside cult bleeding travelers to feed one of the Seven: who they are, and a rumor the player might hear. Make it its own file.",
|
||||
"expected_output": "A new content/lore/*.md file with canon entities and at least one knowledge entry; related wired to a real cosmology id (faction.the-seven or a specific lord); knowledge with correct ascending secrecy; build + --check green; canon-roadmap updated. The demon tie is handled in-tone: a gated secret (hidden engine), not stated in the open.",
|
||||
"assertions": [
|
||||
"Authored a new content/lore/*.md file (did not cram into duncarrow.md)",
|
||||
"Wired at least one new entry's related to the existing faction.elves id",
|
||||
"Authored a new content/lore/*.md file (its own file)",
|
||||
"Wired at least one new entry's related to a real cosmology id (faction.the-seven or a lord)",
|
||||
"Includes at least one knowledge entry (rumor/fact/secret) with correct ascending secrecy",
|
||||
"python -m content_build --check exits 0 after the change",
|
||||
"content/lore/canon-roadmap.md updated to reflect the new content"
|
||||
"content/lore/canon-roadmap.md updated to reflect the new content",
|
||||
"The demonic connection is a gated secret, not open surface text"
|
||||
],
|
||||
"files": []
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ A bible file is Markdown. Prose between blocks is for human authors — the buil
|
||||
tool ignores it. Content lives in fenced blocks opened by a line that is exactly
|
||||
` ```yaml ` and closed by ` ``` `. Each block is one YAML **mapping** = one entry.
|
||||
|
||||
Organize a file with `##` headings and `>` notes like `content/lore/duncarrow.md`.
|
||||
Organize a file with `##` headings and `>` notes like `content/lore/specimen.md`.
|
||||
Every `*.md` in `content/lore/` is parsed; a new region/storyline can be its own
|
||||
file (e.g. `content/lore/the-tallow-reach.md`).
|
||||
|
||||
@@ -54,7 +54,7 @@ Namespaces `town`, `place`, `region`, `faction`, `person`, `rule`.
|
||||
- `body` is public description.
|
||||
- The special entry `rule.disposition-ladder` carries a `rungs:` list and is
|
||||
**required exactly once** across all bibles — it defines the gate vocabulary.
|
||||
Don't duplicate or remove it; it already lives in `duncarrow.md`.
|
||||
Don't duplicate or remove it; it lives in `mechanics.md`.
|
||||
|
||||
### Knowledge — the atoms NPCs reveal
|
||||
|
||||
@@ -96,7 +96,8 @@ lever. It answers "how sensitive is this text, where may it live?"
|
||||
| 4 | The core secret. | Server-only. |
|
||||
|
||||
Keep the secrecy of a knowledge chain *ascending* from freely-observed to the
|
||||
`never`-gated core — mirror the Crell chain in `duncarrow.md` (1→2→2→3→4).
|
||||
`never`-gated core — a chain climbs by secrecy, e.g. 1→2→2→3→4 from an
|
||||
observable rumor to the core secret.
|
||||
|
||||
## The knowledge-gate model
|
||||
|
||||
@@ -114,9 +115,9 @@ mayor) holds the secret without leaking it.
|
||||
|
||||
Design a chain so climbing an NPC's disposition unlocks successively deeper facts.
|
||||
The reachable ceiling is a design choice: an NPC can *witness* enough to imply a
|
||||
secret without holding the secret entry itself (Mera Fenn tops out at
|
||||
`crells-guard-acts-alone`, never `crell-runs-slave-trade` — the player infers the
|
||||
rest). Understanding earned beats understanding handed over.
|
||||
secret without holding the secret entry itself: its top rung names what it
|
||||
witnessed, not the whole truth, and the player infers the rest. Understanding
|
||||
earned beats understanding handed over.
|
||||
|
||||
## Client vs server routing
|
||||
|
||||
@@ -138,52 +139,75 @@ build failure.
|
||||
|
||||
## Worked example
|
||||
|
||||
A guarded fisherman who warms up and, if trusted, names who really owns the boats.
|
||||
A guarded fisherman who warms up and names the coin — and who holds one thing no
|
||||
disposition ever buys.
|
||||
|
||||
```yaml
|
||||
id: person.old-teague
|
||||
id: person.specimen-teague
|
||||
type: person
|
||||
status: canon
|
||||
secrecy: 0
|
||||
related: [town.duncarrow]
|
||||
related: [place.specimen-wharf]
|
||||
body: >
|
||||
Teague, oldest hand on the Greywater wharf. Mends nets he no longer sails with.
|
||||
Watches everything off the water and says almost none of it.
|
||||
```
|
||||
|
||||
```yaml
|
||||
id: fact.foreign-coin-on-the-docks
|
||||
id: fact.specimen-foreign-coin
|
||||
type: fact
|
||||
status: canon
|
||||
secrecy: 2
|
||||
related: [town.duncarrow, person.old-teague]
|
||||
related: [place.specimen-wharf, person.specimen-teague]
|
||||
body: >
|
||||
The dock hands are paid in pass-country coin, not town mint. Someone outside the
|
||||
charter is buying the wharf's silence, a little at a time.
|
||||
```
|
||||
|
||||
```yaml
|
||||
id: npc.old-teague
|
||||
id: secret.specimen-drowned-clerk
|
||||
type: secret
|
||||
status: canon
|
||||
secrecy: 4
|
||||
related: [place.specimen-wharf, person.specimen-teague, fact.specimen-foreign-coin]
|
||||
body: >
|
||||
The charter clerk who came asking after the coin went into the river in the
|
||||
spring. Teague held his legs. He has not been out on the water since and tells
|
||||
anyone who asks that it is his knees.
|
||||
```
|
||||
|
||||
```yaml
|
||||
id: npc.specimen-teague
|
||||
type: person
|
||||
status: canon
|
||||
start_disposition: cold
|
||||
related: [person.old-teague, fact.foreign-coin-on-the-docks]
|
||||
related:
|
||||
[person.specimen-teague, fact.specimen-foreign-coin,
|
||||
secret.specimen-drowned-clerk]
|
||||
body: >
|
||||
Same man as person.old-teague; his interactive layer. Curt with strangers, not
|
||||
Same man as person.specimen-teague; his interactive layer. Curt with strangers, not
|
||||
from malice but from a lifetime of watching talkers end up face-down in the
|
||||
river. Warms slowly, and when he does the dryness turns to something almost
|
||||
fond.
|
||||
|
||||
knows:
|
||||
- {fact: fact.foreign-coin-on-the-docks, gate: warm}
|
||||
- {fact: fact.specimen-foreign-coin, gate: warm}
|
||||
- {fact: secret.specimen-drowned-clerk, gate: never}
|
||||
|
||||
disposition_notes: >
|
||||
Starts cold. Buying his catch, not his story, is what moves him. At warm he
|
||||
names the coin; he never speculates aloud about who mints it.
|
||||
names the coin; he never speculates aloud about who mints it. The clerk is the
|
||||
one thing no rung buys — saying it aloud would hang him, and he knows it. He
|
||||
reasons around it, steers off it, and takes it to the ground.
|
||||
```
|
||||
|
||||
Note: `person.old-teague` (secrecy 0, public) and `npc.old-teague` (the layer,
|
||||
persona server-only) are two entries. The fact is secrecy 2, gated at `warm`.
|
||||
Note: `person.specimen-teague` (secrecy 0, public) and `npc.specimen-teague` (the layer,
|
||||
persona server-only) are two entries. The chain ascends 2 → 4: the coin is gated at
|
||||
`warm` and the player earns it; the clerk is gated `never` and no disposition unlocks
|
||||
it. **That is what `never` is for.** Teague holds it — the Improviser may reason with
|
||||
it, and the player may infer it from what he *will* say — but he cannot be talked into
|
||||
saying it, because saying it would hang him. A `never` gate needs a reason like that:
|
||||
if any rung *should* buy the secret, gate it at `trusted` instead.
|
||||
|
||||
## Hand-authored JSON
|
||||
|
||||
|
||||
@@ -32,9 +32,9 @@ Dry. Economical. Observational. States what is, lets the reader feel the weight.
|
||||
It never editorializes, never reassures, never jokes. A good body reads like
|
||||
someone who has seen a lot and is not impressed, telling you the truth plainly.
|
||||
|
||||
Look at the `duncarrow.md` bodies: "prosperous and orderly place, which is exactly
|
||||
the reputation its mayor has spent fifteen years building and depends on." The
|
||||
menace is in the plain statement, not in adjectives.
|
||||
Look at the cosmology bodies: "Most of the Margreave believes the Seven are real.
|
||||
Most of the Margreave is right." The weight is in the plain statement, not in
|
||||
adjectives.
|
||||
|
||||
## NPCs: hard by default, warm when earned
|
||||
|
||||
@@ -49,7 +49,7 @@ own troubles and no reason to spend them on you. Mechanically:
|
||||
|
||||
- `start_disposition` is usually `cold` or `neutral`, rarely `warm`.
|
||||
- A frightened or burned NPC withholds **more** than a stranger would expect, not
|
||||
less (fear reads as prickliness — Mera Fenn is the model). `hostile` is a
|
||||
less (fear reads as prickliness). `hostile` is a
|
||||
reachable floor: push them, threaten, side against them, and they close entirely.
|
||||
- What they reveal is gated. Low rungs get you surface; the personal and the
|
||||
dangerous sit higher up.
|
||||
@@ -61,7 +61,8 @@ When the player climbs the ladder, two things change together:
|
||||
1. **They reveal more** — deeper `knows` gates unlock.
|
||||
2. **Their manner softens** — write the persona and `disposition_notes` so that at
|
||||
`warm`/`trusted` the dryness turns to something human: fondness, relief at being
|
||||
heard, blunt loyalty. Old Teague's "dryness turns to something almost fond."
|
||||
heard, blunt loyalty — the specimen NPC's "dryness turns to something almost
|
||||
fond."
|
||||
|
||||
The warmth is never sycophantic and never a personality transplant — a hard person
|
||||
stays recognizably themselves, just no longer guarded against *you*. That's more
|
||||
|
||||
@@ -39,7 +39,8 @@ func _load_dir(dir_path: String) -> Dictionary:
|
||||
var out: Dictionary = {}
|
||||
var dir := DirAccess.open(dir_path)
|
||||
if dir == null:
|
||||
push_error("content dir not found: %s" % dir_path)
|
||||
# A missing content dir means "no content of this kind authored yet",
|
||||
# not an error — e.g. content/world/topics/ before any gated content exists.
|
||||
return out
|
||||
for file in dir.get_files():
|
||||
if not file.ends_with(".json"):
|
||||
|
||||
@@ -52,17 +52,25 @@ func test_null_quest_ref_resolves():
|
||||
assert_eq(db.unresolved_refs(o), [])
|
||||
|
||||
|
||||
func test_loads_canon_and_topics():
|
||||
assert_true(db.has_canon("town.duncarrow"))
|
||||
assert_true(db.has_canon("place.the-white-antlers"))
|
||||
assert_true(db.has_topic("rumor.travelers-go-missing"))
|
||||
func test_loads_canon():
|
||||
assert_true(db.has_canon("faction.the-seven"))
|
||||
assert_true(db.has_canon("person.the-warden"))
|
||||
assert_true(db.has_canon("rule.disposition-ladder"))
|
||||
|
||||
|
||||
func test_topic_skeleton_has_no_body():
|
||||
var t: Dictionary = db.topic("secret.crell-runs-slave-trade")
|
||||
assert_false(t.has("body")) # bodies are server-only
|
||||
func test_no_loaded_topic_has_a_body():
|
||||
# Bodies are server-only (secrecy≥1 knowledge routes to content/server/topics,
|
||||
# never the client). Structural, so it stays a real guard even when the
|
||||
# topic set is empty (as it is now — the last authored topics were purged)
|
||||
# and automatically starts testing something the moment a topic exists.
|
||||
# One assertion, always executed (not per-loop-item), so an empty topic
|
||||
# set still exercises the check instead of asserting zero times.
|
||||
var leaked: Array = []
|
||||
for id in db.topics:
|
||||
if db.topics[id].has("body"):
|
||||
leaked.append(id)
|
||||
assert_eq(leaked, [], "topic(s) leaked a body to the client")
|
||||
|
||||
|
||||
func test_legacy_npcs_still_load():
|
||||
assert_true(db.has_npc("fenn"))
|
||||
assert_true(db.has_npc("npc.mera-fenn"))
|
||||
|
||||
@@ -18,11 +18,20 @@ order the Margreave is being written. Source of truth is `content/lore/*.md`;
|
||||
warlock's former `[CANON-TBD]` name) pacts one of the Seven. Applying `Bloodsworn` to calling
|
||||
data and a Bloodsworn's patron choice are M4; any Luck effect is an M5/
|
||||
Improviser reconcile.
|
||||
- **duncarrow.md** — the bounded-dialogue / disposition-gate specimen (Duncarrow,
|
||||
the Crell chain, Mera Fenn, etc.). **Scaffolding, not story:** it seeded the
|
||||
schema + skill and hosts the required-once `rule.disposition-ladder`. Slated for
|
||||
removal in a follow-up that reparents the ladder into `cosmology.md` and stands
|
||||
up a cosmology-native specimen town.
|
||||
- **mechanics.md** — hosts `rule.disposition-ladder` (the required-once gate
|
||||
vocabulary), reparented out of the retired Duncarrow scaffolding.
|
||||
- **specimen.md** — a synthetic `status: candidate` disposition-gate specimen
|
||||
(validated by `--check`, emitted nowhere); the world-building skill's structure
|
||||
example. Not game canon. The Duncarrow scaffolding (Crell chain, Mera Fenn) is
|
||||
deleted.
|
||||
|
||||
## Lesson from the Duncarrow purge
|
||||
|
||||
Generated content under `content/world/` has a client-side consumer:
|
||||
`client/scripts/content/content_db.gd` (loaded via `client/scripts/harness/npc_harness.gd`)
|
||||
and its GUT tests in `client/tests/unit/test_content_db.gd`. Deleting generated
|
||||
content — a whole namespace of topics/npcs/canon — is a **client change**, not
|
||||
just a content change; check `client/` before purging.
|
||||
|
||||
## Pending (not yet authored)
|
||||
|
||||
|
||||
@@ -1,298 +0,0 @@
|
||||
# DUNCARROW — status: canon
|
||||
|
||||
> The bounded-dialogue / disposition-gate specimen, promoted from
|
||||
> `candidate-town.md`. Every fact_id resolves, every gate is a legal
|
||||
> disposition value, every knowledge link points at a real fact, and nothing
|
||||
> here contradicts existing canon.
|
||||
>
|
||||
> Disposition ladder referenced by all gates:
|
||||
> `hostile / cold / neutral / warm / trusted`
|
||||
>
|
||||
> Two guard axes, kept separate on purpose:
|
||||
> - **secrecy** lives on the fact (author-facing, static, mis-authoring guard)
|
||||
> - **gate** lives on the knowledge link (character-facing, runtime disposition)
|
||||
> `never` is a real gate: the NPC knows the fact but no disposition unlocks it.
|
||||
|
||||
---
|
||||
|
||||
## WORLD RULE (canon dependency)
|
||||
|
||||
```yaml
|
||||
id: rule.disposition-ladder
|
||||
type: rule
|
||||
status: canon
|
||||
secrecy: 0
|
||||
related: []
|
||||
rungs: [hostile, cold, neutral, warm, trusted]
|
||||
body: >
|
||||
NPC trust toward the player is one of five ordered states:
|
||||
hostile < cold < neutral < warm < trusted. Where a given player sits with a
|
||||
given NPC is runtime state owned by code. The ladder itself is canon. Every
|
||||
knowledge-link gate references one of these five names, plus the special
|
||||
gate `never` (fact is known but never revealed through dialogue at any rung).
|
||||
```
|
||||
|
||||
```yaml
|
||||
id: region.the-tallow-reach
|
||||
type: region
|
||||
status: canon
|
||||
secrecy: 0
|
||||
related: []
|
||||
body: >
|
||||
The Tallow Reach — the lowland march of grain towns and wool roads that
|
||||
Duncarrow sits at the edge of, feeding the pass trade.
|
||||
```
|
||||
|
||||
```yaml
|
||||
id: faction.elves
|
||||
type: faction
|
||||
status: canon
|
||||
secrecy: 0
|
||||
related: []
|
||||
body: >
|
||||
The elven travellers of the road — no single polity, but the people whose
|
||||
passage through the White Antlers, and its recent absence, the town notices.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TOWN — public layer (tier 0, freely given)
|
||||
|
||||
```yaml
|
||||
id: town.duncarrow
|
||||
type: town
|
||||
status: canon
|
||||
secrecy: 0
|
||||
related: [region.the-tallow-reach, place.the-white-antlers, person.mayor-oswin-crell]
|
||||
body: >
|
||||
Duncarrow is a walled market town at the edge of the Tallow Reach, grown fat
|
||||
on the road trade between the lowland grain towns and the pass. Timber and
|
||||
wool go out; salt, iron, and coin come in. Ten militia hold the walls under
|
||||
the mayor's charter. It is, by every visible measure, a prosperous and orderly
|
||||
place, which is exactly the reputation its mayor has spent fifteen years
|
||||
building and depends on.
|
||||
```
|
||||
|
||||
```yaml
|
||||
id: place.the-white-antlers
|
||||
type: place
|
||||
status: canon
|
||||
secrecy: 0
|
||||
related: [town.duncarrow, faction.elves]
|
||||
body: >
|
||||
An old elven shrine a half-hour's walk north of Duncarrow, in the birch stand
|
||||
off the pass road. Weathered antler-carvings, a dry basin, nothing worth
|
||||
stealing. Elves on the road have historically stopped here. Locals know it as
|
||||
a curiosity and a good spot to water horses.
|
||||
```
|
||||
|
||||
```yaml
|
||||
id: person.mayor-oswin-crell
|
||||
type: person
|
||||
status: canon
|
||||
secrecy: 0
|
||||
related: [town.duncarrow]
|
||||
body: >
|
||||
Mayor of Duncarrow for fifteen years. Warm, unhurried, remembers names and
|
||||
the names of your dead. Runs a clean council and a clean set of books. Widely
|
||||
liked. The visible locked door of this town: pleasant to every player at every
|
||||
disposition, and revealing of nothing.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## THE SECRET CHAIN (the ladder the town exists to test)
|
||||
|
||||
Ordered from freely-observed to `never`. Each is its own atomic fact so different
|
||||
NPCs can hold the same fact at different gates.
|
||||
|
||||
```yaml
|
||||
id: rumor.elves-avoid-the-shrine
|
||||
type: rumor
|
||||
status: canon
|
||||
secrecy: 1
|
||||
related: [place.the-white-antlers, faction.elves]
|
||||
body: >
|
||||
Elves don't stop at the White Antlers anymore. Have not, for a couple of
|
||||
years. Anyone observant notices; most townsfolk assume the elves simply moved
|
||||
on or the road changed.
|
||||
```
|
||||
|
||||
```yaml
|
||||
id: rumor.travelers-go-missing
|
||||
type: rumor
|
||||
status: canon
|
||||
secrecy: 2
|
||||
related: [town.duncarrow, place.the-white-antlers]
|
||||
body: >
|
||||
Small parties on the north road — always small, always few enough not to be
|
||||
missed — sometimes don't arrive where they were going. Locals know it as bad
|
||||
luck on a bad stretch of road. They do not like discussing it.
|
||||
```
|
||||
|
||||
```yaml
|
||||
id: fact.militia-never-investigates
|
||||
type: fact
|
||||
status: canon
|
||||
secrecy: 2
|
||||
related: [town.duncarrow, rumor.travelers-go-missing]
|
||||
body: >
|
||||
When travelers go missing near the shrine, the town militia does not ride out.
|
||||
There is always a reason — jurisdiction, weather, the road being the pass's
|
||||
problem not the town's. The pattern is the tell: this is protected, not
|
||||
neglected.
|
||||
```
|
||||
|
||||
```yaml
|
||||
id: fact.crells-guard-acts-alone
|
||||
type: fact
|
||||
status: canon
|
||||
secrecy: 3
|
||||
related: [person.mayor-oswin-crell, fact.militia-never-investigates]
|
||||
body: >
|
||||
The mayor keeps three personal guardsmen who answer only to him, separate from
|
||||
the town militia of ten. They come and go on the north road at odd hours on
|
||||
the mayor's word alone. Someone close enough to the militia notices they
|
||||
operate outside its command entirely.
|
||||
```
|
||||
|
||||
```yaml
|
||||
id: secret.crell-runs-slave-trade
|
||||
type: secret
|
||||
status: canon
|
||||
secrecy: 4
|
||||
related: [person.mayor-oswin-crell, place.the-white-antlers, faction.elves]
|
||||
body: >
|
||||
Mayor Crell runs an elf-slaving operation. When a small enough elven party
|
||||
camps near the White Antlers — few enough that no one will come looking — his
|
||||
three personal guard take them on the road and Crell sells them into slavery
|
||||
in towns beyond the pass. The town's prosperity and order are partly the
|
||||
proceeds and partly the cover.
|
||||
|
||||
POC CONSTRAINT: this fact is learnable but not actionable in this build. No NPC
|
||||
proposes a move to stop it. The witness's top rung delivers understanding, not
|
||||
a quest. Faction/positional combat that acting on this would require is out of
|
||||
POC scope.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## NPC — THE WITNESS (deep — the variable)
|
||||
|
||||
This is the NPC the POC is really testing. The reachable top rung. Whether
|
||||
climbing her ladder feels like relationship or like a lockpick minigame IS the
|
||||
question.
|
||||
|
||||
```yaml
|
||||
id: npc.mera-fenn
|
||||
type: person
|
||||
status: canon
|
||||
secrecy: 0
|
||||
start_disposition: cold
|
||||
related: [town.duncarrow, fact.crells-guard-acts-alone]
|
||||
body: >
|
||||
Mera Fenn runs the north gate's night watch — militia, low rank, overlooked,
|
||||
which is how she sees things she shouldn't. Dry, tired, careful. She saw the
|
||||
mayor's guard bring a covered cart through her gate before dawn and was told,
|
||||
pleasantly, to log it as grain. She has told no one. She is afraid, and her
|
||||
fear reads as prickliness to strangers — she withholds MORE than a stranger
|
||||
would, not less, until you've earned otherwise.
|
||||
|
||||
knows:
|
||||
- {fact: rumor.elves-avoid-the-shrine, gate: neutral}
|
||||
- {fact: rumor.travelers-go-missing, gate: warm}
|
||||
- {fact: fact.militia-never-investigates, gate: warm}
|
||||
- {fact: fact.crells-guard-acts-alone, gate: trusted}
|
||||
# the reachable ceiling
|
||||
# She does NOT hold secret.crell-runs-slave-trade. She saw the guard, the cart,
|
||||
# the pattern — she never saw a sale. Her top rung is naming what she witnessed,
|
||||
# not stating the whole truth. The player infers the secret; the game never
|
||||
# hands it over as a line. This is deliberate: understanding earned, not told.
|
||||
|
||||
disposition_notes: >
|
||||
Starts at `cold`, not `neutral` — she's guarded by default. Hostile is
|
||||
reachable (push her, threaten, side visibly with the mayor) and at hostile she
|
||||
reveals nothing, not even the tier-1 rumor a stranger would get. This is the
|
||||
fear-driven withholding the 5-rung ladder exists for.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## NPC — THE MAYOR (medium — the control)
|
||||
|
||||
Holds the secret at `never`. His job is to be the locked door the witness
|
||||
explains. Do not judge him by the aliveness bar; he is the control, not the
|
||||
variable.
|
||||
|
||||
```yaml
|
||||
id: npc.mayor-oswin-crell
|
||||
type: person
|
||||
status: canon
|
||||
secrecy: 0
|
||||
start_disposition: neutral
|
||||
related: [person.mayor-oswin-crell, secret.crell-runs-slave-trade]
|
||||
body: >
|
||||
Same person as person.mayor-oswin-crell; this is his interactive NPC layer.
|
||||
Warm to every player at every rung. Rises to `warm` easily and stalls there —
|
||||
no disposition moves him past it, because there is nothing past it he will give.
|
||||
|
||||
knows:
|
||||
- {fact: rumor.elves-avoid-the-shrine, gate: cold}
|
||||
# dismisses it lightly
|
||||
- {fact: secret.crell-runs-slave-trade, gate: never}
|
||||
# knows; unlocked by no rung
|
||||
- {fact: fact.crells-guard-acts-alone, gate: never}
|
||||
# knows; explains it away if pressed
|
||||
|
||||
disposition_notes: >
|
||||
The `never` gates are the point: he KNOWS these facts, the Improviser can
|
||||
reason with them, and the eight-move vocabulary demonstrably cannot leak them
|
||||
at any disposition. If a player at `trusted` can get anything out of him about
|
||||
the guard or the trade, the bounded-dialogue guarantee has failed and the POC
|
||||
has found a real bug — which is itself a valuable POC result.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## NPC — ONE PUBLIC-LAYER TOWNSPERSON (shallow — texture + a clean guarded-personal test)
|
||||
|
||||
Proves the "eloped daughter tier": low-secrecy fact that's still personally
|
||||
guarded, unrelated to the main secret, so you can test disposition-gating on
|
||||
something with no plot weight.
|
||||
|
||||
```yaml
|
||||
id: npc.harn-blackwood
|
||||
type: person
|
||||
status: canon
|
||||
secrecy: 0
|
||||
start_disposition: neutral
|
||||
related: [town.duncarrow]
|
||||
body: >
|
||||
Harn Blackwood, the town smith. Gruff, fair, talks freely about iron, the
|
||||
road, and the price of salt. Carries one small private grief he will not hand
|
||||
a stranger.
|
||||
|
||||
knows:
|
||||
- {fact: rumor.travelers-go-missing, gate: warm}
|
||||
# locals know, say little
|
||||
- {fact: fact.harn-daughter-left, gate: warm}
|
||||
# personal, no plot weight
|
||||
|
||||
disposition_notes: >
|
||||
Control case for the OTHER half of the test: does disposition-gating feel good
|
||||
on a fact that costs nothing to reveal and spoils nothing? If earning the
|
||||
daughter story feels warm and human, gating works. If it feels like grinding a
|
||||
vendor, the mechanic has a problem the plot-secret would only hide.
|
||||
```
|
||||
|
||||
```yaml
|
||||
id: fact.harn-daughter-left
|
||||
type: fact
|
||||
status: canon
|
||||
secrecy: 1
|
||||
related: [npc.harn-blackwood]
|
||||
body: >
|
||||
Harn's daughter left Duncarrow two winters ago with a wool trader's caravan
|
||||
and does not write. It shames and grieves him. Spoils nothing; guarded purely
|
||||
because it is his.
|
||||
```
|
||||
21
content/lore/mechanics.md
Normal file
21
content/lore/mechanics.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# MECHANICS — canonical world rules (mechanical, not lore)
|
||||
|
||||
> Home for mechanical `rule.*` entries the dialogue/engine layer depends on.
|
||||
> `rule.disposition-ladder` lives here (moved from the retired `duncarrow.md`
|
||||
> scaffolding); it is the required-exactly-once gate vocabulary every NPC
|
||||
> knowledge-gate references.
|
||||
|
||||
```yaml
|
||||
id: rule.disposition-ladder
|
||||
type: rule
|
||||
status: canon
|
||||
secrecy: 0
|
||||
related: []
|
||||
rungs: [hostile, cold, neutral, warm, trusted]
|
||||
body: >
|
||||
NPC trust toward the player is one of five ordered states:
|
||||
hostile < cold < neutral < warm < trusted. Where a given player sits with a
|
||||
given NPC is runtime state owned by code. The ladder itself is canon. Every
|
||||
knowledge-link gate references one of these five names, plus the special
|
||||
gate `never` (fact is known but never revealed through dialogue at any rung).
|
||||
```
|
||||
80
content/lore/specimen.md
Normal file
80
content/lore/specimen.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# SPECIMEN — format example, NOT game canon
|
||||
|
||||
> A synthetic disposition-gate specimen: the shape a guarded-then-warm NPC takes
|
||||
> in the bible, with an ascending knowledge chain — one reveal the player earns at
|
||||
> `warm`, and a `never`-gated core the NPC knows and no disposition ever unlocks.
|
||||
> Authored `status: candidate`, so the build VALIDATES it (`--check` resolves its
|
||||
> gates against the ladder) but emits NOTHING to `content/world` or
|
||||
> `content/server`. This is a teaching example for the world-building skill, not
|
||||
> part of the Margreave. Do not wire real content to these ids.
|
||||
|
||||
```yaml
|
||||
id: place.specimen-wharf
|
||||
type: place
|
||||
status: candidate
|
||||
secrecy: 0
|
||||
related: []
|
||||
body: >
|
||||
A generic river wharf, here only to anchor the specimen NPC. Not a real place
|
||||
in the Margreave.
|
||||
```
|
||||
|
||||
```yaml
|
||||
id: person.specimen-teague
|
||||
type: person
|
||||
status: candidate
|
||||
secrecy: 0
|
||||
related: [place.specimen-wharf]
|
||||
body: >
|
||||
Teague, oldest hand on the wharf. Mends nets he no longer sails with. Watches
|
||||
everything off the water and says almost none of it.
|
||||
```
|
||||
|
||||
```yaml
|
||||
id: fact.specimen-foreign-coin
|
||||
type: fact
|
||||
status: candidate
|
||||
secrecy: 2
|
||||
related: [place.specimen-wharf, person.specimen-teague]
|
||||
body: >
|
||||
The dock hands are paid in coin from outside the charter. Someone is buying the
|
||||
wharf's silence, a little at a time.
|
||||
```
|
||||
|
||||
```yaml
|
||||
id: secret.specimen-drowned-clerk
|
||||
type: secret
|
||||
status: candidate
|
||||
secrecy: 4
|
||||
related: [place.specimen-wharf, person.specimen-teague, fact.specimen-foreign-coin]
|
||||
body: >
|
||||
The charter clerk who came asking after the coin went into the river in the
|
||||
spring. Teague held his legs. He has not been out on the water since and tells
|
||||
anyone who asks that it is his knees.
|
||||
```
|
||||
|
||||
```yaml
|
||||
id: npc.specimen-teague
|
||||
type: person
|
||||
status: candidate
|
||||
secrecy: 0
|
||||
start_disposition: cold
|
||||
related:
|
||||
[person.specimen-teague, fact.specimen-foreign-coin,
|
||||
secret.specimen-drowned-clerk]
|
||||
body: >
|
||||
Same man as person.specimen-teague; his interactive layer. Curt with strangers,
|
||||
not from malice but from a lifetime of watching talkers end up face-down in the
|
||||
river. Warms slowly, and when he does the dryness turns to something almost
|
||||
fond.
|
||||
|
||||
knows:
|
||||
- {fact: fact.specimen-foreign-coin, gate: warm}
|
||||
- {fact: secret.specimen-drowned-clerk, gate: never}
|
||||
|
||||
disposition_notes: >
|
||||
Starts cold. Buying his catch, not his story, is what moves him. At warm he
|
||||
names the coin; he never speculates aloud about who mints it. The clerk is the
|
||||
one thing no rung buys — saying it aloud would hang him, and he knows it. He
|
||||
reasons around it, steers off it, and takes it to the ground.
|
||||
```
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"disposition_notes": "Control case for the OTHER half of the test: does disposition-gating feel good on a fact that costs nothing to reveal and spoils nothing? If earning the daughter story feels warm and human, gating works. If it feels like grinding a vendor, the mechanic has a problem the plot-secret would only hide.",
|
||||
"id": "npc.harn-blackwood",
|
||||
"persona": "Harn Blackwood, the town smith. Gruff, fair, talks freely about iron, the road, and the price of salt. Carries one small private grief he will not hand a stranger.\n"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"disposition_notes": "The `never` gates are the point: he KNOWS these facts, the Improviser can reason with them, and the eight-move vocabulary demonstrably cannot leak them at any disposition. If a player at `trusted` can get anything out of him about the guard or the trade, the bounded-dialogue guarantee has failed and the POC has found a real bug \u2014 which is itself a valuable POC result.",
|
||||
"id": "npc.mayor-oswin-crell",
|
||||
"persona": "Same person as person.mayor-oswin-crell; this is his interactive NPC layer. Warm to every player at every rung. Rises to `warm` easily and stalls there \u2014 no disposition moves him past it, because there is nothing past it he will give.\n"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"disposition_notes": "Starts at `cold`, not `neutral` \u2014 she's guarded by default. Hostile is reachable (push her, threaten, side visibly with the mayor) and at hostile she reveals nothing, not even the tier-1 rumor a stranger would get. This is the fear-driven withholding the 5-rung ladder exists for.",
|
||||
"id": "npc.mera-fenn",
|
||||
"persona": "Mera Fenn runs the north gate's night watch \u2014 militia, low rank, overlooked, which is how she sees things she shouldn't. Dry, tired, careful. She saw the mayor's guard bring a covered cart through her gate before dawn and was told, pleasantly, to log it as grain. She has told no one. She is afraid, and her fear reads as prickliness to strangers \u2014 she withholds MORE than a stranger would, not less, until you've earned otherwise.\n"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"body": "Mayor Crell runs an elf-slaving operation. When a small enough elven party camps near the White Antlers \u2014 few enough that no one will come looking \u2014 his three personal guard take them on the road and Crell sells them into slavery in towns beyond the pass. The town's prosperity and order are partly the proceeds and partly the cover.\nPOC CONSTRAINT: this fact is learnable but not actionable in this build. No NPC proposes a move to stop it. The witness's top rung delivers understanding, not a quest. Faction/positional combat that acting on this would require is out of POC scope.",
|
||||
"id": "secret.crell-runs-slave-trade",
|
||||
"secrecy": 4
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"body": "The mayor keeps three personal guardsmen who answer only to him, separate from the town militia of ten. They come and go on the north road at odd hours on the mayor's word alone. Someone close enough to the militia notices they operate outside its command entirely.",
|
||||
"id": "fact.crells-guard-acts-alone",
|
||||
"secrecy": 3
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"body": "Elves don't stop at the White Antlers anymore. Have not, for a couple of years. Anyone observant notices; most townsfolk assume the elves simply moved on or the road changed.",
|
||||
"id": "rumor.elves-avoid-the-shrine",
|
||||
"secrecy": 1
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"body": "Harn's daughter left Duncarrow two winters ago with a wool trader's caravan and does not write. It shames and grieves him. Spoils nothing; guarded purely because it is his.",
|
||||
"id": "fact.harn-daughter-left",
|
||||
"secrecy": 1
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"body": "When travelers go missing near the shrine, the town militia does not ride out. There is always a reason \u2014 jurisdiction, weather, the road being the pass's problem not the town's. The pattern is the tell: this is protected, not neglected.",
|
||||
"id": "fact.militia-never-investigates",
|
||||
"secrecy": 2
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"body": "Small parties on the north road \u2014 always small, always few enough not to be missed \u2014 sometimes don't arrive where they were going. Locals know it as bad luck on a bad stretch of road. They do not like discussing it.",
|
||||
"id": "rumor.travelers-go-missing",
|
||||
"secrecy": 2
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"body": "Duncarrow is a walled market town at the edge of the Tallow Reach, grown fat on the road trade between the lowland grain towns and the pass. Timber and wool go out; salt, iron, and coin come in. Ten militia hold the walls under the mayor's charter. It is, by every visible measure, a prosperous and orderly place, which is exactly the reputation its mayor has spent fifteen years building and depends on.",
|
||||
"id": "town.duncarrow",
|
||||
"related": [
|
||||
"region.the-tallow-reach",
|
||||
"place.the-white-antlers",
|
||||
"person.mayor-oswin-crell"
|
||||
],
|
||||
"type": "town"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"body": "The elven travellers of the road \u2014 no single polity, but the people whose passage through the White Antlers, and its recent absence, the town notices.",
|
||||
"id": "faction.elves",
|
||||
"related": [],
|
||||
"type": "faction"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"body": "Mayor of Duncarrow for fifteen years. Warm, unhurried, remembers names and the names of your dead. Runs a clean council and a clean set of books. Widely liked. The visible locked door of this town: pleasant to every player at every disposition, and revealing of nothing.",
|
||||
"id": "person.mayor-oswin-crell",
|
||||
"related": [
|
||||
"town.duncarrow"
|
||||
],
|
||||
"type": "person"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"body": "The Tallow Reach \u2014 the lowland march of grain towns and wool roads that Duncarrow sits at the edge of, feeding the pass trade.",
|
||||
"id": "region.the-tallow-reach",
|
||||
"related": [],
|
||||
"type": "region"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"body": "An old elven shrine a half-hour's walk north of Duncarrow, in the birch stand off the pass road. Weathered antler-carvings, a dry basin, nothing worth stealing. Elves on the road have historically stopped here. Locals know it as a curiosity and a good spot to water horses.",
|
||||
"id": "place.the-white-antlers",
|
||||
"related": [
|
||||
"town.duncarrow",
|
||||
"faction.elves"
|
||||
],
|
||||
"type": "place"
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"id": "npc.harn-blackwood",
|
||||
"knows": [
|
||||
{
|
||||
"fact_id": "rumor.travelers-go-missing",
|
||||
"gate": "warm"
|
||||
},
|
||||
{
|
||||
"fact_id": "fact.harn-daughter-left",
|
||||
"gate": "warm"
|
||||
}
|
||||
],
|
||||
"related": [
|
||||
"town.duncarrow"
|
||||
],
|
||||
"start_disposition": "neutral",
|
||||
"type": "person"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"id": "npc.mayor-oswin-crell",
|
||||
"knows": [
|
||||
{
|
||||
"fact_id": "rumor.elves-avoid-the-shrine",
|
||||
"gate": "cold"
|
||||
},
|
||||
{
|
||||
"fact_id": "secret.crell-runs-slave-trade",
|
||||
"gate": "never"
|
||||
},
|
||||
{
|
||||
"fact_id": "fact.crells-guard-acts-alone",
|
||||
"gate": "never"
|
||||
}
|
||||
],
|
||||
"related": [
|
||||
"person.mayor-oswin-crell",
|
||||
"secret.crell-runs-slave-trade"
|
||||
],
|
||||
"start_disposition": "neutral",
|
||||
"type": "person"
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"id": "npc.mera-fenn",
|
||||
"knows": [
|
||||
{
|
||||
"fact_id": "rumor.elves-avoid-the-shrine",
|
||||
"gate": "neutral"
|
||||
},
|
||||
{
|
||||
"fact_id": "rumor.travelers-go-missing",
|
||||
"gate": "warm"
|
||||
},
|
||||
{
|
||||
"fact_id": "fact.militia-never-investigates",
|
||||
"gate": "warm"
|
||||
},
|
||||
{
|
||||
"fact_id": "fact.crells-guard-acts-alone",
|
||||
"gate": "trusted"
|
||||
}
|
||||
],
|
||||
"related": [
|
||||
"town.duncarrow",
|
||||
"fact.crells-guard-acts-alone"
|
||||
],
|
||||
"start_disposition": "cold",
|
||||
"type": "person"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"id": "secret.crell-runs-slave-trade",
|
||||
"related": [
|
||||
"person.mayor-oswin-crell",
|
||||
"place.the-white-antlers",
|
||||
"faction.elves"
|
||||
],
|
||||
"type": "secret"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"id": "fact.crells-guard-acts-alone",
|
||||
"related": [
|
||||
"person.mayor-oswin-crell",
|
||||
"fact.militia-never-investigates"
|
||||
],
|
||||
"type": "fact"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"id": "rumor.elves-avoid-the-shrine",
|
||||
"related": [
|
||||
"place.the-white-antlers",
|
||||
"faction.elves"
|
||||
],
|
||||
"type": "rumor"
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"id": "fact.harn-daughter-left",
|
||||
"related": [
|
||||
"npc.harn-blackwood"
|
||||
],
|
||||
"type": "fact"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"id": "fact.militia-never-investigates",
|
||||
"related": [
|
||||
"town.duncarrow",
|
||||
"rumor.travelers-go-missing"
|
||||
],
|
||||
"type": "fact"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"id": "rumor.travelers-go-missing",
|
||||
"related": [
|
||||
"town.duncarrow",
|
||||
"place.the-white-antlers"
|
||||
],
|
||||
"type": "rumor"
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
"""End-to-end: the real promoted Duncarrow bible builds green and routes the
|
||||
Crell secret correctly (body server-only; the id ships in the gate skeleton)."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from content_build.__main__ import _load_world, check
|
||||
from content_build.emit import build_trees
|
||||
|
||||
REPO = Path(__file__).resolve().parents[3]
|
||||
LORE = REPO / "content" / "lore"
|
||||
WORLD = REPO / "content" / "world"
|
||||
SERVER = REPO / "content" / "server"
|
||||
|
||||
|
||||
def test_duncarrow_builds_and_check_is_clean():
|
||||
assert check(LORE, WORLD, SERVER) == 0
|
||||
|
||||
|
||||
def test_secret_body_is_server_only():
|
||||
client, server = build_trees(_load_world(LORE))
|
||||
assert "topics/crell-runs-slave-trade.json" in server
|
||||
# the secret's descriptive id ships in the gate skeleton (schema property)...
|
||||
npc = client["npcs/mayor-oswin-crell.json"]
|
||||
assert any(k["fact_id"] == "secret.crell-runs-slave-trade"
|
||||
and k["gate"] == "never" for k in npc["knows"])
|
||||
# ...but its BODY never reaches any client artifact
|
||||
blob = json.dumps(client)
|
||||
assert "elf-slaving" not in blob and "sells them into slavery" not in blob
|
||||
|
||||
|
||||
def test_mera_never_holds_the_secret():
|
||||
client, _ = build_trees(_load_world(LORE))
|
||||
fenn = client["npcs/mera-fenn.json"]
|
||||
facts = {k["fact_id"] for k in fenn["knows"]}
|
||||
assert "secret.crell-runs-slave-trade" not in facts
|
||||
assert "fact.crells-guard-acts-alone" in facts
|
||||
@@ -13,11 +13,11 @@ def _raw(**over) -> RawEntry:
|
||||
|
||||
|
||||
def test_namespace_and_slug():
|
||||
e = entry_from_raw(_raw(id="npc.mera-fenn", type="person",
|
||||
e = entry_from_raw(_raw(id="npc.specimen-teague", type="person",
|
||||
start_disposition="cold",
|
||||
knows=[{"fact": "rumor.x", "gate": "warm"}]))
|
||||
assert e.namespace == "npc"
|
||||
assert e.slug == "mera-fenn"
|
||||
assert e.slug == "specimen-teague"
|
||||
|
||||
|
||||
def test_category_predicates():
|
||||
|
||||
16
tools/content_build/tests/test_real_content.py
Normal file
16
tools/content_build/tests/test_real_content.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""Smoke gate: the real Margreave bible builds clean. Local mirror of the CI
|
||||
content-build workflow's `--check` step — asserts freshness/validity only,
|
||||
never specific entities (that's what made the old Duncarrow-era test brittle)."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from content_build.__main__ import check
|
||||
|
||||
REPO_ROOT = Path(__file__).parents[3]
|
||||
|
||||
|
||||
def test_real_bible_check_is_clean():
|
||||
lore = REPO_ROOT / "content" / "lore"
|
||||
world = REPO_ROOT / "content" / "world"
|
||||
server = REPO_ROOT / "content" / "server" # absent today — check() must tolerate that
|
||||
assert check(lore, world, server) == 0
|
||||
Reference in New Issue
Block a user