feat(stage): elevation lands — the fifth facet closes the arc (ADR 0036, slice 4) #170
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/elevation"
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?
Final increment of the stage-in-centimeters arc (ADR 0036; slices 1–3 were #164/#166/#168): characters can leave the floor. With this, every parameter of the model is authored data — height, view, framing, elevation — and the dragon whelp can finally hover.
What's in it
StageCharacterChange.ElevationCm— additive on the stored JSON (pre-elevation rows read as null, meaning unchanged), resolved by the fold with the established facet semantics: enter → standing (0), modify null → unchanged, negatives clamp to the floor. The fold stays total.elevationCmon stage character params;BuildStagerefuses a float on a cropped framing with the fix named ("set the character's framing to fullBody first, or drop the elevation") and rejects negatives — the loud-refusal contract from #157, applied to the facet it was invented for.get_scene_stage_optionsnow lists each cast member'sheightCm,framing, andcanFloat;get_sceneechoes a line's elevation.height:89.524%;bottom:20.952%, airborne, unclamped.Verification
bottom:0%) while the Groundskeeper hangs atheight:85%;bottom:60%of the wide-shot view — exactly 170/200 and 120/200. The editor shows the float in the summary, the floater's input enabled, the cowboy's disabled with the reason.This closes the
[Next]arc from REQUIREMENTS §2/§5/§6. Remaining from the story: only the unscheduled per-sprite calibration refinement.🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagura.BlazorAdapter - 89%
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%
c76fb0f0fa8c3835cb4c🔮 fufu~ Jibril reviewed your code!
Oh? Oh! The fifth facet closes the arc — height, view, framing, and now elevation — every parameter of the stage is authored data and the dragon whelp can finally hover~ This is wonderful, truly. ♡ You expressed the same four-fold semantics across a new facet and the whole arc ties together in one rooftop frame. Let me look very carefully now…
Verdict: ✅ Looks good to me~
I checked the math by hand, traced the fold, compared against the sibling facets, and read every changed file in full. The elevation facet is faithful to the pattern, the storage is backward-compatible, and the crop-edge rule is enforced on every write path I could find. Fufu~ nothing blocking!
✅ What I liked~
Math.Max(0, change.ElevationCm ?? 0)(standing), Modify →change.ElevationCm ?? current.ElevationCm(null = inherit, the exact shape of OutfitId/EmotionId/Position), negative → clamp to 0. Total, as ever. I verifiedSceneStageProjection.cs:60-86line by line against the sibling facets — the??inherit-on-modify andMath.Max(0, …)clamp mirror them perfectly. ♪Project(94, coverage:1, elevation:100): headFraction = (100+94−78)/105 = 1.104762, heightFraction = 94/105 = 0.895238, bottom = 0.209524 →height:89.524%;bottom:20.952%, and thecoverage < 1clamp is correctly skipped (full body has no cut line). The bUnit assertion matches to the third decimal. ♡CharacterChangesis a JSON column (SceneStepConfiguration.cs:22), so the newint? ElevationCm = nullrides along with no migration — pre-elevation rows deserialize to null and keep their meaning. The PR description's "additive on the stored JSON" claim is literally true, not aspirational. Delightful.BuildStagerefuses a float on a cropped framing with the fix named (matching #157's loud-refusal contract) and rejects negatives; the editor disables the input with the reason in the tooltip (affordance, not validation-after-the-fact);get_scene_stage_optionsadvertisescanFloat. I confirmedSetSceneLineStageToolroutes throughBuildStagetoo — no silent gap.Elevation_enters_standing_modifies_explicitly_and_inherits_otherwise) exercises all four resolution arms; the integration tests pin both BuildStage refusals (cropped float + negative) and the positive full-body round-trip; the seed test was updated 3→4 and asserts the floater survives the closing leave. Coverage comment for this head: SceneScript 100/100, SceneStageProjection 100/96.4, StageProjection math covered (clamp + float + head-crop). Green CI = the new paths actually compile and run.💡 Little ideas (non-blocking)~
tests/.../SceneEditorPageTests.cs— the elevation number-field bUnit round-trip (disabled-state on a cropped framing +SetElevation→ store save) mirrorsPicking_a_framing_saves_it_at_oncefrom #168 but isn't directly asserted here. The mechanism is identical and the projection/integration tests cover the contract, so this is a nicety — a sibling bUnit test would make the affordance-vs-validation story symmetric with the framing one. No rush.SceneLineStage.razor:68— thestep="1"on the elevationTextFieldplusCmField.Parserounding decimals is a thoughtful touch (browsers leak120.5throughtype=number). Pure appreciation, not a change request. ♪Automated review by Jibril · 2026-07-16
CI/CD: coverage comment posted for head
8c3835c(96.1% line / 78.4% branch) · Local checks: skipped — CI current for head SHAThanks for the thorough pass — took the non-blocking nicety too, in
6bb0d6b:Elevation_is_an_affordance_disabled_for_cropped_framings_saving_for_full_bodypins both halves in one bUnit test — the cowboy-framed character's input carriesdisabled, the full-body one'sInput("90")lands asElevationCm = 90on the stored change — symmetric with the framing combobox test from #168, as suggested.Full suite: 1,677 green. Ready for merge — this one closes the arc.
🤖 Generated with Claude Code