feat(cg): a CG fills the background layer — CGs on the scene stage #226
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/cg-in-scenes"
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?
Slice 6 of the event-CG arc: the payoff. The stage model's comment said "An event CG is a later asset type; it will fill this same layer" — this PR cashes that in.
The layer
StageBackgroundChangegainsCgVariationId— additive on the stored JSON likeLocationIdand elevation before it, pinned by a deserialization test proving pre-CG rows read unchanged. Exactly one of the three fills the layer; a change replaces the whole layer; all-null clears. The projection carries a CG through the fold until the next change replaces it (its own test walks variant → CG → inherit → location).The surfaces
GetSceneStageOptionslists every variation grouped by set.StageBackgroundOptiongeneralizes (LocationIdnullable,GroupName/Nameinstead of location-specific names) and carriesCgVariationId+PublishedGenerationId.cg:key beside locations, and the line summary names them the same way.cgVariationIdon the background, validated against the stage options with the id named in the refusal so the self-repair loop can correct itself; the options tool reports CG entries distinctly.Contracts
The seeded rooftop scene cuts to "First refusal" on a narration beat — the picker, summary, validator and player all have something real to exercise — and the seed test pins the CG inheriting through the fold to the scene's final frame.
Verification
Tests at every layer: JSON additivity, projection fold, the tool's accept + named-refusal round trip through the real pipeline, the picker writing the change onto the stored row, and the player rendering the published generation and the empty state (publish flips it, same test). Browser-verified end to end: playing the seeded chapter walks classroom → school base → "The rooftop confession — First refusal — no image yet", inherited through the following frames.
Full suite: 1763 green (314 / 792 / 657), all three project lines present.
🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagura.BlazorAdapter - 88.6%
Kagura.Domain - 94.3%
D04ADFED3A21D401C2764A1D17367E35BEB556CBB3B4B0B74__NonSlugChars_0
Kagura.Infrastructure - 97.5%
n
n
on
ng
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__ArtifactTimestampRegex_2
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__ProjectRoute_0
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__TabQuery_1
Kagura.Kernel - 90%
Kagura.Server - 81.8%
Kagura.UseCases - 95.3%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! Oh! This is the CG payoff slice — the one the stage model's comment has been promising since the beginning! "An event CG is a later asset type; it will fill this same layer." And fill it it does, with surgical precision across every surface. The layer model, the fold, the options, the picker, the player, the assistant tools — every single one touched, every one consistent. This is the kind of architecture-sweeping feature done right, and I am genuinely delighted~ ♡
Verdict: ✅ Looks good to me~
Not a single blocking issue. I traced every branch, compared against every sibling, and the design is airtight. Let me show you what I checked~
✅ What I liked~
The fold is flawless.
SceneStageProjectionlines 96-98 enforce exactly-one-of-three with surgical clearing:backgroundCg = bg.VariantId is null && bg.LocationId is null ? bg.CgVariationId : null. A CG can never bleed into a variant's slot, a variant can never shadow a CG. The invariant "at most one background id is set" holds at every line. Mwah. ♪Additive JSON, proven.
StageBackgroundChangegainsCgVariationIdas the third optional param with= null, andA_background_stored_before_cgs_existed_reads_unchangeddeserializes{"VariantId":"..."}and assertsCgVariationIdis null. The old rows read unchanged — exactly the promiseLocationIdandElevationCmmade before it. The pattern is perfectly consistent.The CG → published generation resolution is elegant.
BackgroundSrcroutes a CG toprojects/{Slug}/cgs/generations/{published}— the generation id IS the whole cache key (ADR 0049), so publishing another render propagates to every scene with no relinking. No version param, no relinking, just the pointer. And when there's no render yet → honestnull→ the "no image yet" state, never a failure. The player test even publishes mid-test and asserts the same scene flips to the rendered URL. Chef's kiss.The assistant validation names the bad id in the refusal.
"No stageable event CG variation with id {cgVariationId}"— so the agent's self-repair loop can correct itself against the options list. This is exactly the pattern the location/variant refusals follow, and it's tested both ways (accept + bad-id-refused-with-id-in-message) through the real pipeline.The
BackgroundKey/BackgroundValue/OnBackgroundChangedround-trip is clean.cg:{id}→ parse →new StageBackgroundChange(null, null, id)→ saved →BackgroundValuereadsCgVariationId→cg:{id}. No collision withloc:or bare Guid — Guid.ToString() never carries a prefix. I traced it both directions. ♡Every surface updated consistently. The "Empty stage" condition in SceneLineStage now includes
BackgroundCgVariationId is null. TheBackgroundOptionssort puts locations before CGs with the primary location first.BackgroundLabel()resolves CG to GroupName — Name.BackgroundName()in the player generalizes correctly. Nothing was left half-done.DI wiring verified. Both
ListCgSetsandListCgVariationsare registered inDependencyInjection.cs(lines 80, 85) and inAdapterTestContext. The production constructor resolves cleanly — I confirmed it.Test coverage at every layer. JSON additivity (SceneStepDomain), projection fold variant→CG→inherit→location (SceneStageProjection), player published+empty (PlayerPage), editor picker round-trip (SceneEditorPage), assistant accept+refuse (AssistantSceneScriptTools), seed inheriting through fold to final frame (SeedDevData). 1763 green, all three project lines.
💡 Little ideas (non-blocking)~
SceneStageOptions.cs:27-33— There are two stacked<summary>XML doc tags onStageBackgroundOption. The old one (lines 27-30) still says "a location variant, or the location's own base background" — it doesn't mention CGs and is now superseded by the new one (lines 31-33) which is strictly better. Consider removing the old summary and keeping only the new one, so IntelliSense/DocFX doesn't show the stale one. ♡SceneScriptTools.cs:135-139— The new "else" branch (empty background object →new StageBackgroundChange(null)) is a new code path (before this PR,LocationIdwas non-nullable so an empty object was impossible). The behavior is trivially correct by inspection —new StageBackgroundChange(null)clears the layer — but no test exercises it. A quick test sending{"background": {}}through the assistant tool and asserting the stored row clears would pin this fallback. Not a bug risk, just belt-and-suspenders~Automated review by Jibril · 2026-08-05
CI/CD: absent for head
2760b8e(PR just opened, 0 comments) · Local checks: build 0/0, 101/101 relevant tests pass (55 BlazorAdapter + 18 Integration + 28 UseCases)Both taken in
203ce9d:💡 1 — the two summaries merged into one: the CG-aware text keeps the old one's still-true
ImageUpdatedAtsentence and the stale location-only framing is gone. (Same stacked-summary wart you caught on #220's interface — twice caught, hopefully now unlearned.)💡 2 — the tool test now also sends
{"background": {}}at the staged line and asserts the stored row carries the change with all three slots null — the clear-the-layer fallback the newly nullableLocationIdopened up, pinned rather than left to inspection.Both suites re-ran green.
🔮 fufu~ Jibril reviewed your code!
Back so soon? Fufu~ you really do spoil me~ ♡ The synchronized
203ce9dis a surgical +16/-5 across 2 files that resolves both my little ideas from the first pass. I love a clean response-to-review — no scope creep, no drift, just the two fixes asked for. Let me show you what I checked~Verdict: ✅ Looks good to me~
Both non-blocking suggestions closed, both verified. No new issues. The original architectural approval stands in full.
✅ What I liked~
💡 1 — one summary, no stale residue. The two stacked
<summary>tags onStageBackgroundOptionare merged into one. The CG-aware text keeps the old one's still-trueImageUpdatedAtsentence (the cache-busting version, player-preview story), and the stale location-only framing is gone. I read the full merged summary — it's accurate, single-voiced, and no longer contradicts itself. You even noted you'd caught the same stacked-summary wart on #220's interface — twice caught, hopefully unlearned indeed~ ♪💡 2 — the empty-background clear is pinned, not inspected. This is the one that made me giddy. The
elsebranch (new StageBackgroundChange(null)) was trivially correct by inspection at the first review, but "correct by inspection" is a bedtime story the compiler doesn't read. NowA_cg_variation_stages_as_the_background_and_a_bad_id_is_refused_by_namesends{"background": {}}at the staged CG line, re-reads the stored row through the real store (ListSceneSteps), and assertscleared.Background is { VariantId: null, LocationId: null, CgVariationId: null }.I mutation-checked it independently. I neutered the
elsebranch tobackgroundChange = nulland re-ran the test — it FAILED. Restored, it passes. So this is a genuine directional test, not a tautology: delete or weaken the clear behavior and the assertion catches it. Fufu~ you wouldn't leave THIS one to inspection in production, would you? ♡ (Well — you didn't. That's why I'm happy.)✅ Verification~
git diff 2760b8e..203ce9d=SceneStageOptions.cs(doc merge, 8 lines) +AssistantSceneScriptToolsTests.cs(the clear-the-layer test block, 13 lines). Zero production behavioral drift — theelsebranch code is byte-identical to2760b8e, only now it has a test.203ce9d(timestamp 23:46 post-sync — the PR was already merged to main as1ebc96fby then, but the coverage run reflects the branch state).No blocking issues. No new non-blocking suggestions. This is done~ ♡
Automated review by Jibril · 2026-08-06
CI/CD: coverage #5842 present (96.1%/77%) · Local: target test pass + mutation-verified directional