Give Miss Ring eyebrows, four more faces, and reactions that settle - #684
Merged
Conversation
Her expression vocabulary is five cells wide — the cavity — and three of the four axes in it were already spent. The fourth was sitting there unused: the lashes. The art file has always documented that ´ on the left and ` on the right lifts both inner brows (the soft, open look) and that swapping them drops the inner ends and reads as stern; nothing ever varied them. Now `Mascot.brows(pose)` does, in four leanings — soft, furrowed, and one brow cocked each way — which buys a whole expression axis with no new glyph, no new cell, and no font-coverage risk. The error face finally gets the furrowed brows it always wanted. Derived from the pose, never carried on `Frame`. A brow field would have been the third member of the folded-field class the `wink` and `shake` comments describe: `Mascot.cavity` is the only reader, so anything it did not honour would be a Frame comparing unequal to its neighbour while painting identical cells, and `#tick` would report a repaint the diff forwards nothing for. Four new poses on top of it — a curious "oh?", a knowing half-smile, a narrow-eyed "hmm", a sulk — 11 faces to 15. One new mouth glyph, `n`: it is `u` turned over, the same letterform in the same x-height band, in the same fonts, and the frown is the one shape the existing four cannot approximate. `*` eyes were tried and dropped: the asterisk sits at cap height in most monospace faces, which is the band the lashes occupy, and that is exactly how the ˘ mouth shipped broken once already. A REACTION IS NOW AN ARC. A mood is held three to five seconds — fifteen to twenty-five beats of an identical sprite — which is long enough that she stops reading as reacting and starts reading as stuck. She hits the peak on the beat the result lands, then settles into a quieter cousin of it 1.6s later: beams then smiles, tenses then considers, recoils then stares. The peak staying at beat 0 is the half that matters — the settle is a tail on something the operator saw, never a different first impression — and it costs exactly one extra frame change per note. Unlike the idle tracks it plays on `calm` too: "calm drops the rest" is about motion she starts on her own. Idle gestures go four to seven, and one per 75s to one per 25s. The rate is sized against SLEEP_AFTER rather than taste: she dozes after 90 seconds, so at the old rate an operator who stepped away saw a single gesture per waking spell and the table might as well have had one entry in it. SEVEN AND NOT MORE, because the table is bounded by what the OPENING can show. The schedule is a pure hash of a beat counter that starts at 0 in every process, so the first firings are the same fixed sequence for every user of every build, and a spec pins that they cover the whole table. Ten firings fit in the sweep; enumerating the salt space against the real arithmetic, seven scripts leave 111 of 1024 salts that cover it, eight leave four, and nine leave none. Salt 918 opens on a clean permutation — all seven inside three minutes — and stays within ±1.7% over 25k firings. Everything is still expressed in the cavity, so `placement = bar` shows all of it. Measured after the retune: 98 frame changes in 449 beats (21.8%, ceiling 134), and winks are untouched at 45 → 43 beats per 6000 — nothing specced that a wink ever *appears* until now, only where one may not, which is how raising the gesture rate could have taken them away silently. Signed-off-by: hahwul <hahwul@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Miss Ring's expression vocabulary is five cells wide — the cavity — and three of the four axes in it were already spent. The fourth was sitting there unused: the lashes.
mascot.crhas always documented that´on the left with`on the right lifts both inner brows (the soft, open look) and that swapping them reads as stern; nothing ever varied them. NowMascot.brows(pose)does, in four leanings — soft, furrowed, and one brow cocked each way. A whole expression axis for no new glyph, no new cell and no font-coverage risk, and:errorfinally gets the furrowed brows it always wanted.Derived from the pose, never carried on
Frame: a brow field would have been the third member of the folded-field class thewinkandshakecomments describe.Faces: 11 → 15
One new mouth glyph,
n—uturned over, same letterform, same x-height band, same fonts; the frown is the one shape the existing four cannot approximate.*eyes were tried and dropped: the asterisk sits at cap height in most monospace faces, the band the lashes occupy, which is exactly how the˘mouth shipped broken once already.A reaction is an arc, not one frozen face
A mood is held 3–5s — 15 to 25 beats of an identical sprite — long enough that she reads as stuck rather than as reacting. She now hits the peak on the beat the result lands and settles into a quieter cousin of it 1.6s later:
happy → smile,alert → hmm,error → flat. The peak staying at beat 0 is the half that matters (the settle is a tail on something the operator saw, never a different first impression), and it costs one extra frame change per note. It plays oncalmtoo — "calm drops the rest" is about motion she starts on her own.Gestures: 4 → 7, and 1/75s → 1/25s
The rate is sized against
SLEEP_AFTER, not taste: she dozes after 90s, so at the old rate someone who stepped away saw a single gesture per waking spell.Seven and not more, because the table is bounded by what the OPENING can show. The schedule is a pure hash of a beat counter that starts at 0 in every process, so the first firings are the same fixed sequence for every user of every build, and a spec pins that they cover the whole table. Ten firings fit in the sweep; enumerating the salt space against the real arithmetic:
Salt 918 opens on a clean permutation — all seven inside three minutes — and stays within ±1.7% over 25k firings.
Verification
smile,wonder,poutandwryall rendered at width 1, no coverage fallback.crystal spec: 8607 examples, 8 failures — all the known environmentalGori::Sessionport-bind set.crystal tool format --check src spec bench scriptsclean.Noted deviation
just check's ameba step gains 2 findings:Mascot.mouth(14) and.eyes(15) againstMaxComplexity: 12. The tree is already at 659 findings onmain,.ameba.yml's own comment blesses inherently-branchy dispatchers, and the file'sSPELL OUT EVERY POSEdoctrine is what requires the arms — a pose missing one falls through to the idle face and renders as a gesture that never visibly happens. Happy to convert the three tables to constant lookups instead if you'd rather hold the bar.Everything is still expressed in the cavity, so
placement = barshows all of it. Prose updated in four places: theMotionsettings hint,settings/companion.cr, the settings guide, and CHANGELOG.