The interrogation
A will burns in the study at nine. A suspect lies about where he was, and every answer in the interview room is checked against what each of them can know.
I was in the library at nine.holds-other-valuecanon contradicts
I never touched the will.holds-oppositecanon contradicts
Mr Felix went into the study at nine. I saw him.holdscanon agrees
Smoke came out of the study chimney.holdscanon agrees
It was Mr Felix who burned it, mark my words.unknowncanon agrees
The interview room at midnight. Each line is checked with check.utterance against what its speaker holds; canon is reported beside it, and never decides it.
The idea
A mystery is a game of who could know what. Your dialogue system turns what a suspect says into claims. Umwelt answers, for each claim, whether that speaker holds it, holds the opposite, holds another value, or cannot know it at all, and separately whether canon agrees. A lie and a lucky guess look different.
Your game decides
- what each line of dialogue claims
- who lies, and what the lie says
- how the inspector reasons
- what a contradiction does to the story
Umwelt records and answers
- who saw what at nine
- each statement, honest or
deceptive - a verdict for every line said
- where the inspector’s notes disagree
Ashcombe House
A country house, the night the will was read. At nine o’clock Felix Ashcombe, the nephew who was left nothing, goes into the study and burns the will.
Agnes, the housemaid, sees him go in. Hob, the gardener, sees smoke rise from the study chimney. Inspector Hale arrives at ten.
Nine o’clock
witness records the scene once: the claim, a Fact for canon, and a Holding for Felix and for Agnes. A second commit records the fire and what Hob saw.
// Nine o'clock. Felix burns the will in the study. Agnes sees him go in; Hob sees the chimney smoke.
const inStudy = claim.entity('felix', 'was-in', 'study');
const inLibrary = claim.entity('felix', 'was-in', 'library');
const burned = claim.entity('felix', 'burned', 'will');
const smoke = claim.unary('study', 'smoked');
const nine = at(21);
await client.witness({
projectId, attemptId: 'nine',
event: { id: 'nine', type: 'scene', title: 'Nine o’clock', position: nine, participants: ['felix', 'agnes', 'hob'], provenance },
claims: [{ claim: inStudy, canon: true, factId: 'canon-study' }],
witnesses: [{ holder: 'felix', recordIds: ['felix-study'] }, { holder: 'agnes', recordIds: ['agnes-saw'] }],
});
await client.commit({
projectId, attemptId: 'fire',
event: { id: 'fire', type: 'scene', title: 'The fire in the study', position: nine, participants: ['felix', 'hob'], provenance },
records: [
fact(burned, { id: 'canon-burned' }),
fact(smoke, { id: 'canon-smoke' }),
knows('felix', burned, witnessed('fire'), { id: 'felix-burned' }),
knows('hob', smoke, witnessed('fire'), { id: 'hob-smoke' }),
],
});// Nine o'clock. Felix burns the will in the study. Agnes sees him go in; Hob sees the chimney smoke.
var inStudy = Claim.Entity("felix", "was-in", "study");
var inLibrary = Claim.Entity("felix", "was-in", "library");
var burned = Claim.Entity("felix", "burned", "will");
var smoke = Claim.Unary("study", "smoked");
var nine = Position.At(21);
engine.Witness(new WitnessInput
{
ProjectId = projectId,
AttemptId = "nine",
Event = new EventInput { Id = "nine", Type = "scene", Title = "Nine o’clock", Position = nine, Participants = new() { "felix", "agnes", "hob" }, Provenance = provenance },
Claims = new() { new WitnessedClaim { Claim = inStudy, Canon = true, FactId = "canon-study" } },
Witnesses = new() { new Witness { Holder = "felix", RecordIds = new() { "felix-study" } }, new Witness { Holder = "agnes", RecordIds = new() { "agnes-saw" } } },
});
var fire = Acquired.Witnessed("fire");
engine.Commit(new CommitInput
{
ProjectId = projectId,
AttemptId = "fire",
Event = new EventInput { Id = "fire", Type = "scene", Title = "The fire in the study", Position = nine, Participants = new() { "felix", "hob" }, Provenance = provenance },
Records = new()
{
Records.Fact(burned, id: "canon-burned"),
Records.Fact(smoke, id: "canon-smoke"),
Records.Knows("felix", burned, fire, id: "felix-burned"),
Records.Knows("hob", smoke, fire, id: "hob-smoke"),
},
});The statements
Each of them tells the inspector what they know. Felix tells him something else: a deceptive transmission with a distortedClaim.
// Ten and eleven o'clock: what each of them tells the inspector. Felix lies.
// The intent is the narrator's knowledge: `trace` shows it, but when Hale's own
// perspective is read, as for a prompt, it says `unknown`. He does not know yet.
await client.transmitBatch({
shared: { projectId, to: 'hale', intent: 'honest', attitude: 'believes', position: at(23) },
transmits: [
{ attemptId: 'felix-tells', from: 'felix', sourceRecordId: 'felix-study', intent: 'deceptive', distortedClaim: inLibrary,
confidence: 0.7, position: at(22), recordId: 'hale-library', text: 'I was reading in the library all evening, Inspector.' },
{ attemptId: 'agnes-tells', from: 'agnes', sourceRecordId: 'agnes-saw', confidence: 0.9, recordId: 'hale-study' },
{ attemptId: 'hob-tells', from: 'hob', sourceRecordId: 'hob-smoke', confidence: 0.95, recordId: 'hale-smoke' },
],
});// Ten and eleven o'clock: what each of them tells the inspector. Felix lies.
// The intent is the narrator's knowledge: Trace shows it, but when Hale's own
// perspective is read, as for a prompt, it says Unknown. He does not know yet.
engine.TransmitBatch(new TransmitBatchPayload
{
Shared = new TransmitShared { ProjectId = projectId, To = "hale", Intent = Intent.Honest, Attitude = Attitude.Believes, Position = Position.At(23) },
Transmits = new()
{
new TransmitBatchItem
{
AttemptId = "felix-tells", From = "felix", SourceRecordId = "felix-study", Intent = Intent.Deceptive, DistortedClaim = inLibrary,
Confidence = 0.7, Position = Position.At(22), RecordId = "hale-library", Text = "I was reading in the library all evening, Inspector.",
},
new TransmitBatchItem { AttemptId = "agnes-tells", From = "agnes", SourceRecordId = "agnes-saw", Confidence = 0.9, RecordId = "hale-study" },
new TransmitBatchItem { AttemptId = "hob-tells", From = "hob", SourceRecordId = "hob-smoke", Confidence = 0.95, RecordId = "hale-smoke" },
},
});A lie is a valid belief. Hale now believes Felix was in the library. Umwelt keeps that belief, and remembers that it came from Felix, with intent to deceive. That intent is the narrator’s knowledge: trace shows it, but Hale’s own perspective reads intent: "unknown", so a prompt built from what he holds never tells the model he was lied to.
The interview
Your dialogue system maps each line to a claim. was-in has cardinality one: one place at a time, so “the library” and “the study” compete for the same slot.
// Midnight, the interview room. Your dialogue system turns each line into claims;
// Umwelt checks every one against what the speaker can know.
const interview: [string, string, Claim][] = [
['felix', 'I was in the library at nine.', inLibrary],
['felix', 'I never touched the will.', negate(burned)],
['agnes', 'Mr Felix went into the study at nine. I saw him.', inStudy],
['hob', 'Smoke came out of the study chimney.', smoke],
['hob', 'It was Mr Felix who burned it, mark my words.', burned],
];
const transcript = [];
for (const [speaker, line, claim] of interview) {
const { items: [check] } = await client.checkUtterance({ projectId, holder: speaker, at: { tick: 24 }, claims: [claim] });
transcript.push({ speaker, line, verdict: check.verdict, canon: check.canon });
}
print('transcript', transcript);// Midnight, the interview room. Your dialogue system turns each line into claims;
// Umwelt checks every one against what the speaker can know.
var interview = new (string Speaker, string Line, Claim Claim)[]
{
("felix", "I was in the library at nine.", inLibrary),
("felix", "I never touched the will.", burned.Negated()),
("agnes", "Mr Felix went into the study at nine. I saw him.", inStudy),
("hob", "Smoke came out of the study chimney.", smoke),
("hob", "It was Mr Felix who burned it, mark my words.", burned),
};
var transcript = new List<object>();
foreach (var (speaker, line, claim) in interview)
{
var check = engine.CheckUtterance(new CheckUtteranceInput { ProjectId = projectId, Holder = speaker, At = new At { Tick = 24 }, Claims = new() { claim } }).Items[0];
transcript.Add(new { speaker, line, verdict = check.Verdict, canon = check.Canon });
}
Print("transcript", transcript);| Line | Verdict | Why |
|---|---|---|
| Felix | holds-other-value | He holds another value for the same slot: he knows he was in the study. |
| Felix | holds-opposite | He holds the opposite: he knows he burned it. |
| Agnes | holds | Agnes holds it, and canon agrees. |
| Hob | holds | Hob holds it, and canon agrees. |
| Hob | unknown | True, as it happens, but Hob never saw it. A guess. |
The inspector’s notebook
At midnight Hale’s own records disagree: Felix put him in the library, Agnes in the study. contradictions reports both conflicts, with himself and with canon.
// The inspector's notebook: what he was told contradicts itself, and canon.
print('notebook', await client.contradictions({ projectId, holder: 'hale', at: { tick: 24 } }));
const felixStory = await client.trace({ projectId, recordId: 'hale-library' });
print('library-source', felixStory.chain.map(({ record, acquisition }) => ({
holder: record.kind === 'holding' ? record.holder : null,
how: acquisition?.kind,
intent: acquisition?.kind === 'told-by' ? acquisition.intent : null,
})));
// He works it out: presence plus smoke. Your deduction rule; Umwelt records it as inferred.
await client.commit({
projectId, attemptId: 'deduction',
event: { id: 'deduction', type: 'deduction', title: 'Hale works it out', position: at(24), participants: ['hale'], provenance },
records: [
believes('hale', burned, inferred(['hale-study', 'hale-smoke'], 'was there when the fire started'), { id: 'hale-burned', confidence: 0.85 }),
],
// The transition takes the event's position and provenance too.
transitions: [{ targetRecordId: 'hale-library', status: 'retracted' }],
});
print('hale-burned', await client.holds({ projectId, holder: 'hale', claim: burned, at: { tick: 24 } }));
print('notebook-after', await client.contradictions({ projectId, holder: 'hale', at: { tick: 24 } }));
print('who-knows', await client.whoHolds({ projectId, claim: burned, at: { tick: 24 } }));
await client.close();// The inspector's notebook: what he was told contradicts itself, and canon.
Print("notebook", engine.Contradictions(new HolderAt { ProjectId = projectId, Holder = "hale", At = new At { Tick = 24 } }));
var felixStory = engine.Trace(new RecordId { ProjectId = projectId, RecordIdValue = "hale-library" });
Print("library-source", felixStory.Chain.Select(link => new
{
holder = (link.Record as Record.Holding)?.Holder,
how = link.Acquisition?.Kind,
intent = (link.Acquisition as Acquisition.ToldBy)?.Intent,
}));
// He works it out: presence plus smoke. Your deduction rule; Umwelt records it as inferred.
var midnight = Position.At(24);
engine.Commit(new CommitInput
{
ProjectId = projectId,
AttemptId = "deduction",
Event = new EventInput { Id = "deduction", Type = "deduction", Title = "Hale works it out", Position = midnight, Participants = new() { "hale" }, Provenance = provenance },
Records = new()
{
Records.Believes("hale", burned, Acquired.Inferred(new[] { "hale-study", "hale-smoke" }, "was there when the fire started"), id: "hale-burned", confidence: 0.85),
},
// The transition takes the event's position and provenance too.
Transitions = new() { new TransitionInput { TargetRecordId = "hale-library", Status = TransitionStatus.Retracted } },
});
Print("hale-burned", engine.Holds(new HoldsPayload { ProjectId = projectId, Holder = "hale", Claim = burned, At = new At { Tick = 24 } }));
Print("notebook-after", engine.Contradictions(new HolderAt { ProjectId = projectId, Holder = "hale", At = new At { Tick = 24 } }));
Print("who-knows", engine.WhoHolds(new WhoHoldsPayload { ProjectId = projectId, Claim = burned, At = new At { Tick = 24 } }));[
{
"holdingId": "hale-library",
"kind": "vs-canon",
"otherRecordId": "canon-study",
"reason": "different-value-in-functional-slot"
},
{
"holdingId": "hale-library",
"kind": "vs-self",
"otherRecordId": "hale-study",
"reason": "different-value-in-functional-slot"
}
]trace shows where the library came from, and how:
[
{ "holder": "hale", "how": "told-by", "intent": "deceptive" },
{ "holder": "felix", "how": "witnessed", "intent": null }
]Hale works it out. Your deduction rule writes an inferred Holding, from the two statements it rests on, and retracts the lie. His notebook is clean, and two people now hold that Felix burned the will: the one who did it, and the one who reasoned it out.
{
"attitude": "believes",
"confidence": 0.85,
"holds": true,
"holdsAs": "same",
"polarity": true,
"recordId": "hale-burned"
}[
{
"attitude": "knows",
"confidence": 1,
"holder": "felix",
"polarity": true,
"recordId": "felix-burned"
},
{
"attitude": "believes",
"confidence": 0.85,
"holder": "hale",
"polarity": true,
"recordId": "hale-burned"
}
]Retracted, not erased. Read Hale at eleven o’clock and he still believes the library. The retraction counts from midnight.
Try next
check.utterance on each line as the player picks it, and let the inspector react to holds-other-value.compare to set her story against Felix’s.The whole program
One file that builds the world, runs it and prints every answer on this page. The TypeScript program and the .NET one print the same answers: the script that runs them for these pages checks it.
import { at, believes, by, claim, createClient, fact, inferred, knows, negate, witnessed } from '@kallamo/umwelt';
import type { Claim } from '@kallamo/umwelt';
const client = createClient({ file: 'ashcombe.umw', create: true });
const projectId = 'ashcombe';
const provenance = by.human();
const print = (label: string, value: unknown) => console.log(`@@${label}\n${JSON.stringify(value)}`);
// The world, declared once: sending the same manifest again changes nothing.
await client.worldEnsure({
project: { id: projectId, name: 'Ashcombe House', timescale: 'hour' },
entityTypes: [
{ id: 'person', name: 'Person', kind: 'agent' },
{ id: 'thing', name: 'Thing', kind: 'other' },
],
entities: [
{ id: 'hale', typeId: 'person', name: 'Inspector Hale' },
{ id: 'felix', typeId: 'person', name: 'Felix Ashcombe' },
{ id: 'agnes', typeId: 'person', name: 'Agnes, the housemaid' },
{ id: 'hob', typeId: 'person', name: 'Hob, the gardener' },
{ id: 'study', typeId: 'thing', name: 'The study' },
{ id: 'library', typeId: 'thing', name: 'The library' },
{ id: 'will', typeId: 'thing', name: 'The will' },
],
predicates: [
{ id: 'was-in', label: 'was in', object: 'entity', cardinality: 'one' },
{ id: 'burned', label: 'burned', object: 'entity', cardinality: 'many' },
{ id: 'smoked', label: 'smoked', object: 'none', cardinality: 'many' },
],
});
// Nine o'clock. Felix burns the will in the study. Agnes sees him go in; Hob sees the chimney smoke.
const inStudy = claim.entity('felix', 'was-in', 'study');
const inLibrary = claim.entity('felix', 'was-in', 'library');
const burned = claim.entity('felix', 'burned', 'will');
const smoke = claim.unary('study', 'smoked');
const nine = at(21);
await client.witness({
projectId, attemptId: 'nine',
event: { id: 'nine', type: 'scene', title: 'Nine o’clock', position: nine, participants: ['felix', 'agnes', 'hob'], provenance },
claims: [{ claim: inStudy, canon: true, factId: 'canon-study' }],
witnesses: [{ holder: 'felix', recordIds: ['felix-study'] }, { holder: 'agnes', recordIds: ['agnes-saw'] }],
});
await client.commit({
projectId, attemptId: 'fire',
event: { id: 'fire', type: 'scene', title: 'The fire in the study', position: nine, participants: ['felix', 'hob'], provenance },
records: [
fact(burned, { id: 'canon-burned' }),
fact(smoke, { id: 'canon-smoke' }),
knows('felix', burned, witnessed('fire'), { id: 'felix-burned' }),
knows('hob', smoke, witnessed('fire'), { id: 'hob-smoke' }),
],
});
// Ten and eleven o'clock: what each of them tells the inspector. Felix lies.
// The intent is the narrator's knowledge: `trace` shows it, but when Hale's own
// perspective is read, as for a prompt, it says `unknown`. He does not know yet.
await client.transmitBatch({
shared: { projectId, to: 'hale', intent: 'honest', attitude: 'believes', position: at(23) },
transmits: [
{ attemptId: 'felix-tells', from: 'felix', sourceRecordId: 'felix-study', intent: 'deceptive', distortedClaim: inLibrary,
confidence: 0.7, position: at(22), recordId: 'hale-library', text: 'I was reading in the library all evening, Inspector.' },
{ attemptId: 'agnes-tells', from: 'agnes', sourceRecordId: 'agnes-saw', confidence: 0.9, recordId: 'hale-study' },
{ attemptId: 'hob-tells', from: 'hob', sourceRecordId: 'hob-smoke', confidence: 0.95, recordId: 'hale-smoke' },
],
});
// Midnight, the interview room. Your dialogue system turns each line into claims;
// Umwelt checks every one against what the speaker can know.
const interview: [string, string, Claim][] = [
['felix', 'I was in the library at nine.', inLibrary],
['felix', 'I never touched the will.', negate(burned)],
['agnes', 'Mr Felix went into the study at nine. I saw him.', inStudy],
['hob', 'Smoke came out of the study chimney.', smoke],
['hob', 'It was Mr Felix who burned it, mark my words.', burned],
];
const transcript = [];
for (const [speaker, line, claim] of interview) {
const { items: [check] } = await client.checkUtterance({ projectId, holder: speaker, at: { tick: 24 }, claims: [claim] });
transcript.push({ speaker, line, verdict: check.verdict, canon: check.canon });
}
print('transcript', transcript);
// The inspector's notebook: what he was told contradicts itself, and canon.
print('notebook', await client.contradictions({ projectId, holder: 'hale', at: { tick: 24 } }));
const felixStory = await client.trace({ projectId, recordId: 'hale-library' });
print('library-source', felixStory.chain.map(({ record, acquisition }) => ({
holder: record.kind === 'holding' ? record.holder : null,
how: acquisition?.kind,
intent: acquisition?.kind === 'told-by' ? acquisition.intent : null,
})));
// He works it out: presence plus smoke. Your deduction rule; Umwelt records it as inferred.
await client.commit({
projectId, attemptId: 'deduction',
event: { id: 'deduction', type: 'deduction', title: 'Hale works it out', position: at(24), participants: ['hale'], provenance },
records: [
believes('hale', burned, inferred(['hale-study', 'hale-smoke'], 'was there when the fire started'), { id: 'hale-burned', confidence: 0.85 }),
],
// The transition takes the event's position and provenance too.
transitions: [{ targetRecordId: 'hale-library', status: 'retracted' }],
});
print('hale-burned', await client.holds({ projectId, holder: 'hale', claim: burned, at: { tick: 24 } }));
print('notebook-after', await client.contradictions({ projectId, holder: 'hale', at: { tick: 24 } }));
print('who-knows', await client.whoHolds({ projectId, claim: burned, at: { tick: 24 } }));
await client.close();using System.Text.Json;
using System.Text.Json.Serialization;
using Kallamo.Umwelt;
using var engine = UmweltEngine.Create("ashcombe.umw");
const string projectId = "ashcombe";
var provenance = Provenance.Human();
var json = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.KebabCaseLower) },
};
void Print(string label, object? value) => Console.WriteLine($"@@{label}\n{JsonSerializer.Serialize(value, json)}");
// The world, declared once: sending the same manifest again changes nothing.
engine.WorldEnsure(new EnsureWorldInput
{
Project = new WorldProject { Id = projectId, Name = "Ashcombe House", Timescale = "hour" },
EntityTypes = new()
{
new WorldEntityType { Id = "person", Name = "Person", Kind = EntityKind.Agent },
new WorldEntityType { Id = "thing", Name = "Thing", Kind = EntityKind.Other },
},
Entities = new()
{
new WorldEntity { Id = "hale", TypeId = "person", Name = "Inspector Hale" },
new WorldEntity { Id = "felix", TypeId = "person", Name = "Felix Ashcombe" },
new WorldEntity { Id = "agnes", TypeId = "person", Name = "Agnes, the housemaid" },
new WorldEntity { Id = "hob", TypeId = "person", Name = "Hob, the gardener" },
new WorldEntity { Id = "study", TypeId = "thing", Name = "The study" },
new WorldEntity { Id = "library", TypeId = "thing", Name = "The library" },
new WorldEntity { Id = "will", TypeId = "thing", Name = "The will" },
},
Predicates = new()
{
new PredicateDef { Id = "was-in", Label = "was in", Object = ObjectKind.Entity, Cardinality = Cardinality.One },
new PredicateDef { Id = "burned", Label = "burned", Object = ObjectKind.Entity, Cardinality = Cardinality.Many },
new PredicateDef { Id = "smoked", Label = "smoked", Object = ObjectKind.None, Cardinality = Cardinality.Many },
},
});
// Nine o'clock. Felix burns the will in the study. Agnes sees him go in; Hob sees the chimney smoke.
var inStudy = Claim.Entity("felix", "was-in", "study");
var inLibrary = Claim.Entity("felix", "was-in", "library");
var burned = Claim.Entity("felix", "burned", "will");
var smoke = Claim.Unary("study", "smoked");
var nine = Position.At(21);
engine.Witness(new WitnessInput
{
ProjectId = projectId,
AttemptId = "nine",
Event = new EventInput { Id = "nine", Type = "scene", Title = "Nine o’clock", Position = nine, Participants = new() { "felix", "agnes", "hob" }, Provenance = provenance },
Claims = new() { new WitnessedClaim { Claim = inStudy, Canon = true, FactId = "canon-study" } },
Witnesses = new() { new Witness { Holder = "felix", RecordIds = new() { "felix-study" } }, new Witness { Holder = "agnes", RecordIds = new() { "agnes-saw" } } },
});
var fire = Acquired.Witnessed("fire");
engine.Commit(new CommitInput
{
ProjectId = projectId,
AttemptId = "fire",
Event = new EventInput { Id = "fire", Type = "scene", Title = "The fire in the study", Position = nine, Participants = new() { "felix", "hob" }, Provenance = provenance },
Records = new()
{
Records.Fact(burned, id: "canon-burned"),
Records.Fact(smoke, id: "canon-smoke"),
Records.Knows("felix", burned, fire, id: "felix-burned"),
Records.Knows("hob", smoke, fire, id: "hob-smoke"),
},
});
// Ten and eleven o'clock: what each of them tells the inspector. Felix lies.
// The intent is the narrator's knowledge: Trace shows it, but when Hale's own
// perspective is read, as for a prompt, it says Unknown. He does not know yet.
engine.TransmitBatch(new TransmitBatchPayload
{
Shared = new TransmitShared { ProjectId = projectId, To = "hale", Intent = Intent.Honest, Attitude = Attitude.Believes, Position = Position.At(23) },
Transmits = new()
{
new TransmitBatchItem
{
AttemptId = "felix-tells", From = "felix", SourceRecordId = "felix-study", Intent = Intent.Deceptive, DistortedClaim = inLibrary,
Confidence = 0.7, Position = Position.At(22), RecordId = "hale-library", Text = "I was reading in the library all evening, Inspector.",
},
new TransmitBatchItem { AttemptId = "agnes-tells", From = "agnes", SourceRecordId = "agnes-saw", Confidence = 0.9, RecordId = "hale-study" },
new TransmitBatchItem { AttemptId = "hob-tells", From = "hob", SourceRecordId = "hob-smoke", Confidence = 0.95, RecordId = "hale-smoke" },
},
});
// Midnight, the interview room. Your dialogue system turns each line into claims;
// Umwelt checks every one against what the speaker can know.
var interview = new (string Speaker, string Line, Claim Claim)[]
{
("felix", "I was in the library at nine.", inLibrary),
("felix", "I never touched the will.", burned.Negated()),
("agnes", "Mr Felix went into the study at nine. I saw him.", inStudy),
("hob", "Smoke came out of the study chimney.", smoke),
("hob", "It was Mr Felix who burned it, mark my words.", burned),
};
var transcript = new List<object>();
foreach (var (speaker, line, claim) in interview)
{
var check = engine.CheckUtterance(new CheckUtteranceInput { ProjectId = projectId, Holder = speaker, At = new At { Tick = 24 }, Claims = new() { claim } }).Items[0];
transcript.Add(new { speaker, line, verdict = check.Verdict, canon = check.Canon });
}
Print("transcript", transcript);
// The inspector's notebook: what he was told contradicts itself, and canon.
Print("notebook", engine.Contradictions(new HolderAt { ProjectId = projectId, Holder = "hale", At = new At { Tick = 24 } }));
var felixStory = engine.Trace(new RecordId { ProjectId = projectId, RecordIdValue = "hale-library" });
Print("library-source", felixStory.Chain.Select(link => new
{
holder = (link.Record as Record.Holding)?.Holder,
how = link.Acquisition?.Kind,
intent = (link.Acquisition as Acquisition.ToldBy)?.Intent,
}));
// He works it out: presence plus smoke. Your deduction rule; Umwelt records it as inferred.
var midnight = Position.At(24);
engine.Commit(new CommitInput
{
ProjectId = projectId,
AttemptId = "deduction",
Event = new EventInput { Id = "deduction", Type = "deduction", Title = "Hale works it out", Position = midnight, Participants = new() { "hale" }, Provenance = provenance },
Records = new()
{
Records.Believes("hale", burned, Acquired.Inferred(new[] { "hale-study", "hale-smoke" }, "was there when the fire started"), id: "hale-burned", confidence: 0.85),
},
// The transition takes the event's position and provenance too.
Transitions = new() { new TransitionInput { TargetRecordId = "hale-library", Status = TransitionStatus.Retracted } },
});
Print("hale-burned", engine.Holds(new HoldsPayload { ProjectId = projectId, Holder = "hale", Claim = burned, At = new At { Tick = 24 } }));
Print("notebook-after", engine.Contradictions(new HolderAt { ProjectId = projectId, Holder = "hale", At = new At { Tick = 24 } }));
Print("who-knows", engine.WhoHolds(new WhoHoldsPayload { ProjectId = projectId, Claim = burned, At = new At { Tick = 24 } }));