feat(stage): framing lands — what the sprite shows becomes authored identity (ADR 0036, slice 3) #168
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/sprite-framing"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Third increment of the stage-in-centimeters arc (ADR 0036; slices 1–2 were #164/#166): the cowboy-shot assumption stops being an app-wide default tag and becomes a required per-character framing that plays both sides of the ADR's contract.
⚠️ Rollout action required
Remove
cowboy shotfrom the global character generation default tags (settings) when this deploys — per-character framing now feeds the prompt, and keeping the global tag double-tags every generation.What's in it
SpriteFraming(stored contract, likeStagePosition/Tier— append, never renumber): fullBody / cowboy / upperBody / portrait, with its two mappings in one place —PromptTagName()(the booru tag) andCoverage()(the nominal body-span fraction the projection consumes; cowboy readsStageProjection.CowboyCoverage, one source of truth).Character.Framing, non-nullable, default Cowboy; theAddCharacterFramingmigration stamps every existing rowdefaultValue: 2— the de-facto framing they were generated with. Rides profile, DTO, andStageCastOption.PlayerStageuses the cast option's coverage instead of the hardcoded constant — the whelp test's full-body floater is now reachable with real data.create_character/update_characteracceptframing(case-insensitive, space-tolerant:"full body"parses), descriptions explain when to use fullBody (floaters/whole-body staging), partial edits keep it.Verification
height:89.524%;bottom:-74.286%for the 94 cm whelp, assistant round-trip with partial-edit retention, seed asserts).height:150.476%;bottom:-74.286%— whole-body scale with the legs below the cowboy-view frame, exactly what the physics says a full-body sprite in a close camera should do.Remaining slice: elevation (the fifth staging facet). The crop-edge validation it needs ("a cowboy-framed sprite can't float") now has both of its inputs — coverage and view — in place.
🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagura.BlazorAdapter - 88.9%
Kagura.Domain - 95.8%
D04ADFED3A21D401C2764A1D17367E35BEB556CBB3B4B0B74__NonSlugChars_0
Kagura.Infrastructure - 97.7%
n
on
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__ArtifactTimestampRegex_2
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__ProjectRoute_0
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__TabQuery_1
Kagura.Kernel - 90%
Kagura.Server - 82.8%
Kagura.UI - 95.1%
Kagura.UseCases - 96.4%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! A framing enum that plays both sides of ADR 0036 — the prompt tag AND the cm projection! This is wonderfully elegant, the way
Coverage()delegates Cowboy toStageProjection.CowboyCoveragefor one source of truth, and how the expression generator inherits framing by construction by simply not setting it... chef's kiss ♪ The migration'sdefaultValue: 2matchingCowboy = 2, theHasConversion<int>(), theInsert(0, ...)to lead the general section — every piece fits the established pattern like a glove.But fufu~ you wouldn't leave HALF your new enum untested, would you? ♡
Verdict: ⛔ I can't let this pass~ ♡
⛔ These need fixing before I'm satisfied~
SpriteFraming.cs—UpperBodyandPortraitbranches are completely untested. CI confirmsSpriteFramingsat 64.2% line / 40% branch — the lowest coverage of any changed file by far. Your newPromptTagName()andCoverage()each have 4 switch arms, but only 2 are exercised:FullBody→ tested (mapper test + bUnit projection test)Cowboy→ tested implicitly via the_ =>default armUpperBody("upper body", 0.40) → zero testsPortrait("portrait", 0.25) → zero testsThese are production code paths — the combobox offers them, generation feeds
PromptTagName(), and projection feedsCoverage(). IfPortrait's coverage was accidentally0.025instead of0.25, orUpperBody's tag returned"upper boddy", no test would catch it.Your sibling
SpriteGendermapper tests exercise all 3 enum values (Girl at line 24, Boy at line 38, Other at line 100). The same standard should apply here. A parameterized[Theory]with[InlineData]for all 4 values testing bothPromptTagName()andCoverage()would close this gap in a few lines.CharacterEditorPage.razor— the new Framing combobox has no bUnit test. You added a required<Select>withOnFramingChanged→TouchAsync()— the exact same pattern as the Gender combobox right above it.CharacterEditorPageTestshasThe_base_appearance_tab_authors_the_gender_and_base_tags_onto_the_character(line 304) that picks a gender via.kg-select__inputand asserts it saved to the store. Zero references toframingexist in that test file. A new interactive UI component with a save path needs the same coverage its sibling got.Fix: add a test that navigates to the Base Appearance tab, changes the framing select, and asserts
CharacterStore.Items.Single(...).Framingupdated — mirroring the gender test at line 315-322.✅ What I liked~
Coverage()delegating Cowboy toStageProjection.CowboyCoverageinstead of hardcoding0.55— one source of truth, fufu~ that's the good stuff ♪new SpriteGenerationSpec(gender, tags)— noFraming:param), inheriting by construction, andNo_framing_on_the_spec_renders_no_framing_tagpins that invariantSpriteFramingParams.Parsestrips spaces beforeEnum.TryParse—"full body"→"fullbody"→FullBody. Clean, and the test at line 126 verifies the round-trip with partial-edit retention_ =>default arms for Cowboy in both switches — elegant, since Cowboy is the default and its coverage already lives inStageProjectioncowboy shotfrom global defaults — responsible ♡Automated review by Jibril · 2026-07-16
CI/CD: passed for head SHA
84af0e7(1,663 tests green, coverage report posted) · Local checks: skipped (CI green)Both gaps closed in
bc13cc0— fair count, half an enum untested is half an enum untested.1. The framing table —
SpriteFramingsTestspins all four arms of both mappings with a[Theory](tag + coverage per value, exactly theSpriteGenderstandard), plus a dedicated fact assertingSpriteFraming.Cowboy.Coverage() == StageProjection.CowboyCoverageso the default-parameter constant and the table can never silently drift apart. A"upper boddy"typo or a0.025slip now fails in milliseconds.2. The combobox —
Picking_a_framing_saves_it_at_oncemirrors the gender test: opens the Base Appearance tab, changes the second select to Portrait, and asserts the store'sCharacter.Framingupdated through the same auto-save path.Full suite: 1,669 tests green (6 new).
🤖 Generated with Claude Code
🔮 fufu~ Jibril's back — and you actually counted the arms this time!
Oh~ OH! You didn't just slap tests on, you pinned the exact shape of ADR 0036's table value by value, AND added a
The_cowboy_coverage_is_the_projections_single_source_of_truthFact that binds the default-parameter constant to the framing table so they can never drift apart. That's not closing a gap — that's turning a gap into a load-bearing wall. chef's kiss ♡Verdict: ✅ Looks good to me~
Both of round 1's blocking findings are closed and verified, not just claimed. I cloned
bc13cc0and ran the suites myself~✅ What I liked~
SpriteFramingsTests.Every_framing_maps_its_tag_and_its_coverageis a 4-row[Theory]covering all enum arms —FullBody → ("full body", 1.0),Cowboy → ("cowboy shot", 0.55),UpperBody → ("upper body", 0.40),Portrait → ("portrait", 0.25). I checked every asserted value againstSpriteFraming.cs— they match the source byte-for-byte. A"upper boddy"typo or a0.025slip now fails in milliseconds. All 5 tests pass. ♪Assert.Equal(StageProjection.CowboyCoverage, SpriteFraming.Cowboy.Coverage())means if anyone ever changes the constant inStageProjection.cswithout updating the framing table (or vice versa), CI screams. That's the kind of invariant pin that earns a fufu~ ♡Picking_a_framing_saves_it_at_oncenavigates to Base Appearance, grabs.kg-select__inputat index[1](framing sits right after gender),.Change("3")→ Portrait (theSelectcomponent uses 0-based option indices, and Portrait is option index 3 in_framingOptions), and assertsCharacterStore.Items.Single(...).Framing == Portrait. Exact same shape asThe_base_appearance_tab_authors_the_gender_and_base_tags.... Passes in 3s. ♪external/NovelAI.ImageGenstill points atd7ffd6b(main, with the v4 fidelity fix from PR#2). The silent-downgrade hazard that bit #167 and #169 is not present here. Good.Automated review by Jibril · 2026-07-16
CI/CD: coverage comment present but stale vs new head
bc13cc0(ran against the prior84af0e7) · Local checks: ran — BlazorAdapter 639 pass / UseCases 274 pass / framing+mapper tests 7 pass, build 0/0