docs(mystery): spec for engine prereqs (verbs, disambiguation, ending UI) #1
@@ -158,7 +158,7 @@ describe('parser — verb + target', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('parser — disambiguation', () => {
|
describe('parser — disambiguation', () => {
|
||||||
it('returns disambiguation request when two candidates match', () => {
|
it('returns ambiguous when two candidates match', () => {
|
||||||
const ctx: ParserContext = {
|
const ctx: ParserContext = {
|
||||||
knownItems: ['brass-key', 'iron-key'],
|
knownItems: ['brass-key', 'iron-key'],
|
||||||
knownEncounters: [],
|
knownEncounters: [],
|
||||||
@@ -171,12 +171,11 @@ describe('parser — disambiguation', () => {
|
|||||||
awaitingDisambiguation: null,
|
awaitingDisambiguation: null,
|
||||||
}
|
}
|
||||||
const result = parse('take key', ctx)
|
const result = parse('take key', ctx)
|
||||||
expect(result.kind).toBe('unknown')
|
expect(result.kind).toBe('ambiguous')
|
||||||
if (result.kind === 'unknown') {
|
if (result.kind === 'ambiguous') {
|
||||||
// Parser flags ambiguity by returning unknown-noun; the dispatcher
|
expect(result.verb).toBe('take')
|
||||||
// turns this into a PendingDisambiguation. (Keeping parser pure: it
|
expect(result.rawNoun).toBe('key')
|
||||||
// signals; the dispatcher decides UI flow.)
|
expect(result.candidates).toEqual(['brass-key', 'iron-key'])
|
||||||
expect(result.reason).toBe('unknown-noun')
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -268,3 +267,33 @@ describe('stop-word stripping', () => {
|
|||||||
expect(cmd.kind).toBe('verb-target')
|
expect(cmd.kind).toBe('verb-target')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('ambiguous noun', () => {
|
||||||
|
const ctx: ParserContext = {
|
||||||
|
knownItems: ['iron-key', 'brass-key'],
|
||||||
|
knownEncounters: [],
|
||||||
|
visibleNouns: [
|
||||||
|
{ id: 'iron-key', aliases: ['key', 'iron key'] },
|
||||||
|
{ id: 'brass-key', aliases: ['key', 'brass key'] },
|
||||||
|
],
|
||||||
|
inventoryItemIds: [],
|
||||||
|
lastNoun: null,
|
||||||
|
awaitingDisambiguation: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
it('returns ambiguous when two aliases match the same noun phrase', () => {
|
||||||
|
const cmd = parse('take key', ctx)
|
||||||
|
expect(cmd).toEqual({
|
||||||
|
kind: 'ambiguous',
|
||||||
|
verb: 'take',
|
||||||
|
rawNoun: 'key',
|
||||||
|
candidates: ['iron-key', 'brass-key'],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('still returns verb-target when the phrase is unambiguous', () => {
|
||||||
|
const cmd = parse('take iron key', ctx)
|
||||||
|
expect(cmd.kind).toBe('verb-target')
|
||||||
|
if (cmd.kind === 'verb-target') expect(cmd.target.canonical).toBe('iron-key')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -173,11 +173,16 @@ export function parse(rawInput: string, ctx: ParserContext): ParsedCommand {
|
|||||||
return { kind: 'unknown', raw: trimmed, reason: 'unknown-noun' }
|
return { kind: 'unknown', raw: trimmed, reason: 'unknown-noun' }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multiple candidates → ambiguous. Parser signals; the dispatcher records the
|
// Multiple candidates → ambiguous. Dedupe by id; if only one distinct id
|
||||||
// PendingDisambiguation in state so the next turn's input is interpreted as
|
// remains, two aliases of the same item matched — not truly ambiguous.
|
||||||
// a disambiguation reply.
|
|
||||||
if (candidates.length > 1) {
|
if (candidates.length > 1) {
|
||||||
return { kind: 'unknown', raw: trimmed, reason: 'unknown-noun' }
|
const uniqueIds = [...new Set(candidates.map((c) => c.id))]
|
||||||
|
if (uniqueIds.length === 1) {
|
||||||
|
// Two aliases of the same item — not actually ambiguous.
|
||||||
|
const id = uniqueIds[0]!
|
||||||
|
return { kind: 'verb-target', verb, target: { canonical: id, raw: candidates[0]!.alias } }
|
||||||
|
}
|
||||||
|
return { kind: 'ambiguous', verb, rawNoun: targetRaw, candidates: uniqueIds }
|
||||||
}
|
}
|
||||||
|
|
||||||
const target = candidates[0]!
|
const target = candidates[0]!
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export type ParsedCommand =
|
|||||||
| { kind: 'verb-only'; verb: Verb | 'look' | 'inventory' | 'wait' }
|
| { kind: 'verb-only'; verb: Verb | 'look' | 'inventory' | 'wait' }
|
||||||
| { kind: 'verb-target'; verb: Verb; target: NounRef }
|
| { kind: 'verb-target'; verb: Verb; target: NounRef }
|
||||||
| { kind: 'verb-target-prep'; verb: Verb; target: NounRef; preposition: string; indirect: NounRef }
|
| { kind: 'verb-target-prep'; verb: Verb; target: NounRef; preposition: string; indirect: NounRef }
|
||||||
|
| { kind: 'ambiguous'; verb: Verb; rawNoun: string; candidates: string[] }
|
||||||
| { kind: 'go'; direction: Direction }
|
| { kind: 'go'; direction: Direction }
|
||||||
| { kind: 'meta'; verb: MetaVerb }
|
| { kind: 'meta'; verb: MetaVerb }
|
||||||
| { kind: 'disambiguation'; chosen: string }
|
| { kind: 'disambiguation'; chosen: string }
|
||||||
|
|||||||
Reference in New Issue
Block a user