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,17 +1,29 @@
- id: old_man
name: Old Man
description: A stooped old man with a grey beard.
name: Old Caretaker
description: A stooped old man with a grey beard, watching from among the shelves.
greeting: |
"Hello, traveller. If you bring me the oil lamp,
I will give you the key to the cellar."
reactions:
- onReceive: lamp
response: |
"Thank you! Here, take this key."
gives: key
consumes: lamp
"Another lost soul in this house. Get the power running and tend to what
waits upstairs — that is the only way out that's worth taking."
# First matching line wins; otherwise the greeting is used.
dialogue:
- when: [{ flag: power_on }]
- when: [{ flag: ghost_banished }]
text: |
"You got the power running! The whole house feels less...
watchful now."
"You laid the poor thing to rest. Bless you. Go now — the night is
clear and the house will hold its peace."
- when: [{ flag: power_on }, { notFlag: ghost_banished }]
text: |
"The lights are back — feel how the house has quieted? Climb the stair.
In the chapel there's a shrine: give it the locket, then the photograph,
in that order."
- when: [{ notFlag: cellar_drained }]
text: |
"Cold down there, isn't it? The cellar's flooded over the fuse. Find the
valve wheel — the conservatory, I think — and drain it."
reactions:
- onReceive: letter
response: |
"Ah... that letter. I wrote it, long ago, for whoever came after. So
someone finally read it. Keep going — you're closer than they ever got."
consumes: letter
effects:
- { setFlag: heard_tale }