whiskers-chooser

The character chooser: the main screen in an edit mode. She taps the character in the side panel and sees the character with an arrow on each side (previous and next, wrapping around), under it the name of its voice with an arrow on each side (previous and next voice, wrapping), and the bottom buttons are a check (save) and a cross (throw the change away and leave). Choosing a voice speaks "Hi Ada, this voice is called Jennifer" in that voice; changing the character says "Hi Ada" in that character's saved voice. Nothing is saved until the check, and the check saves exactly the shown character and the voice shown for it.

The rules

What each thing she does means is not code in the shell. It is src/domain.rs: twenty rules on the estate's shared Rete engine (rete, in the submodule third-party/rustcrates, the same engine lmjtfy runs). A rule is a list of tests on facts (what she did, whether the chooser is open, whether anything is unsaved, whether voices are listed, and what an earlier effect already did) and what to do when they hold; order is priority. The picture of the network that runs is docs/chooser-rules.svg, drawn by rete-draw and held to the network by a test (cargo run -p whiskers-chooser --example draw writes it again).

InputRuleWhat happens
openopenthe chooser opens on the saved character and its saved voice (says nothing)
right, leftnext / previous characterthe next or previous character shows, with the voice saved for it, then it says hi in that voice
voice right, leftnext / previous voicethe next or previous listed voice shows, then it says its name in that voice
checkcheck saveswith something unsaved, the shown character and its voice are kept, then the chooser closes
checkcheck with no change closesnothing to keep: it closes
crosscross throws awaywhat shows is put back to what is saved, then the chooser closes
anything that means nothing nowclosed: ..., open already, no voices to stepa note says so; nothing changes

What it is

Chooser holds the state the rules act on, because a character and a voice are too wide to be facts: which are shown, which are saved. Chooser::input(Input) runs the rules and returns an Output: the View to draw, the ShellEffects to do in order (speak an Utterance, keep a Save, leave), and the trace. The engine (Engine::chooser_input) keeps one between inputs, rebuilds it each time the chooser opens, and keeps the choice on a Save; whiskers-ffi exports it, so the Kotlin shell only sends an input, draws the view and does the effects.

Every step leaves one JSON line per event (which rule fired, which facts it stood on, counts; names and ids only, never the child's name or a voice's name or what was said) and the chooser logs them at info level as chooser {...}.

Files

FileWhat
src/domain.rsThe vocabulary (facts, values, effects, notes) and the rules.
src/lib.rsChooser: the state and the effects the rules decide.
src/tests.rsThe laws: wrap-around both ways, a preview does not save, the check saves exactly one character and its voice, the cross changes nothing, a character shows its own saved voice, the lines said.
tests/drawing.rs, tests/common/mod.rs, examples/draw.rsThe picture, and the test that holds it to the rules.
docs/chooser-rules.svgThe network, drawn.