feat(mystery): parseEncounterNarration — phase and transition prose
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { describe, it, expect } from 'vitest'
|
import { describe, it, expect } from 'vitest'
|
||||||
import { parseRoom, parseItem, parseEnding } from './loader'
|
import { parseRoom, parseItem, parseEnding, parseEncounterNarration } from './loader'
|
||||||
|
|
||||||
const FOYER_MD = `---
|
const FOYER_MD = `---
|
||||||
id: foyer
|
id: foyer
|
||||||
@@ -246,3 +246,43 @@ whenFlags: {}
|
|||||||
expect(result.ending.narration).toBe('')
|
expect(result.ending.narration).toBe('')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const RAT_MD = `---
|
||||||
|
id: rat
|
||||||
|
startsIn: "[[cellar-stair]]"
|
||||||
|
initialPhase: lurking
|
||||||
|
---
|
||||||
|
|
||||||
|
## lurking
|
||||||
|
A heavy rat watches you from the third step. Its eyes catch the light.
|
||||||
|
|
||||||
|
## attack-resolved
|
||||||
|
You stamp. The rat squeals and is gone into the dark.
|
||||||
|
|
||||||
|
## wait-stays
|
||||||
|
The rat does not move. Neither do you.
|
||||||
|
`
|
||||||
|
|
||||||
|
describe('parseEncounterNarration', () => {
|
||||||
|
it('parses frontmatter and narration sections', () => {
|
||||||
|
const doc = parseEncounterNarration(RAT_MD, 'encounters/rat.md')
|
||||||
|
expect(doc.id).toBe('rat')
|
||||||
|
expect(doc.startsIn).toBe('cellar-stair')
|
||||||
|
expect(doc.initialPhase).toBe('lurking')
|
||||||
|
expect(doc.narrations).toEqual({
|
||||||
|
lurking: 'A heavy rat watches you from the third step. Its eyes catch the light.',
|
||||||
|
'attack-resolved': 'You stamp. The rat squeals and is gone into the dark.',
|
||||||
|
'wait-stays': 'The rat does not move. Neither do you.',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws when no sections are present', () => {
|
||||||
|
const md = `---
|
||||||
|
id: x
|
||||||
|
startsIn: room
|
||||||
|
initialPhase: p
|
||||||
|
---
|
||||||
|
`
|
||||||
|
expect(() => parseEncounterNarration(md, 'encounters/x.md')).toThrow(/no narration sections/i)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
+24
-1
@@ -1,7 +1,7 @@
|
|||||||
import matter from 'gray-matter'
|
import matter from 'gray-matter'
|
||||||
import type { Room, RoomDescriptions, Item } from './types'
|
import type { Room, RoomDescriptions, Item } from './types'
|
||||||
import type { Direction } from '../engine/types'
|
import type { Direction } from '../engine/types'
|
||||||
import { roomFrontmatterSchema, itemFrontmatterSchema, endingFrontmatterSchema } from './schema'
|
import { roomFrontmatterSchema, itemFrontmatterSchema, endingFrontmatterSchema, encounterFrontmatterSchema } from './schema'
|
||||||
|
|
||||||
const WIKILINK = /^\[\[([^\]|]+)(?:\|[^\]]*)?\]\]$/
|
const WIKILINK = /^\[\[([^\]|]+)(?:\|[^\]]*)?\]\]$/
|
||||||
|
|
||||||
@@ -131,3 +131,26 @@ export function parseEnding(raw: string, _sourcePath: string): ParsedEnding {
|
|||||||
ending: { whenFlags: fm.whenFlags, narration: parsed.content.trim() },
|
ending: { whenFlags: fm.whenFlags, narration: parsed.content.trim() },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ParsedEncounterNarration {
|
||||||
|
id: string
|
||||||
|
startsIn: string
|
||||||
|
initialPhase: string
|
||||||
|
narrations: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseEncounterNarration(raw: string, sourcePath: string): ParsedEncounterNarration {
|
||||||
|
const parsed = matter(raw)
|
||||||
|
const frontmatter = stripWikilink(parsed.data) as Record<string, unknown>
|
||||||
|
const fm = encounterFrontmatterSchema.parse(frontmatter)
|
||||||
|
const narrations = splitSections(parsed.content)
|
||||||
|
if (Object.keys(narrations).length === 0) {
|
||||||
|
throw new Error(`${sourcePath}: no narration sections found`)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: fm.id,
|
||||||
|
startsIn: fm.startsIn,
|
||||||
|
initialPhase: fm.initialPhase,
|
||||||
|
narrations,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user