fix(chargen): Campaign CC gate round 1 closeout — Group 3: round review fixes (F4-F11, F14, F16)
The remaining code-bearing findings from the round review, F4-F16 minus the doc-only items (batched separately): - F4: three client-wide UiButton corpus sweeps (LabelBox path — exactly the 4 Town buttons, confined to chargen; conflicting custom-selection- pair + standard Normal/Highlight media — zero found, no gate tightening needed; per-state label-color map — 209 matches beyond chargen, confirming AP-222's mechanism has always been broadly active since it shipped generically in DatWidgetFactory). - F5/F6: LayoutImporter's Batch C un-consumed-children carve-out now honors a child's own AuthoredInvisible flag (a narrow honor scoped to exactly that carve-out, not the general #408 client-wide one) — the chat transcript's new-text indicator (0x1000048C) was building as a visible phantom element retail never shows; verified both directions against the gold-frame pieces, which do not author Invisible. - F7: BoundedProcessOutputCapture.AppendLine combines the line text and its trailing newline into one buffer and one file open/write/close instead of two. - F9: corrected a stale comment in RuntimeSettingsTargets — #407 split DisplayModeCatalog's Resolutions/WindowedResolutions in two, so the fullscreen validator's own narrower list is now DELIBERATELY different from the Config dropdown's fuller offering, not the "must match" bug the comment described. - F10: documented (not changed) why the LabelBox path's default 3px inset and the face-relative +4px gap in DatWidgetFactory.BuildButton are deliberately different numbers — neither carries a retail citation, and moving either to match the other would be an unfounded guess on a button that currently works correctly. - F11: Heritage/Profession/Summary/Town description pages now compose DatRichText.Compose's result ONCE inside their already revision-gated Refresh, caching the built line list instead of re-wrapping on every draw call. - F14: documented (not changed) why PrivateEntityViewportRenderer's _animatedIds set carrying a reserved-but-never-drawn backdrop id is harmless — BuildDrawEntities already excludes a null/empty backdrop from the actual draw list, so the id is never looked up. - F16: the Summary preview now uses its own render-id pair (SummaryPreviewRenderId/SummaryPreviewBackdropRenderId, 0xDA11D035/ 0xDA11D036) instead of sharing the Appearance page's (0xDA11D032/0xDA11D034) — confirmed by tracing FixedEntityTextureOwnerLease through TextureCache to CompositeTextureArrayCache's shared owner tracker that both pages' previews share ONE process-wide TextureCache, so sharing render ids was a real cross-page texture-release collision (either page's own re-dress or disposal could release the OTHER page's still-active textures), not a theoretical one. F3's own register bookkeeping (AP-229 addendum) and F12's register/AD header-count corrections land in the docs-only commit alongside F15. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
0fed5fdd91
commit
bd359d5181
17 changed files with 636 additions and 37 deletions
|
|
@ -66,6 +66,57 @@ public sealed class ChargenPreviewEntityBuilderTests
|
|||
_out.WriteLine($"setup=0x{appearance.SetupId:X8} meshRefs={entity.MeshRefs.Count} subPalettes={entity.PaletteOverride.SubPalettes.Count}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// F16 (Campaign CC gate round 1 closeout): the Appearance and Summary
|
||||
/// pages must stamp DIFFERENT <see cref="AcDream.Core.World.WorldEntity.Id"/>
|
||||
/// values on their preview entities — both feed the SAME shared
|
||||
/// <c>TextureCache</c> owner-tracking key
|
||||
/// (<c>ChargenPreviewEntityBuilder.SummaryPreviewRenderId</c>'s own doc
|
||||
/// has the full collision trace). Pins BOTH halves: the constants
|
||||
/// themselves are distinct, AND the explicit <c>renderId</c> parameter
|
||||
/// actually reaches the built entity (not silently ignored).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TryBuild_ExplicitRenderId_StampsThatIdOnTheEntity_DistinctFromTheAppearanceDefault()
|
||||
{
|
||||
Assert.NotEqual(
|
||||
ChargenPreviewEntityBuilder.PreviewRenderId,
|
||||
ChargenPreviewEntityBuilder.SummaryPreviewRenderId);
|
||||
Assert.NotEqual(
|
||||
ChargenPreviewEntityBuilder.PreviewBackdropRenderId,
|
||||
ChargenPreviewEntityBuilder.SummaryPreviewBackdropRenderId);
|
||||
|
||||
string? datDir = CornerFloodReplayTests.ResolveDatDir();
|
||||
if (datDir is null) { _out.WriteLine("SKIP: dats unavailable"); return; }
|
||||
|
||||
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
||||
using var adapter = new DatCollectionAdapter(dats);
|
||||
|
||||
ChargenOptions options = ChargenTableReader.Load(adapter);
|
||||
Assert.True(options.TryGetHeritage(1u, out ChargenHeritageOptions? aluvian)); // Aluvian.
|
||||
Assert.True(aluvian!.GendersByKey.TryGetValue(1, out ChargenGenderOptions? male));
|
||||
|
||||
var catalog = new ChargenAppearanceCatalog(adapter);
|
||||
ChargenAppearanceSelection selection = ChargenAppearanceSelection.Default with
|
||||
{
|
||||
HairStyle = male!.HairStyles.Count > 0 ? 0u : ChargenAppearanceSelection.Unset,
|
||||
SkinShade = 0.5,
|
||||
};
|
||||
|
||||
bool composed = ChargenAppearanceFactory.TryCompose(
|
||||
options, 1u, 1, selection, catalog, catalog, out ChargenAppearanceResult appearance);
|
||||
Assert.True(composed);
|
||||
|
||||
var animations = new RetailAnimationLoader(adapter);
|
||||
var entity = ChargenPreviewEntityBuilder.TryBuild(
|
||||
adapter, animations, appearance, heritageId: 1u, Quaternion.Identity, new object(),
|
||||
renderId: ChargenPreviewEntityBuilder.SummaryPreviewRenderId);
|
||||
|
||||
Assert.NotNull(entity);
|
||||
Assert.Equal(ChargenPreviewEntityBuilder.SummaryPreviewRenderId, entity!.Id);
|
||||
Assert.NotEqual(ChargenPreviewEntityBuilder.PreviewRenderId, entity.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryBuild_UnknownSetupId_ReturnsNull()
|
||||
{
|
||||
|
|
@ -317,6 +368,36 @@ public sealed class ChargenPreviewEntityBuilderTests
|
|||
_out.WriteLine($"backdropSetup=0x{aluvian.EnvironmentSetupId:X8} meshRefs={entity.MeshRefs.Count}");
|
||||
}
|
||||
|
||||
/// <summary>F16 (Campaign CC gate round 1 closeout): the backdrop's own
|
||||
/// explicit <c>renderId</c> parameter reaches the built entity, the
|
||||
/// same shape as <see cref="TryBuild_ExplicitRenderId_StampsThatIdOnTheEntity_DistinctFromTheAppearanceDefault"/>
|
||||
/// pins for the main preview entity.</summary>
|
||||
[Fact]
|
||||
public void TryBuildBackdrop_ExplicitRenderId_StampsThatIdOnTheEntity()
|
||||
{
|
||||
string? datDir = CornerFloodReplayTests.ResolveDatDir();
|
||||
if (datDir is null) { _out.WriteLine("SKIP: dats unavailable"); return; }
|
||||
|
||||
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
||||
using var adapter = new DatCollectionAdapter(dats);
|
||||
|
||||
ChargenOptions options = ChargenTableReader.Load(adapter);
|
||||
Assert.True(options.TryGetHeritage(1u, out ChargenHeritageOptions? aluvian)); // Aluvian.
|
||||
if (aluvian!.EnvironmentSetupId == 0u)
|
||||
{
|
||||
_out.WriteLine("SKIP: installed dat's Aluvian heritage authors no EnvironmentSetupId.");
|
||||
return;
|
||||
}
|
||||
|
||||
var entity = ChargenPreviewEntityBuilder.TryBuildBackdrop(
|
||||
adapter, aluvian.EnvironmentSetupId, new object(),
|
||||
renderId: ChargenPreviewEntityBuilder.SummaryPreviewBackdropRenderId);
|
||||
|
||||
Assert.NotNull(entity);
|
||||
Assert.Equal(ChargenPreviewEntityBuilder.SummaryPreviewBackdropRenderId, entity!.Id);
|
||||
Assert.NotEqual(ChargenPreviewEntityBuilder.PreviewBackdropRenderId, entity.Id);
|
||||
}
|
||||
|
||||
/// <summary>Retail's own gate at 0x004eed29 (<c>if (eax_32 != INVALID_DID.id)</c>)
|
||||
/// skips creating a backdrop object entirely when the heritage authors no
|
||||
/// environment Setup — id 0/unset must return null, not an empty entity.</summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue