feat(world): Haunted Manor content expansion (14 rooms, one world)

Replace the 5-room demo with a condensed 14-room manor on one spine
(restore power -> escape) that stacks all five mechanics, plus an optional
spirit thread feeding 3 prioritized endings (no hard-fail). Pure YAML on the
existing engines: 14 rooms across 3 levels, ~16 items, 5 combinations, the
old_man hint NPC, restore_power + lay_to_rest quests, and 3 endings.

Consolidate to one world: remove the toy "Test Manor" test fixture so the
loader tests exercise the real shipped world. Adapt WorldLoaderTest and
TutorialLoaderTest to form-invariants of the real world, and add
WorldSolvableTest, which walks the critical path (light/lock gating +
Combinations.tryUse + flags) to guarantee power_on, ghost_banished, and a
victory ending are reachable — an unsolvable world now fails the build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-02 09:06:28 +02:00
parent 88cad77c83
commit d8e1a662bc
16 changed files with 549 additions and 217 deletions

View File

@@ -1,57 +1,18 @@
- type: readable
id: letter
name: Letter
description: A crumpled piece of paper.
readText: |
"Meet me at midnight in the cellar. - A."
# Haunted Manor items. Two items — lit_torch and fuse — are intentionally left
# unplaced: they exist only as the products of combinations. valve, generator
# and shrine are scenery "fixtures": technically takeable, but written as fixed
# in place; combinations resolve them from the room just as well as the bag.
# ── Light sources ───────────────────────────────────────────────────────────
- type: switchable
id: lamp
name: Oil Lamp
description: An old oil lamp, heavy with fuel.
description: An old oil lamp, heavy with fuel. Lighting it would push back the dark.
initialState: false
light: true
onText: The lamp flares to life, casting a warm glow.
offText: You snuff out the lamp.
- type: plain
id: shovel
name: Shovel
description: A rusty shovel. Sturdy enough to dig.
- type: plain
id: key
name: Brass Key
description: A small brass key, polished from use.
- type: switchable
id: generator
name: Generator
description: A rusty generator with a heavy lever.
initialState: false
onText: |
You heave the lever. Somewhere deep in the manor, the lights flicker on.
offText: |
You pull the lever back; the manor falls dark again.
effects:
- { setFlag: power_on }
- type: switchable
id: front_door
name: Front Door
description: The heavy front door. It would let you leave the manor for good.
initialState: false
onText: |
You haul the front door open and step out into the cold night.
offText: |
You ease the door shut again.
effects:
- { setFlag: fled }
- type: plain
id: matches
name: Box of Matches
description: A small box of dry matches. They rattle when you shake it.
onText: The lamp flares to life, casting a warm, steady glow.
offText: You snuff out the lamp and the dark rushes back in.
- type: plain
id: torch
@@ -63,3 +24,101 @@
name: Lit Torch
description: A burning torch, throwing back the dark.
light: true
- type: plain
id: matches
name: Box of Matches
description: A small box of dry matches. They rattle when you shake it.
# ── Power-restoration chain ──────────────────────────────────────────────────
- type: plain
id: valve_wheel
name: Valve Wheel
description: A heavy iron wheel, sized to fit a drainage valve.
- type: plain
id: valve
name: Drainage Valve
description: |
A rusted drainage valve set into the cellar wall. Its wheel is missing —
it is part of the house, not something you can carry off.
- type: plain
id: fuse
name: Ceramic Fuse
description: A fat ceramic fuse, the kind that feeds a generator's main bus.
- type: plain
id: generator
name: Generator
description: |
A rusty generator with an empty fuse socket. It is bolted to the floor —
you will have to work on it where it stands.
# ── Spirit / relic chain ─────────────────────────────────────────────────────
- type: plain
id: locket
name: Silver Locket
description: A tarnished silver locket. Something is engraved inside the clasp.
- type: plain
id: photograph
name: Faded Photograph
description: A faded photograph of two figures standing before the manor.
- type: plain
id: shrine
name: Stone Shrine
description: |
A small stone shrine with a shallow offering niche. It is mortared into
the chapel floor — a place to leave something, not to take it.
# ── Exit ─────────────────────────────────────────────────────────────────────
- type: switchable
id: front_door
name: Front Door
description: The heavy front door. It would let you leave the manor for good.
initialState: false
onText: |
You haul the front door open and step out into the cold night air.
offText: |
You ease the door shut again.
effects:
- { setFlag: left_manor }
# ── Lore & flavour ───────────────────────────────────────────────────────────
- type: readable
id: letter
name: Letter
description: A crumpled piece of paper, addressed to no one.
readText: |
"If you are reading this, the power has failed again. The fuse is in the
flooded cellar — drain it first. And whatever you hear upstairs at night:
it only wants to be remembered. — A."
- type: readable
id: journal
name: Caretaker's Journal
description: A leather journal in a careful, anxious hand.
readText: |
"Day 9. The generator died and the cellar flooded the same week. I keep
the valve wheel in the conservatory now. Day 14. The chapel shrine takes
the locket — but it wants the photograph too, in that order, or nothing
happens at all."
- type: readable
id: portrait
name: Family Portrait
description: A faded oil portrait above the dining-room hearth.
readText: |
A stern family poses before the manor. A small brass plate reads only:
"They never did leave."
- type: plain
id: shovel
name: Shovel
description: A rusty shovel. Sturdy enough to dig, though nothing here needs digging.