UmweltDocs
Recipes/The reader and the character

The reader and the character

Chapter 3 goes back fifteen years. The reader learns who the stranger is two chapters before the heroine does, and the book can ask what each of them knows.

Story order
  1. year 5Fifteen years earlier
  2. year 20Lena comes home
  3. year 21The stranger in the rows
  4. year 22Pruning
  5. year 24The letter
Reading order
  1. ch 1Lena comes home
  2. ch 2The stranger in the rows
  3. ch 3Fifteen years earlier
  4. ch 4Pruning
  5. ch 5The letter

Both rows are the same five events, read with timeline: on the main axis, then on the reading axis. The flashback is first in the story and third in the book.

The idea

A novel has two clocks: the years the story spans, and the pages the reader turns. A flashback puts them out of step, and that gap is where suspense and dramatic irony live. Give the book a reading axis, and Umwelt can tell you, at any page, what the reader knows that the heroine does not yet.

You decide

  • the order of the chapters
  • where each chapter sits in the story
  • that the reader is a holder too
  • which lines go in the book

Umwelt answers

  • what each holder knows, on either clock
  • the reader at chapter 4 against Lena in year 22
  • a line that knows something too early: future
  • the events in story order or in reading order

The book

The Glass Orchard. Lena comes home to her mother’s dying orchard and finds a stranger, Tomas, tending the trees.

Chapter 3 goes back fifteen years: her mother, Iris, hides a letter that says Tomas is Lena’s father. The reader learns it there. Lena finds the letter in chapter 5.

Two orders

One axis, reading, next to the main one. Each chapter is an event with a year as its position and a chapter as its position on the reading axis. The reader is a holder, and reading a chapter is witnessing it.

glass-orchard.ts
const client = createClient({ file: 'glass-orchard.umw', create: true });
const projectId = 'glass-orchard';
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: 'The Glass Orchard', timescale: 'year' },
  entityTypes: [
    { id: 'person', name: 'Person', kind: 'agent' },
    { id: 'thing', name: 'Thing', kind: 'other' },
  ],
  entities: [
    { id: 'lena', typeId: 'person', name: 'Lena' },
    { id: 'iris', typeId: 'person', name: 'Iris, her mother' },
    { id: 'tomas', typeId: 'person', name: 'Tomas' },
    { id: 'reader', typeId: 'person', name: 'The reader' },
    { id: 'orchard', typeId: 'thing', name: 'The orchard' },
    { id: 'letter', typeId: 'thing', name: 'The letter' },
  ],
  predicates: [
    { id: 'father-is', label: 'father is', object: 'entity', cardinality: 'one' },
    { id: 'hid', label: 'hid', object: 'entity', cardinality: 'many' },
    { id: 'is-dying', label: 'is dying', object: 'none', cardinality: 'one' },
    { id: 'tends', label: 'tends', object: 'entity', cardinality: 'many' },
  ],
  // The order of the pages, next to the order of the years.
  axes: [{ id: 'reading', label: 'Reading order' }],
});
Program.cs
using var engine = UmweltEngine.Create("glass-orchard.umw");
const string projectId = "glass-orchard";
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 = "The Glass Orchard", Timescale = "year" },
    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 = "lena", TypeId = "person", Name = "Lena" },
        new WorldEntity { Id = "iris", TypeId = "person", Name = "Iris, her mother" },
        new WorldEntity { Id = "tomas", TypeId = "person", Name = "Tomas" },
        new WorldEntity { Id = "reader", TypeId = "person", Name = "The reader" },
        new WorldEntity { Id = "orchard", TypeId = "thing", Name = "The orchard" },
        new WorldEntity { Id = "letter", TypeId = "thing", Name = "The letter" },
    },
    Predicates = new()
    {
        new PredicateDef { Id = "father-is", Label = "father is", Object = ObjectKind.Entity, Cardinality = Cardinality.One },
        new PredicateDef { Id = "hid", Label = "hid", Object = ObjectKind.Entity, Cardinality = Cardinality.Many },
        new PredicateDef { Id = "is-dying", Label = "is dying", Object = ObjectKind.None, Cardinality = Cardinality.One },
        new PredicateDef { Id = "tends", Label = "tends", Object = ObjectKind.Entity, Cardinality = Cardinality.Many },
    },
    // The order of the pages, next to the order of the years.
    Axes = new() { new AxisDef { Id = "reading", Label = "Reading order" } },
});
glass-orchard.ts
// Five chapters. Chapter 3 goes back fifteen years: the reader learns who Tomas is
// two chapters before Lena does.
const fatherIsTomas = claim.entity('lena', 'father-is', 'tomas');
const irisHid = claim.entity('iris', 'hid', 'letter');
const dying = claim.unary('orchard', 'is-dying');
const tends = claim.entity('tomas', 'tends', 'orchard');

// Chapter, year, title, who is in it, and what it records. Every record is placed
// by its chapter's event: the year on the main axis, the page on the reading axis.
const chapters: [number, number, string, string[], (event: string) => RecordInput[]][] = [
  [1, 20, 'Lena comes home', ['lena'], (e) => [
    fact(dying, { id: 'f-dying' }),
    knows('lena', dying, witnessed(e), { id: 'lena-dying' }),
    knows('reader', dying, witnessed(e), { id: 'reader-dying' }),
  ]],
  [2, 21, 'The stranger in the rows', ['lena', 'tomas'], (e) => [
    fact(tends, { id: 'f-tends' }),
    knows('lena', tends, witnessed(e), { id: 'lena-tends' }),
    knows('reader', tends, witnessed(e), { id: 'reader-tends' }),
  ]],
  [3, 5, 'Fifteen years earlier', ['iris'], (e) => [
    fact(fatherIsTomas, { id: 'f-father' }),
    fact(irisHid, { id: 'f-hid' }),
    knows('iris', fatherIsTomas, witnessed(e), { id: 'iris-father' }),
    knows('reader', fatherIsTomas, witnessed(e), { id: 'reader-father' }),
    knows('reader', irisHid, witnessed(e), { id: 'reader-hid' }),
  ]],
  [4, 22, 'Pruning', ['lena', 'tomas'], (e) => [knows('lena', tends, witnessed(e), { id: 'lena-tends-2' })]],
  [5, 24, 'The letter', ['lena'], (e) => [
    knows('lena', fatherIsTomas, witnessed(e), { id: 'lena-father' }),
    knows('lena', irisHid, witnessed(e), { id: 'lena-hid' }),
  ]],
];
for (const [chapter, year, title, participants, records] of chapters) {
  const event = `chapter-${chapter}`;
  await client.commit({
    projectId, attemptId: event,
    event: { id: event, type: 'chapter', title, position: at(year), on: { reading: chapter }, participants, provenance },
    records: records(event),
  });
}
Program.cs
// Five chapters. Chapter 3 goes back fifteen years: the reader learns who Tomas is
// two chapters before Lena does.
var fatherIsTomas = Claim.Entity("lena", "father-is", "tomas");
var irisHid = Claim.Entity("iris", "hid", "letter");
var dying = Claim.Unary("orchard", "is-dying");
var tends = Claim.Entity("tomas", "tends", "orchard");

// Chapter, year, title, who is in it, and what it records. Every record is placed
// by its chapter's event: the year on the main axis, the page on the reading axis.
var chapters = new (long Chapter, long Year, string Title, string[] Participants, Func<string, List<RecordInput>> Records)[]
{
    (1, 20, "Lena comes home", new[] { "lena" }, e => new()
    {
        Records.Fact(dying, id: "f-dying"),
        Records.Knows("lena", dying, Acquired.Witnessed(e), id: "lena-dying"),
        Records.Knows("reader", dying, Acquired.Witnessed(e), id: "reader-dying"),
    }),
    (2, 21, "The stranger in the rows", new[] { "lena", "tomas" }, e => new()
    {
        Records.Fact(tends, id: "f-tends"),
        Records.Knows("lena", tends, Acquired.Witnessed(e), id: "lena-tends"),
        Records.Knows("reader", tends, Acquired.Witnessed(e), id: "reader-tends"),
    }),
    (3, 5, "Fifteen years earlier", new[] { "iris" }, e => new()
    {
        Records.Fact(fatherIsTomas, id: "f-father"),
        Records.Fact(irisHid, id: "f-hid"),
        Records.Knows("iris", fatherIsTomas, Acquired.Witnessed(e), id: "iris-father"),
        Records.Knows("reader", fatherIsTomas, Acquired.Witnessed(e), id: "reader-father"),
        Records.Knows("reader", irisHid, Acquired.Witnessed(e), id: "reader-hid"),
    }),
    (4, 22, "Pruning", new[] { "lena", "tomas" }, e => new() { Records.Knows("lena", tends, Acquired.Witnessed(e), id: "lena-tends-2") }),
    (5, 24, "The letter", new[] { "lena" }, e => new()
    {
        Records.Knows("lena", fatherIsTomas, Acquired.Witnessed(e), id: "lena-father"),
        Records.Knows("lena", irisHid, Acquired.Witnessed(e), id: "lena-hid"),
    }),
};
foreach (var (chapter, year, title, participants, records) in chapters)
{
    var eventId = $"chapter-{chapter}";
    engine.Commit(new CommitInput
    {
        ProjectId = projectId,
        AttemptId = eventId,
        Event = new EventInput
        {
            Id = eventId, Type = "chapter", Title = title, Position = Position.At(year),
            On = new() { ["reading"] = chapter }, Participants = participants.ToList(), Provenance = provenance,
        },
        Records = records(eventId),
    });
}
glass-orchard.ts
const titles = async (axis?: string) =>
  (await client.timeline({ projectId, at: axis ? { axis } : {} })).items.map((event) => event.title);
print('story-order', await titles());
print('reading-order', await titles('reading'));
Program.cs
IEnumerable<string> Titles(string? axis = null) =>
    engine.Timeline(new TimelinePayload { ProjectId = projectId, At = new At { Axis = axis } }).Items.Select(e => e.Title);
Print("story-order", Titles());
Print("reading-order", Titles("reading"));

Dramatic irony

Chapter by chapter: the reader, asked on the reading axis at that chapter; Lena, asked in the year the chapter takes place.

glass-orchard.ts
// Chapter by chapter: does the reader know, at that page? Does Lena, in that year?
const irony = [];
for (const [chapter, year] of chapters) {
  const reader = await client.holds({ projectId, holder: 'reader', claim: fatherIsTomas, at: { axis: 'reading', tick: chapter } });
  const lena = await client.holds({ projectId, holder: 'lena', claim: fatherIsTomas, at: { tick: year } });
  irony.push({ chapter, year, reader: reader.holdsAs === 'same', lena: lena.holdsAs === 'same' });
}
print('irony', irony);

// Chapter 4, side by side: the reader on the reading axis, Lena in her own year.
const side = await client.compare({ projectId, a: 'reader', b: 'lena', at: { axis: 'reading', tick: 4 }, atB: { tick: 22 } });
print('chapter-4', {
  counts: side.counts,
  onlyReader: side.onlyA.map((entry) => entry.a?.kind === 'holding' ? entry.a.claim.predicate : null),
});
Program.cs
// Chapter by chapter: does the reader know, at that page? Does Lena, in that year?
var irony = new List<object>();
foreach (var (chapter, year, _, _, _) in chapters)
{
    var reader = engine.Holds(new HoldsPayload { ProjectId = projectId, Holder = "reader", Claim = fatherIsTomas, At = new At { Axis = "reading", Tick = chapter } });
    var lena = engine.Holds(new HoldsPayload { ProjectId = projectId, Holder = "lena", Claim = fatherIsTomas, At = new At { Tick = year } });
    irony.Add(new { chapter, year, reader = reader.HoldsAs == HoldsAs.Same, lena = lena.HoldsAs == HoldsAs.Same });
}
Print("irony", irony);

// Chapter 4, side by side: the reader on the reading axis, Lena in her own year.
var side = engine.Compare(new ComparePayload
{
    ProjectId = projectId, A = "reader", B = "lena", At = new At { Axis = "reading", Tick = 4 }, AtB = new At { Tick = 22 },
});
Print("chapter-4", new
{
    counts = side.Counts,
    onlyReader = side.OnlyA.Select(entry => (entry.A as Record.Holding)?.Claim.Predicate),
});
ChapterYearThe reader knowsLena knows
120nono
221nono
35yesno
422yesno
524yesyes

Chapters 3 and 4 are the gap. compare with atB puts the two clocks side by side: in chapter 4, the reader holds two things Lena does not.

Response
{
  "counts": { "conflicting": 0, "onlyA": 2, "onlyB": 0, "shared": 2 },
  "onlyReader": ["hid", "father-is"]
}

Off the axis, invisible. A record whose event has no position on the reading axis is visible to nobody in a read on that axis. Every chapter here has one.

A continuity check

A draft line for chapter 4 has Lena say her father is alive and close by. Checked against what she holds in year 22:

glass-orchard.tsYour code
// A draft line for chapter 4, where Lena says her father is alive and close by.
// Checked before it goes in the book.
const draft = await client.checkUtterance({ projectId, holder: 'lena', at: { tick: 22 }, claims: [fatherIsTomas] });
print('chapter-4-line', draft.items.map(({ verdict, canon }) => ({ verdict, canon })));
await client.close();
Program.csYour code
// A draft line for chapter 4, where Lena says her father is alive and close by.
// Checked before it goes in the book.
var draft = engine.CheckUtterance(new CheckUtteranceInput { ProjectId = projectId, Holder = "lena", At = new At { Tick = 22 }, Claims = new() { fatherIsTomas } });
Print("chapter-4-line", draft.Items.Select(item => new { verdict = item.Verdict, canon = item.Canon }));
Response
[{ "verdict": "future", "canon": "agrees" }]

future: she only comes to hold it later. Canon agrees, so the line is not wrong about the world. It is wrong about Lena, two chapters early.

Try next

A second readerOne who skips the flashback. Give them no Holding at chapter 3, and compare the two readers at the end.
Spoilers for the blurbUse context.admit on the reading axis to keep a summary of chapters 1 and 2 free of chapter 3.
RevisionsEvery read takes asOfVersion: ask what the reader knew in last month’s draft of the book.
A seriesPut each volume on its own axis, or in one reading order across volumes.

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.

glass-orchard.ts
import { at, by, claim, createClient, fact, knows, witnessed } from '@kallamo/umwelt';
import type { RecordInput } from '@kallamo/umwelt';
const client = createClient({ file: 'glass-orchard.umw', create: true });
const projectId = 'glass-orchard';
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: 'The Glass Orchard', timescale: 'year' },
  entityTypes: [
    { id: 'person', name: 'Person', kind: 'agent' },
    { id: 'thing', name: 'Thing', kind: 'other' },
  ],
  entities: [
    { id: 'lena', typeId: 'person', name: 'Lena' },
    { id: 'iris', typeId: 'person', name: 'Iris, her mother' },
    { id: 'tomas', typeId: 'person', name: 'Tomas' },
    { id: 'reader', typeId: 'person', name: 'The reader' },
    { id: 'orchard', typeId: 'thing', name: 'The orchard' },
    { id: 'letter', typeId: 'thing', name: 'The letter' },
  ],
  predicates: [
    { id: 'father-is', label: 'father is', object: 'entity', cardinality: 'one' },
    { id: 'hid', label: 'hid', object: 'entity', cardinality: 'many' },
    { id: 'is-dying', label: 'is dying', object: 'none', cardinality: 'one' },
    { id: 'tends', label: 'tends', object: 'entity', cardinality: 'many' },
  ],
  // The order of the pages, next to the order of the years.
  axes: [{ id: 'reading', label: 'Reading order' }],
});
// Five chapters. Chapter 3 goes back fifteen years: the reader learns who Tomas is
// two chapters before Lena does.
const fatherIsTomas = claim.entity('lena', 'father-is', 'tomas');
const irisHid = claim.entity('iris', 'hid', 'letter');
const dying = claim.unary('orchard', 'is-dying');
const tends = claim.entity('tomas', 'tends', 'orchard');

// Chapter, year, title, who is in it, and what it records. Every record is placed
// by its chapter's event: the year on the main axis, the page on the reading axis.
const chapters: [number, number, string, string[], (event: string) => RecordInput[]][] = [
  [1, 20, 'Lena comes home', ['lena'], (e) => [
    fact(dying, { id: 'f-dying' }),
    knows('lena', dying, witnessed(e), { id: 'lena-dying' }),
    knows('reader', dying, witnessed(e), { id: 'reader-dying' }),
  ]],
  [2, 21, 'The stranger in the rows', ['lena', 'tomas'], (e) => [
    fact(tends, { id: 'f-tends' }),
    knows('lena', tends, witnessed(e), { id: 'lena-tends' }),
    knows('reader', tends, witnessed(e), { id: 'reader-tends' }),
  ]],
  [3, 5, 'Fifteen years earlier', ['iris'], (e) => [
    fact(fatherIsTomas, { id: 'f-father' }),
    fact(irisHid, { id: 'f-hid' }),
    knows('iris', fatherIsTomas, witnessed(e), { id: 'iris-father' }),
    knows('reader', fatherIsTomas, witnessed(e), { id: 'reader-father' }),
    knows('reader', irisHid, witnessed(e), { id: 'reader-hid' }),
  ]],
  [4, 22, 'Pruning', ['lena', 'tomas'], (e) => [knows('lena', tends, witnessed(e), { id: 'lena-tends-2' })]],
  [5, 24, 'The letter', ['lena'], (e) => [
    knows('lena', fatherIsTomas, witnessed(e), { id: 'lena-father' }),
    knows('lena', irisHid, witnessed(e), { id: 'lena-hid' }),
  ]],
];
for (const [chapter, year, title, participants, records] of chapters) {
  const event = `chapter-${chapter}`;
  await client.commit({
    projectId, attemptId: event,
    event: { id: event, type: 'chapter', title, position: at(year), on: { reading: chapter }, participants, provenance },
    records: records(event),
  });
}
const titles = async (axis?: string) =>
  (await client.timeline({ projectId, at: axis ? { axis } : {} })).items.map((event) => event.title);
print('story-order', await titles());
print('reading-order', await titles('reading'));
// Chapter by chapter: does the reader know, at that page? Does Lena, in that year?
const irony = [];
for (const [chapter, year] of chapters) {
  const reader = await client.holds({ projectId, holder: 'reader', claim: fatherIsTomas, at: { axis: 'reading', tick: chapter } });
  const lena = await client.holds({ projectId, holder: 'lena', claim: fatherIsTomas, at: { tick: year } });
  irony.push({ chapter, year, reader: reader.holdsAs === 'same', lena: lena.holdsAs === 'same' });
}
print('irony', irony);

// Chapter 4, side by side: the reader on the reading axis, Lena in her own year.
const side = await client.compare({ projectId, a: 'reader', b: 'lena', at: { axis: 'reading', tick: 4 }, atB: { tick: 22 } });
print('chapter-4', {
  counts: side.counts,
  onlyReader: side.onlyA.map((entry) => entry.a?.kind === 'holding' ? entry.a.claim.predicate : null),
});
// A draft line for chapter 4, where Lena says her father is alive and close by.
// Checked before it goes in the book.
const draft = await client.checkUtterance({ projectId, holder: 'lena', at: { tick: 22 }, claims: [fatherIsTomas] });
print('chapter-4-line', draft.items.map(({ verdict, canon }) => ({ verdict, canon })));
await client.close();
Program.cs
using System.Text.Json;
using System.Text.Json.Serialization;
using Kallamo.Umwelt;
using var engine = UmweltEngine.Create("glass-orchard.umw");
const string projectId = "glass-orchard";
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 = "The Glass Orchard", Timescale = "year" },
    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 = "lena", TypeId = "person", Name = "Lena" },
        new WorldEntity { Id = "iris", TypeId = "person", Name = "Iris, her mother" },
        new WorldEntity { Id = "tomas", TypeId = "person", Name = "Tomas" },
        new WorldEntity { Id = "reader", TypeId = "person", Name = "The reader" },
        new WorldEntity { Id = "orchard", TypeId = "thing", Name = "The orchard" },
        new WorldEntity { Id = "letter", TypeId = "thing", Name = "The letter" },
    },
    Predicates = new()
    {
        new PredicateDef { Id = "father-is", Label = "father is", Object = ObjectKind.Entity, Cardinality = Cardinality.One },
        new PredicateDef { Id = "hid", Label = "hid", Object = ObjectKind.Entity, Cardinality = Cardinality.Many },
        new PredicateDef { Id = "is-dying", Label = "is dying", Object = ObjectKind.None, Cardinality = Cardinality.One },
        new PredicateDef { Id = "tends", Label = "tends", Object = ObjectKind.Entity, Cardinality = Cardinality.Many },
    },
    // The order of the pages, next to the order of the years.
    Axes = new() { new AxisDef { Id = "reading", Label = "Reading order" } },
});
// Five chapters. Chapter 3 goes back fifteen years: the reader learns who Tomas is
// two chapters before Lena does.
var fatherIsTomas = Claim.Entity("lena", "father-is", "tomas");
var irisHid = Claim.Entity("iris", "hid", "letter");
var dying = Claim.Unary("orchard", "is-dying");
var tends = Claim.Entity("tomas", "tends", "orchard");

// Chapter, year, title, who is in it, and what it records. Every record is placed
// by its chapter's event: the year on the main axis, the page on the reading axis.
var chapters = new (long Chapter, long Year, string Title, string[] Participants, Func<string, List<RecordInput>> Records)[]
{
    (1, 20, "Lena comes home", new[] { "lena" }, e => new()
    {
        Records.Fact(dying, id: "f-dying"),
        Records.Knows("lena", dying, Acquired.Witnessed(e), id: "lena-dying"),
        Records.Knows("reader", dying, Acquired.Witnessed(e), id: "reader-dying"),
    }),
    (2, 21, "The stranger in the rows", new[] { "lena", "tomas" }, e => new()
    {
        Records.Fact(tends, id: "f-tends"),
        Records.Knows("lena", tends, Acquired.Witnessed(e), id: "lena-tends"),
        Records.Knows("reader", tends, Acquired.Witnessed(e), id: "reader-tends"),
    }),
    (3, 5, "Fifteen years earlier", new[] { "iris" }, e => new()
    {
        Records.Fact(fatherIsTomas, id: "f-father"),
        Records.Fact(irisHid, id: "f-hid"),
        Records.Knows("iris", fatherIsTomas, Acquired.Witnessed(e), id: "iris-father"),
        Records.Knows("reader", fatherIsTomas, Acquired.Witnessed(e), id: "reader-father"),
        Records.Knows("reader", irisHid, Acquired.Witnessed(e), id: "reader-hid"),
    }),
    (4, 22, "Pruning", new[] { "lena", "tomas" }, e => new() { Records.Knows("lena", tends, Acquired.Witnessed(e), id: "lena-tends-2") }),
    (5, 24, "The letter", new[] { "lena" }, e => new()
    {
        Records.Knows("lena", fatherIsTomas, Acquired.Witnessed(e), id: "lena-father"),
        Records.Knows("lena", irisHid, Acquired.Witnessed(e), id: "lena-hid"),
    }),
};
foreach (var (chapter, year, title, participants, records) in chapters)
{
    var eventId = $"chapter-{chapter}";
    engine.Commit(new CommitInput
    {
        ProjectId = projectId,
        AttemptId = eventId,
        Event = new EventInput
        {
            Id = eventId, Type = "chapter", Title = title, Position = Position.At(year),
            On = new() { ["reading"] = chapter }, Participants = participants.ToList(), Provenance = provenance,
        },
        Records = records(eventId),
    });
}
IEnumerable<string> Titles(string? axis = null) =>
    engine.Timeline(new TimelinePayload { ProjectId = projectId, At = new At { Axis = axis } }).Items.Select(e => e.Title);
Print("story-order", Titles());
Print("reading-order", Titles("reading"));
// Chapter by chapter: does the reader know, at that page? Does Lena, in that year?
var irony = new List<object>();
foreach (var (chapter, year, _, _, _) in chapters)
{
    var reader = engine.Holds(new HoldsPayload { ProjectId = projectId, Holder = "reader", Claim = fatherIsTomas, At = new At { Axis = "reading", Tick = chapter } });
    var lena = engine.Holds(new HoldsPayload { ProjectId = projectId, Holder = "lena", Claim = fatherIsTomas, At = new At { Tick = year } });
    irony.Add(new { chapter, year, reader = reader.HoldsAs == HoldsAs.Same, lena = lena.HoldsAs == HoldsAs.Same });
}
Print("irony", irony);

// Chapter 4, side by side: the reader on the reading axis, Lena in her own year.
var side = engine.Compare(new ComparePayload
{
    ProjectId = projectId, A = "reader", B = "lena", At = new At { Axis = "reading", Tick = 4 }, AtB = new At { Tick = 22 },
});
Print("chapter-4", new
{
    counts = side.Counts,
    onlyReader = side.OnlyA.Select(entry => (entry.A as Record.Holding)?.Claim.Predicate),
});
// A draft line for chapter 4, where Lena says her father is alive and close by.
// Checked before it goes in the book.
var draft = engine.CheckUtterance(new CheckUtteranceInput { ProjectId = projectId, Holder = "lena", At = new At { Tick = 22 }, Claims = new() { fatherIsTomas } });
Print("chapter-4-line", draft.Items.Select(item => new { verdict = item.Verdict, canon = item.Canon }));

More recipes

Was this page helpful?