feat(mystery): add altered rooms and drunk sequence
This commit is contained in:
+1
-1
@@ -41,7 +41,7 @@
|
||||
- [ ] FEATURE: Add a Safe to somewhere that it makes sense (the bedroom?) We can add a safe-cracking mini-game. The safe contains a single bullet, which can be used with the revolver.
|
||||
- [ ] FEATURE: Add item Revolver, which can be used to kill the thing at the end. It comes with no bullets, so players need to defeat the safe-cracking minjgame. Place the revolver somewhere it takes the player some effort to find.
|
||||
- [ ] Feature: A faceless voice that speaks in a whisper in the smoking room, which demands whiskey and dispenses riddles if the player finds a bottle of whiskey (but hasn't drunk it yet). The riddles are randomly chosen from a list of 25 difficult riddles (source them). If the riddle is answered correctly there needs to be some reward and maybe a major plot point is revealed.
|
||||
- [ ] Add item Whiskey bottle, half full of something smoky. In the kitchen. Drinking it gets the player drunk, which causes them to unlock the drunk rooms, which are a series of rooms where they can go many directions, but somehow end up in seemingly back in the same spot. They are essentially a maze of doors and hallways, ladders and levels. There should be boundaries established though, it shouldn't be endless. After 20 or so moves the player passes out and wakes up back in the foyer, with the whiskey bottle returned to the kitchen, somehow still half full. Add a random encounter in the dark rooms, a creaking floorboard helps you find a secret door that opens with a faceless man inside who gives you major plot info. After this encounter you pass out and wake up in the lobby as before.
|
||||
- [x] Add item Whiskey bottle, half full of something smoky. In the kitchen. Drinking it gets the player drunk, which causes them to unlock the drunk rooms, which are a series of rooms where they can go many directions, but somehow end up in seemingly back in the same spot. They are essentially a maze of doors and hallways, ladders and levels. There should be boundaries established though, it shouldn't be endless. After 20 or so moves the player passes out and wakes up back in the foyer, with the whiskey bottle returned to the kitchen, somehow still half full. Add a random encounter in the dark rooms, a creaking floorboard helps you find a secret door that opens with a faceless man inside who gives you major plot info. After this encounter you pass out and wake up in the lobby as before. When the player is "drunk" the text on the terminal should appear blurry, like the user has double vision.
|
||||
- [ ] Set up BugPin as a self-hosted visual bug reporter for the site, then have incoming reports create markdown files under `src/world/bugs/` via a webhook or API bridge so bugs can be tracked in git alongside the game content. Include screenshot/annotation metadata in the markdown and decide whether these bug docs stay outside the world loader or get their own loader later.
|
||||
- [ ] BUG: It says the door closes behind you when you enter the lobby, but you can still exit S to the gate.
|
||||
- [x] FEATURE: Add a short "typed" effect to the text. Make it look like it's being typed out, if that makes sense, one character at a time. The effect should be brief.
|
||||
|
||||
@@ -240,6 +240,7 @@ export const encounters: Record<string, EncounterDef> = {
|
||||
chipCommand: 'hold dog',
|
||||
requires: { item: 'toy-dog' },
|
||||
narration: narration('child-beneath-well', 'hold-toy-dog-resolved'),
|
||||
setFlags: { woofReturned: true },
|
||||
to: 'resolved',
|
||||
},
|
||||
{
|
||||
@@ -386,4 +387,131 @@ export const encounters: Record<string, EncounterDef> = {
|
||||
onFailed: { narration: narration('basilisk', 'failed'), retreatTo: 'vault' },
|
||||
defaultWrongVerbNarration: narration('basilisk', 'wrong-verb'),
|
||||
},
|
||||
'vault-memory': {
|
||||
id: 'vault-memory',
|
||||
aliases: ['vault memory', 'memory', 'bed', 'photograph', 'photo', 'thing', 'buried thing'],
|
||||
startsIn: 'vault',
|
||||
initialPhase: 'buried',
|
||||
phases: {
|
||||
buried: {
|
||||
description: narration('vault-memory', 'buried'),
|
||||
transitions: [
|
||||
{
|
||||
verb: 'read',
|
||||
target: 'family-register',
|
||||
chipLabel: 'READ REGISTER',
|
||||
chipCommand: 'read register',
|
||||
requires: { item: 'family-register' },
|
||||
narration: narration('vault-memory', 'read-register-resolved'),
|
||||
setFlags: { nameSpoken: true },
|
||||
to: 'resolved',
|
||||
},
|
||||
{
|
||||
verb: 'take',
|
||||
target: 'vault-memory',
|
||||
chipLabel: 'TAKE PHOTO',
|
||||
chipCommand: 'take photograph',
|
||||
narration: narration('vault-memory', 'take-photograph-resolved'),
|
||||
setFlags: { tookPhotograph: true },
|
||||
to: 'resolved',
|
||||
},
|
||||
{
|
||||
verb: 'attack',
|
||||
target: 'vault-memory',
|
||||
chipLabel: 'ATTACK BED',
|
||||
chipCommand: 'attack bed',
|
||||
narration: narration('vault-memory', 'attack-bed-resolved'),
|
||||
setFlags: { disturbedVault: true },
|
||||
to: 'resolved',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
defaultWrongVerbNarration: narration('vault-memory', 'wrong-verb'),
|
||||
},
|
||||
'creaking-floorboard': {
|
||||
id: 'creaking-floorboard',
|
||||
aliases: ['creaking floorboard', 'floorboard', 'board', 'creak', 'secret door', 'faceless man', 'man', 'voice'],
|
||||
startsIn: 'drunk-landing',
|
||||
initialPhase: 'creaking',
|
||||
phases: {
|
||||
creaking: {
|
||||
description: narration('creaking-floorboard', 'creaking'),
|
||||
transitions: [
|
||||
{
|
||||
verb: 'listen',
|
||||
chipLabel: 'LISTEN',
|
||||
chipCommand: 'listen',
|
||||
narration: narration('creaking-floorboard', 'listen-resolved'),
|
||||
setFlags: { drunkSecretFound: true, facelessManMet: true, houseDebtNamed: true },
|
||||
to: 'resolved',
|
||||
},
|
||||
{
|
||||
verb: 'open',
|
||||
target: 'creaking-floorboard',
|
||||
chipLabel: 'OPEN BOARD',
|
||||
chipCommand: 'open floorboard',
|
||||
narration: narration('creaking-floorboard', 'listen-resolved'),
|
||||
setFlags: { drunkSecretFound: true, facelessManMet: true, houseDebtNamed: true },
|
||||
to: 'resolved',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
defaultWrongVerbNarration: narration('creaking-floorboard', 'wrong-verb'),
|
||||
},
|
||||
'distant-steps': {
|
||||
id: 'distant-steps',
|
||||
aliases: ['distant steps', 'steps', 'footsteps', 'hallway'],
|
||||
startsIn: 'wrong-hallway',
|
||||
initialPhase: 'approaching',
|
||||
phases: {
|
||||
approaching: {
|
||||
description: narration('distant-steps', 'approaching'),
|
||||
transitions: [
|
||||
{
|
||||
verb: 'wait',
|
||||
chipLabel: 'WAIT',
|
||||
narration: narration('distant-steps', 'wait-resolved'),
|
||||
to: 'resolved',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
onResolved: { setFlags: { distantStepsPassed: true } },
|
||||
onFailed: { narration: narration('distant-steps', 'failed'), retreatTo: 'parlor' },
|
||||
defaultWrongVerbNarration: narration('distant-steps', 'wrong-verb'),
|
||||
},
|
||||
'rainwater-basin': {
|
||||
id: 'rainwater-basin',
|
||||
aliases: ['rainwater basin', 'basin', 'water', 'rainwater', 'reflection'],
|
||||
startsIn: 'rain-room',
|
||||
initialPhase: 'reflecting',
|
||||
phases: {
|
||||
reflecting: {
|
||||
description: narration('rainwater-basin', 'reflecting'),
|
||||
transitions: [
|
||||
{
|
||||
verb: 'look',
|
||||
target: 'rainwater-basin',
|
||||
chipLabel: 'LOOK BASIN',
|
||||
chipCommand: 'look basin',
|
||||
narration: narration('rainwater-basin', 'look-resolved'),
|
||||
to: 'resolved',
|
||||
},
|
||||
{
|
||||
verb: 'examine',
|
||||
target: 'rainwater-basin',
|
||||
chipLabel: 'EXAMINE BASIN',
|
||||
chipCommand: 'examine basin',
|
||||
narration: narration('rainwater-basin', 'look-resolved'),
|
||||
to: 'resolved',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
onResolved: { setFlags: { rainRoomEntered: true, houseAcceptedYou: true } },
|
||||
onFailed: { narration: narration('rainwater-basin', 'failed'), retreatTo: 'wrong-hallway' },
|
||||
defaultWrongVerbNarration: narration('rainwater-basin', 'wrong-verb'),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: creaking-floorboard
|
||||
startsIn: "[[drunk-landing]]"
|
||||
initialPhase: creaking
|
||||
---
|
||||
|
||||
## creaking
|
||||
One floorboard creaks after you have stopped moving.
|
||||
|
||||
It creaks again, softer, from under the wrong foot.
|
||||
|
||||
## listen-resolved
|
||||
You listen.
|
||||
|
||||
The board lifts by itself. Beneath it is a narrow door, and behind the door a man without a face sits with his hands folded.
|
||||
|
||||
"Halfstreet keeps what is owed," he whispers. "It does not know the difference between a debt and a child."
|
||||
|
||||
## wrong-verb
|
||||
The floorboard waits until you breathe, then creaks beneath that too.
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
id: distant-steps
|
||||
startsIn: "[[wrong-hallway]]"
|
||||
initialPhase: approaching
|
||||
---
|
||||
|
||||
## approaching
|
||||
The footsteps come closer without growing louder.
|
||||
|
||||
They stop whenever you move.
|
||||
|
||||
## wait-resolved
|
||||
You stand still.
|
||||
|
||||
The steps pass through you with the cold, careful pressure of someone carrying a tray through a dark room. When they are gone, the hallway is shorter by one door.
|
||||
|
||||
## wrong-verb
|
||||
The hallway lengthens. The footsteps begin again from farther away.
|
||||
|
||||
## failed
|
||||
You turn back too quickly and find the parlor waiting with all its chairs facing you.
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
id: rainwater-basin
|
||||
startsIn: "[[rain-room]]"
|
||||
initialPhase: reflecting
|
||||
---
|
||||
|
||||
## reflecting
|
||||
The basin shows no ceiling. It shows a hallway instead, and in that hallway a lamp going out.
|
||||
|
||||
## look-resolved
|
||||
You look into the rainwater.
|
||||
|
||||
Rooms gather there one beneath another: nursery, chapel, vault, gate. For a moment they fit together correctly. Then your reflection enters them from the wrong side, and every door in the image opens inward.
|
||||
|
||||
When you look up, the room has learned you.
|
||||
|
||||
## wrong-verb
|
||||
Rain touches your hands, though you are not beneath it.
|
||||
|
||||
## failed
|
||||
The rain rises without filling the basin. You step back into the wrong hallway before it reaches your mouth.
|
||||
@@ -3,7 +3,6 @@ id: rat
|
||||
startsIn: "[[cellar-stair]]"
|
||||
initialPhase: lurking
|
||||
---
|
||||
![[Pasted image 20260509213136.png]]
|
||||
## lurking
|
||||
A heavy rat watches you from the third step. Its eyes catch the light.
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
id: vault-memory
|
||||
startsIn: "[[vault]]"
|
||||
initialPhase: buried
|
||||
---
|
||||
|
||||
## buried
|
||||
The bed waits in the center of the vault. The photograph lies face down on the coverlet.
|
||||
|
||||
The family register feels heavier here, as if all its missing pages have returned at once.
|
||||
|
||||
## read-register-resolved
|
||||
You read the name from the register.
|
||||
|
||||
The letters are difficult only until you say them. Then they seem to have been waiting in your mouth since the gate.
|
||||
|
||||
## take-photograph-resolved
|
||||
You take the photograph.
|
||||
|
||||
It is dry and light and terribly easy to lift.
|
||||
|
||||
## attack-bed-resolved
|
||||
You strike the little bed.
|
||||
|
||||
The sound is small. The house hears it anyway.
|
||||
|
||||
## wrong-verb
|
||||
The photograph remains face down. The bedclothes do not stir.
|
||||
@@ -1,6 +1,13 @@
|
||||
---
|
||||
id: bad
|
||||
whenFlags:
|
||||
_never: true
|
||||
tookPhotograph: true
|
||||
---
|
||||
|
||||
You take the photograph.
|
||||
|
||||
The child in it is you.
|
||||
|
||||
Behind you, on the stairs, someone has come up to meet you.
|
||||
|
||||
You do not go down again.
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
id: mercy
|
||||
whenFlags:
|
||||
nameSpoken: true
|
||||
woofReturned: true
|
||||
basiliskSpared: true
|
||||
burialRingPlaced: true
|
||||
familyResemblanceSeen: true
|
||||
---
|
||||
|
||||
You remain in the vault until the lamp goes out.
|
||||
|
||||
The house is quiet.
|
||||
|
||||
Somewhere above, floorboards settle one final time.
|
||||
|
||||
By morning, Halfstreet contains one fewer restless thing.
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
id: replacement
|
||||
whenFlags:
|
||||
houseAcceptedYou: true
|
||||
rainRoomEntered: true
|
||||
hallwayShifted: true
|
||||
---
|
||||
|
||||
You open the front door.
|
||||
|
||||
Someone is standing at the gate outside.
|
||||
|
||||
They look tired. Rain-darkened. Lost.
|
||||
|
||||
After a moment, you understand they have arrived for you.
|
||||
@@ -1,7 +1,18 @@
|
||||
---
|
||||
id: true
|
||||
whenFlags:
|
||||
ratGone: true
|
||||
nameSpoken: true
|
||||
basiliskSpared: true
|
||||
burialRingPlaced: true
|
||||
familyResemblanceSeen: true
|
||||
---
|
||||
|
||||
You stand at the top of the stair. The thing below has settled. The door behind you opens, and outside, finally, is morning.
|
||||
You stand in the vault. What is buried at Halfstreet is buried because it was loved, and grieved, and finally let go.
|
||||
|
||||
You set the lamp beside it.
|
||||
|
||||
You speak the name aloud.
|
||||
|
||||
The house settles around you like a long exhalation.
|
||||
|
||||
Outside, the road exists again.
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
---
|
||||
id: wrong
|
||||
whenFlags:
|
||||
_never: true
|
||||
disturbedVault: true
|
||||
---
|
||||
|
||||
You disturb what should have remained untouched.
|
||||
|
||||
The house rearranges itself around your leaving.
|
||||
|
||||
Corridors repeat.
|
||||
|
||||
Doors return you to themselves.
|
||||
|
||||
The hallway grows longer.
|
||||
|
||||
+3
-1
@@ -50,6 +50,8 @@ const endings: World['endings'] = {
|
||||
true: { whenFlags: {}, narration: '' },
|
||||
wrong: { whenFlags: {}, narration: '' },
|
||||
bad: { whenFlags: {}, narration: '' },
|
||||
replacement: { whenFlags: {}, narration: '' },
|
||||
mercy: { whenFlags: {}, narration: '' },
|
||||
}
|
||||
const seenEndings = new Set<string>()
|
||||
for (const [path, raw] of Object.entries(endingFiles)) {
|
||||
@@ -57,7 +59,7 @@ for (const [path, raw] of Object.entries(endingFiles)) {
|
||||
endings[id] = ending
|
||||
seenEndings.add(id)
|
||||
}
|
||||
const requiredEndings = ['true', 'wrong', 'bad'] as const
|
||||
const requiredEndings = ['true', 'wrong', 'bad', 'replacement', 'mercy'] as const
|
||||
for (const id of requiredEndings) {
|
||||
if (!seenEndings.has(id)) {
|
||||
throw new Error(`endings/${id}.md is missing — every ending id must have a markdown file.`)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
id: altar-stone
|
||||
names: ["altar", "altar stone", "cracked altar", "altar-stone"]
|
||||
short: "a cracked altar stone"
|
||||
takeable: false
|
||||
initialState: {}
|
||||
---
|
||||
|
||||
The altar has split across the center. The crack is narrow enough to hide a thread and deep enough to swallow light.
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
id: portrait-frame
|
||||
names: ["frame", "portrait frame", "shattered frame", "portrait-frame"]
|
||||
short: "a shattered portrait frame"
|
||||
takeable: false
|
||||
initialState: {}
|
||||
---
|
||||
|
||||
The backing has warped away from the frame. Someone wrote beneath it, then scratched the writing into harmless lines.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
id: rainwater-basin
|
||||
names: ["basin", "rainwater basin", "rainwater", "water", "rainwater-basin"]
|
||||
short: "a rainwater basin"
|
||||
takeable: false
|
||||
initialState: {}
|
||||
---
|
||||
|
||||
The basin is cut directly into the floor. Rain strikes its surface without making rings.
|
||||
|
||||
If you lean near it, the water shows rooms you have already left.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
id: rusted-key
|
||||
names: ["key", "rusted key", "rusted-key"]
|
||||
short: "a rusted key"
|
||||
takeable: true
|
||||
initialState: {}
|
||||
---
|
||||
|
||||
The key is furred with rust except where fingers have worried the bow clean. Its teeth are thin, almost eaten through.
|
||||
|
||||
It smells faintly of rain.
|
||||
+1
-1
@@ -179,7 +179,7 @@ export function parseItem(raw: string, sourcePath: string): Item {
|
||||
}
|
||||
|
||||
export interface ParsedEnding {
|
||||
id: 'true' | 'wrong' | 'bad'
|
||||
id: 'true' | 'wrong' | 'bad' | 'replacement' | 'mercy'
|
||||
ending: { whenFlags: Record<string, string | boolean | number | string[]>; narration: string }
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
id: bedroom
|
||||
title: "[ Bedroom ]"
|
||||
exitN: null
|
||||
exitS: null
|
||||
exitS: "[[returned-nursery]]"
|
||||
exitSRequires: woofReturned
|
||||
exitSLockedText: "The south wall holds only wallpaper, faded to the color of old linen."
|
||||
exitE: "[[nursery]]"
|
||||
exitW: null
|
||||
exitU: "[[attic]]"
|
||||
|
||||
@@ -9,6 +9,7 @@ exitU: null
|
||||
exitD: null
|
||||
items:
|
||||
- "[[family-register]]"
|
||||
- "[[portrait-frame]]"
|
||||
encounter: "[[portrait-woman]]"
|
||||
---
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ exitE: null
|
||||
exitW: null
|
||||
exitU: null
|
||||
exitD: null
|
||||
items: ["silver-vial"]
|
||||
items: ["silver-vial", "altar-stone"]
|
||||
encounter: "[[basilisk]]"
|
||||
safe: false
|
||||
---
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
id: drunk-cellar
|
||||
title: "[ Cellar ]"
|
||||
exitN: "[[drunk-stairs]]"
|
||||
exitS: "[[drunk-hall]]"
|
||||
exitE: "[[drunk-door-loop]]"
|
||||
exitW: null
|
||||
exitU: "[[drunk-kitchen]]"
|
||||
exitD: "[[drunk-cellar]]"
|
||||
items: []
|
||||
encounter: null
|
||||
---
|
||||
|
||||
## first-visit
|
||||
The cellar smells of earth, bottles, and hair singed by a candle.
|
||||
|
||||
The ceiling is too low until you stoop, then too high. Shelves line the walls with jars that hold only dark.
|
||||
|
||||
## revisit
|
||||
The cellar keeps its dark in jars.
|
||||
|
||||
## examined
|
||||
The floor slopes toward a drain that is not at the lowest point. The jars have labels, but the ink runs away from your eyes.
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
id: drunk-door-loop
|
||||
title: "[ Door ]"
|
||||
exitN: "[[drunk-hall]]"
|
||||
exitS: "[[drunk-kitchen]]"
|
||||
exitE: "[[drunk-stairs]]"
|
||||
exitW: "[[drunk-landing]]"
|
||||
exitU: null
|
||||
exitD: null
|
||||
items: []
|
||||
encounter: null
|
||||
---
|
||||
|
||||
## first-visit
|
||||
A door stands alone in a room too narrow to contain it.
|
||||
|
||||
It is open. It is closed. Both facts are true enough to make your stomach turn.
|
||||
|
||||
## revisit
|
||||
The door returns you to itself by another route.
|
||||
|
||||
## examined
|
||||
The hinges are on both sides. The keyhole shows a dim hall, then the back of your own head, then nothing.
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
id: drunk-hall
|
||||
title: "[ Hallway ]"
|
||||
exitN: "[[drunk-door-loop]]"
|
||||
exitS: "[[drunk-stairs]]"
|
||||
exitE: "[[drunk-kitchen]]"
|
||||
exitW: "[[drunk-hall]]"
|
||||
exitU: "[[drunk-landing]]"
|
||||
exitD: "[[drunk-cellar]]"
|
||||
items: []
|
||||
encounter: null
|
||||
---
|
||||
|
||||
## first-visit
|
||||
The hallway receives you sideways.
|
||||
|
||||
There are too many doors, and each one has the familiar brass knob from somewhere else in the house. The carpet rises and falls as if the boards beneath it are breathing through cloth.
|
||||
|
||||
## revisit
|
||||
The hallway is still here, or here again.
|
||||
|
||||
## examined
|
||||
The walls lean without falling. North is ahead until you look at it. South is behind you unless it is upstairs. Every direction seems possible in the way a bad idea seems possible.
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
id: drunk-kitchen
|
||||
title: "[ Kitchen ]"
|
||||
exitN: "[[drunk-landing]]"
|
||||
exitS: "[[drunk-door-loop]]"
|
||||
exitE: "[[drunk-hall]]"
|
||||
exitW: "[[drunk-cellar]]"
|
||||
exitU: null
|
||||
exitD: "[[drunk-stairs]]"
|
||||
items: []
|
||||
encounter: null
|
||||
---
|
||||
|
||||
## first-visit
|
||||
The kitchen has no stove. It has three sinks, all running.
|
||||
|
||||
Steam gathers near the ceiling and lowers itself in slow sheets. Something knocks once from inside a cupboard, politely.
|
||||
|
||||
## revisit
|
||||
The kitchen pours water into itself.
|
||||
|
||||
## examined
|
||||
Pots hang where windows should be. The counter is wet. A corkscrew turns in a small circle by itself and leaves no mark on the wood.
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
id: drunk-landing
|
||||
title: "[ Landing ]"
|
||||
exitN: "[[drunk-cellar]]"
|
||||
exitS: "[[drunk-hall]]"
|
||||
exitE: "[[drunk-kitchen]]"
|
||||
exitW: "[[drunk-stairs]]"
|
||||
exitU: "[[drunk-door-loop]]"
|
||||
exitD: "[[drunk-hall]]"
|
||||
items: []
|
||||
encounter: "[[creaking-floorboard]]"
|
||||
---
|
||||
|
||||
## first-visit
|
||||
The landing hangs in the dark without a staircase touching it.
|
||||
|
||||
A floorboard complains under your weight, then answers from several feet away.
|
||||
|
||||
## revisit
|
||||
The landing waits for the sound of your next step.
|
||||
|
||||
## examined
|
||||
There is a narrow seam between two boards. Cold air moves through it in small spoken breaths.
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
id: drunk-stairs
|
||||
title: "[ Stairs ]"
|
||||
exitN: "[[drunk-hall]]"
|
||||
exitS: "[[drunk-cellar]]"
|
||||
exitE: null
|
||||
exitW: "[[drunk-door-loop]]"
|
||||
exitU: "[[drunk-landing]]"
|
||||
exitD: "[[drunk-hall]]"
|
||||
items: []
|
||||
encounter: null
|
||||
---
|
||||
|
||||
## first-visit
|
||||
The stairs climb and descend with the same steps.
|
||||
|
||||
The rail is cold on one side and warm on the other. Halfway along, a landing passes you without stopping.
|
||||
|
||||
## revisit
|
||||
The stairs remember your feet badly.
|
||||
|
||||
## examined
|
||||
Each tread is worn in the center. Some are worn on the underside too. The banister has been gripped hard enough to smooth the varnish away.
|
||||
@@ -2,7 +2,9 @@
|
||||
id: parlor
|
||||
title: "[ Parlor ]"
|
||||
exitN: "[[study]]"
|
||||
exitS: null
|
||||
exitS: "[[wrong-hallway]]"
|
||||
exitSRequires: hallwayShifted
|
||||
exitSLockedText: "The chairs face that wall with great expectation, but the room has not yet made a door there."
|
||||
exitE: null
|
||||
exitW: null
|
||||
exitU: "[[stair-up]]"
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
id: rain-room
|
||||
title: "[ Rain Room ]"
|
||||
exitN: null
|
||||
exitS: "[[wrong-hallway]]"
|
||||
exitE: null
|
||||
exitW: null
|
||||
exitU: null
|
||||
exitD: null
|
||||
items:
|
||||
- "[[rusted-key]]"
|
||||
- "[[rainwater-basin]]"
|
||||
encounter: "[[rainwater-basin]]"
|
||||
---
|
||||
|
||||
## first-visit
|
||||
Rain falls steadily inside the room and nowhere else.
|
||||
|
||||
It drops from a ceiling without clouds and gathers in a basin set into the floor. The walls are papered like a bedroom, tiled like a kitchen, then papered again.
|
||||
|
||||
Something small and iron glints near the basin's rim.
|
||||
|
||||
## revisit
|
||||
The rain room keeps raining.
|
||||
|
||||
## examined
|
||||
Water threads down the walls without staining them. The basin catches every drop and never fills. The south door opens back into the hallway that should not be here.
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
id: returned-nursery
|
||||
title: "[ Nursery ]"
|
||||
exitN: null
|
||||
exitS: null
|
||||
exitE: null
|
||||
exitW: "[[bedroom]]"
|
||||
exitU: null
|
||||
exitD: null
|
||||
items: []
|
||||
encounter: null
|
||||
safe: true
|
||||
---
|
||||
|
||||
## first-visit
|
||||
The toys are no longer arranged.
|
||||
|
||||
They lie where a child might have dropped them: horse on its side, blocks scattered under the bed, a wooden soldier face down near the door.
|
||||
|
||||
The room is less careful now. Less awake.
|
||||
|
||||
## revisit
|
||||
The nursery remains untidied.
|
||||
|
||||
## examined
|
||||
The circle on the floor has opened. Dust fills the places where the toys once sat, pale as old chalk. On the shelf, the bolted music box is closed, and for once it does not seem to be listening.
|
||||
@@ -8,7 +8,7 @@ exitW: "[[chamber]]"
|
||||
exitU: null
|
||||
exitD: null
|
||||
items: []
|
||||
encounter: null
|
||||
encounter: "[[vault-memory]]"
|
||||
safe: true
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
id: wrong-hallway
|
||||
title: "[ Hallway ]"
|
||||
exitN: "[[rain-room]]"
|
||||
exitNRequires: distantStepsPassed
|
||||
exitNLockedText: "The hallway lengthens ahead of you. Something is still walking there."
|
||||
exitS: "[[parlor]]"
|
||||
exitE: null
|
||||
exitW: null
|
||||
exitU: null
|
||||
exitD: null
|
||||
items: []
|
||||
encounter: "[[distant-steps]]"
|
||||
---
|
||||
|
||||
## first-visit
|
||||
The hallway is longer now.
|
||||
|
||||
The wallpaper repeats in small mistakes: vine, gate, hand, vine, gate, hand. Doors stand where no doors stood before, but each knob turns into the same cold brass.
|
||||
|
||||
Footsteps approach from the far end.
|
||||
|
||||
## revisit
|
||||
The wrong hallway has kept its length for you.
|
||||
|
||||
## examined
|
||||
The boards bend slightly under your weight, though the footsteps ahead do not touch them. A rain-smell waits north, thin and steady.
|
||||
@@ -68,7 +68,7 @@ describe('endingFrontmatterSchema', () => {
|
||||
})
|
||||
|
||||
it('rejects unknown ending id', () => {
|
||||
const data = { id: 'mercy', whenFlags: {} }
|
||||
const data = { id: 'secret', whenFlags: {} }
|
||||
expect(() => endingFrontmatterSchema.parse(data)).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ export const itemFrontmatterSchema = z.object({
|
||||
export type ItemFrontmatter = z.infer<typeof itemFrontmatterSchema>
|
||||
|
||||
export const endingFrontmatterSchema = z.object({
|
||||
id: z.enum(['true', 'wrong', 'bad']),
|
||||
id: z.enum(['true', 'wrong', 'bad', 'replacement', 'mercy']),
|
||||
whenFlags: stateRecordSchema.default({}),
|
||||
})
|
||||
|
||||
|
||||
@@ -80,6 +80,8 @@ export interface EncounterTransition {
|
||||
narration: string
|
||||
/** Resolve cost for the player on this transition (0–2). */
|
||||
resolveCost?: 0 | 1 | 2
|
||||
/** Optional transition-specific story flags. */
|
||||
setFlags?: Record<string, string | boolean | number>
|
||||
}
|
||||
|
||||
export interface EncounterDef {
|
||||
@@ -108,5 +110,7 @@ export interface World {
|
||||
true: { whenFlags: Record<string, string | boolean | number | string[]>; narration: string }
|
||||
wrong: { whenFlags: Record<string, string | boolean | number | string[]>; narration: string }
|
||||
bad: { whenFlags: Record<string, string | boolean | number | string[]>; narration: string }
|
||||
replacement?: { whenFlags: Record<string, string | boolean | number | string[]>; narration: string }
|
||||
mercy?: { whenFlags: Record<string, string | boolean | number | string[]>; narration: string }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user