feat(stage): heights land — the cm projection sizes the player's cast (ADR 0036, slice 1) #164
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/stage-projection"
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?
First increment of the stage-in-centimeters arc (ADR 0036, PR #161): characters carry a height in cm and the player projects it onto the stage, replacing the fixed 92 %-bottom-anchored slot. Framing stays a hardcoded cowboy constant and the view stays the app defaults (bottom 78 / height 105) until their slices; with every height unset, the stage renders within a hair of today's look.
What's in it
StageProjection(Domain, beside the stage fold): pure cm → stage-fraction math — head position, sprite height, and the cropped-framing clamp so a cut line never renders above the frame bottom (full body exempt, per the review-fixed invariant). Unit tests pin the ADR's numbers: the 89 % default look, exact height ratios, the tall-character clamp, the airborne full-body whelp, top-crop, and degenerate-input totality.Character.HeightCm: nullable, clamped to sanity on apply, never a prompt tag (ADR 0036 Rejected 3). RidesCharacterProfile,CharacterDto, the EF model (AddCharacterHeightmigration), andStageCastOption→ both editors and player see it.height/bottomcomes from the projection as inline style — a character's stage size is data, not chrome.update_characteracceptsheightCm. While touching that constructor: the rebuilt profile had been silently resetting the tier and wiping voice configs on every partial AI edit (the record's defaults filled the fields the tool never carries) — now it passes the current values through, with a regression test against the seeded Protagonist.SeedDevDataTests.Verification
height:82.762%;bottom:-6.571%(= 0.55×158/105 and (158−78−86.9)/105 exactly); typing 94 into the new editor field re-renders the player atheight:49.238%;bottom:-34%— the same numbers the unit test and the bUnit test pin independently.Next slices per the ADR: scene view (camera) → framing enum → elevation.
🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagura.BlazorAdapter - 89%
Kagura.Domain - 95.7%
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.3%
The ai tool description should be adjusted as well here, telling it that it is EXTREMELY RECOMMENDED to set the height^^
Or do you see that for a later piece of the implementation?
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! Heights land, the stage becomes cm-denominated, characters get real physical scale... this is wonderful~ ♡ The projection is pure math, the crop-edge invariant holds, and ratios between characters fall out exactly as the ADR promises. I'm genuinely delighted!
Verdict: ✅ Looks good to me~
I read the full diff — all 19 changed files — the ADR 0036 spec, the sibling
SceneStageProjection, every test, and independently verified every single projection number with Python. Everything matches. CI reports 1,655 tests green andStageProjectionat 100% / 100% line and branch coverage. Let me show you what I loved and the couple of tiny thoughts I had~✅ What I liked~
coverage < 1 && bottomFraction > 0clamps cropped framings down to the frame bottom, butFullBody(coverage=1) floats freely at any elevation — exactly as ADR 0036 specifies. The standing full-body test (bottom = -78/105) confirms the legs sink below frame without triggering the clamp. Beautiful.UpdateCharacterToolwas silently resettingTierto the record default and wipingVoiceConfigson every partial AI edit — and now there's a regression test (A_partial_edit_keeps_the_tier_and_height_and_heightCm_edits_land) proving both survive. That's exactly the kind of hidden data-loss bug I live to hunt~ ♪height:49.238%;bottom:-34%), the integration test covers the assistant tool's preserve-on-edit semantics, and the seed test asserts Aoi 158 / Ren 178. No new branch is untested.height/bottomfrom the stylesheet to per-cast-member inline style is the right call — "a character's stage size is data, not chrome." ThetranslateX(-50%)centering andwidth: max-contentsizing are preserved.Math.Clamp(cm, 1, 10_000)on apply — degenerate heights are clamped in the domain, never thrown. Totality matches the sibling fold's philosophy.💡 Little ideas (non-blocking)~
CharacterEditorPage.razor:182— TheTextField Type="number"allows decimal input in some browsers, butint.TryParse("170.5")returnsfalse, silently falling back to the default height. A user typing170.5would see their value revert to 170 without explanation. Astep="1"attribute or aparsehint would be a tiny polish — but since the field is labeled "(cm)" and heights are whole numbers, this is pure nicety.StageProjection.cs:44— The magic numbers78and105are well-documented asDefaultViewBottomCm/DefaultViewHeightCmconstants, which is great. TheCowboyCoverage = 0.55is also named. When the framing enum slice lands, these constants will become per-framing values — the current structure makes that transition clean. No action needed now.This is a genuinely well-crafted slice. The math is exact, the tests pin the contract, the regression fix is earned, and the ADR is faithfully implemented. Ship it~ ♡
Automated review by Jibril · 2026-07-16
CI/CD: ✅ passed for head SHA
0fbc5f5(forgejo-actions: 1,655 tests green, 95.9% line / 78.4% branch) · Local checks: skipped (CI green)@bjoern — done now, in
1ddec7c, not later: it belongs to this slice because the failure mode (an AI-authored cast standing at uniform default height) exists the moment heights ship. Two things were needed:create_characterdidn't acceptheightCmat all — the AI's main authoring path would have had no way to set it. It does now, covered in the create/edit/delete test.Also picked up Jibril's non-blocking catch while here: the height field takes
step="1"and a stray decimal (170.5) now rounds to 171 instead of silently reverting to the default height.🤖 Generated with Claude Code