Three Quarters of a Turn Is Thinking
A play turn is a look, a model's judgment and an input. Before building anything faster, the turn was measured over eight sessions of 822 to 1,175 inputs each. The frame a look returns is 24 ms old (median). The first screen change comes 122 ms after an input. The time from a look's answer to her next input is 4.7 s (median, 3.9 at the 25th percentile, 7.9 at the 90th). cwkPippa's own turn log of 166 play turns agrees: a look's round trip is 0.58 s and the model spends 4.15 s between a look's answer and its next tool call. Judgment is about three quarters of a 5.5-second cycle. Capture and transfer are small.
The conclusion follows from the numbers: nobody fights at that pace, and no faster model call fixes it. Reflexes need a layer with no model in it. That layer has to read the screen itself, every frame, and act on what it reads.
A Root Class, Not a Dark Souls Script
The obvious build was a screen analyzer for the one game she was playing. Dad ruled it out the day it gained ears, in words that are the design rule: what matters is not writing it once; think object-oriented whatever you build, make a canonical root class, inherit from it so anything can be done polymorphically, a root that detects images (video) and audio both, apply it to this game, and never overfit it to this game, or the next game or video means building it again.
So backend/sense.py knows no modality and no game:
- A
Detectortakes timestamped samples and returns timestampedEvents: the kind of reading, the name its profile gave it, the value, how sure, which reader said it, the media time it is true of, and how late it was said. - Its children pay their own polymorphic tax:
VisionDetectordecodes frames,AudioDetectorjoins waveform blocks. The root is never edited to fit one. - A
Readeris one thing read from a sample. Reader classes register by modality and kind, so a new kind is a new class, never a branch in the root.
| Kind | Reads |
|---|---|
level | how full a bar is, and whether it is empty or hidden |
count | a small number, matched to glyph pictures the profile learned |
mark / float_bar | a small bright spot, or thin bars anywhere in a region |
splash | how much of a region one colour covers (a banner) |
pan / landmark | degrees the view turned since the last frame, and where a learned picture is |
detect | figures in a frame, by a small learned model |
onset / sound | a sudden rise in a band over its own floor, or a learned sound |
A colour is never a constant in code either: it is a test the profile sets (how far one channel leads another, how near a colour it is, how bright, how dark). And consumers read events, never a detector: events go to a Bus, and a consumer listens or asks its State (the newest reading of each name, with its age). Combining readings is itself a consumer that may publish events of its own.
The Game Is Data
Everything about a game is its profile, kept as data under the game's own name with its history beside it: which readers, their numbers, the fusions, the combos and rules. It is learned from Oriel's recordings and tuned by Pippa from her own runs, every change a row with its why, never transcribed from a walkthrough. The proof that no game hides in the code is in the tests: test_sense.py and test_hand.py run two games that do not exist on the same classes. A constant that only fits the real game fails the fake ones.
The Hand Stands Under Every Stop
The fast layer that acts on the bus is the hand: conditions on fresh readings, named combos, rules with priority and cooldown, and modes that list the live rules. It is built so that speed buys no new authority:
- It sends only through the play session's input door (
Play.actwithreflex), so the course, the denied buttons, the timer, the red eye, On Air's kill switch and Dad's Stop hold for it as for any input. A closed session takes no press. - It starts
off, and only the head turns it on, with a why.offkeeps the senses reading and presses nothing. - A value is acted on only while fresh, and a reading that could not be read passes no test: no count is no drink.
- Every press is a record: the rule, the readings and their ages, the steps sent and left out, the time from frame to press, and whether the screen then showed what the combo expects. Reflexes are tallied apart from her own loop, which measures the head.
The first live trial (a coached Solo session of eight minutes, on Dad's word) read 3,034 frames: fetched in 18 ms, read in 43 ms, decided in under a millisecond. From the frame that showed health falling to the press handed to the console: 65 and 71 ms. And both healing presses were pressed and neither was shown: one landed during a stagger right after a hit, the other just before the blow that killed her. The shown field did its work, since a press that did nothing was not counted as a heal, and the rule now waits out a hit. A fast layer that records whether its action took effect can be corrected. One that only records that it fired cannot.