fix(gfx): restore third CG glow

This commit is contained in:
gamer147
2026-07-11 20:05:51 -04:00
parent 7c0495c88d
commit 2a49a0697c
9 changed files with 154 additions and 27 deletions

View File

@@ -24,6 +24,22 @@ public class GfxAnimationTests
Assert.True(o.TranslationEnabled);
}
[Fact]
public void CurrentScaleSetter_ExpandsGlowAroundItsAnchor()
{
var g = new GfxState();
g.SetSurface(6, 0x29, -1);
g.BindDraw(0xcb8e, 6, 0, 0, 800, 800, 0, 550);
g.GetOrCreate(0xcb8e).V18 = (400, 950, 0);
g.SetCurrentScale(0xcb8e, (210, 210, 100));
var v = g.SnapshotVisibleObjects().Single();
var top = Transform2DMath.Build(v.Transform).FromLocalOrigin(v.DstX, v.DstY).Apply(400, 0);
Assert.Equal(2.1, v.Transform.ScaleX, 3);
Assert.Equal(2.1, v.Transform.ScaleY, 3);
Assert.Equal(110, top.Y, 3);
}
[Fact]
public void RotationCycle_DoesNotOverwriteMatrixChannels()
{
@@ -140,6 +156,20 @@ public class GfxAnimationTests
Assert.True(o.RotationChannelEnabled);
}
[Fact]
public void Op0x1fd_SetsCurrentScalePercentages()
{
var t = T();
var scene = ScriptAssembler.Assemble(t, "STATIC_SCALE", new List<(int, Operand[])>
{
MovGI(1, 0xcb8e), MovGI(2, 210), MovGI(3, 240), MovGI(4, 100),
(0x1fd, new[] { G(1), G(2), G(3), G(4) }), Exit(),
}, System.Array.Empty<string>());
var vm = new VirtualMachine(scene, t, new RecordingHost());
vm.Run();
Assert.Equal((2.1, 2.4, 1.0), vm.Gfx.TryGet(0xcb8e)!.ScaleCurrent);
}
[Fact]
public void RotationCycleAndClock_DispatchRemainSeparate()
{

View File

@@ -46,24 +46,53 @@ public class RenderObjectBlendTests
}
[Fact]
public void DrawColor_0x203_IsTintStrength_ObjectStaysOpaque()
public void TexturedMode0_UsesOpaqueMultiplicativeRgbAndIgnoresPackedAlpha()
{
// Op 0x203/0x202 alpha is TINT-BLEND STRENGTH, not object opacity (evidence: a CG drawn with
// (alpha=0, color=white) must stay fully OPAQUE + untinted, not vanish). Root cause of the grey-BG.
// Mode 0 is the native opaque textured path. Its packed alpha byte is neither object opacity nor
// tint strength; RGB multiplicatively modulates the source, so white is identity.
var g = WithVisibleObject(0x100, resId: 5, colorKey: -1);
g.SetObjectColor(0x100, GfxState.PackColor(0x00, 0xFFFFFF)); // "no tint" — the grey-BG case
var ro = g.SnapshotVisibleObjects().Single();
Assert.Equal(255, ro.Alpha); // OBJECT STAYS OPAQUE (was wrongly 0 -> invisible)
Assert.Equal(255, ro.Alpha); // opaque
Assert.Equal(0, ro.TintStrength); // zero tint strength
Assert.True(ro.MultiplyTint);
g.SetObjectColor(0x100, GfxState.PackColor(0x80, 0x102030)); // half-strength tint toward 0x102030
g.SetObjectColor(0x100, GfxState.PackColor(0x80, 0x102030));
var ro2 = g.SnapshotVisibleObjects().Single();
Assert.Equal(255, ro2.Alpha); // still opaque
Assert.Equal(0x80, ro2.TintStrength); // strength from the alpha byte
Assert.Equal(255, ro2.Alpha); // packed alpha remains ignored
Assert.Equal(0, ro2.TintStrength);
Assert.Equal(0x102030, ro2.Tint);
Assert.True(ro2.MultiplyTint);
Assert.Equal(BlendKind.Alpha, ro2.Blend);
}
[Fact]
public void TransitionCleanup_Mode2ToMode0PreservedWhite_RemainsIdentity()
{
var g = WithVisibleObject(0x100, resId: 5, colorKey: -1);
g.SetStaticObjectColorResolved(0x100, 2, -1, -1); // 0x128de: transition source
Assert.Equal(0, g.SnapshotVisibleObjects().Single().TintStrength);
g.SetStaticObjectColorResolved(0x100, 0, -1, -1); // 0x12478: transition cleanup
var rendered = g.SnapshotVisibleObjects().Single();
Assert.Equal(255, rendered.Alpha);
Assert.Equal(0xffffff, rendered.Tint);
Assert.Equal(0, rendered.TintStrength);
Assert.True(rendered.MultiplyTint);
}
[Fact]
public void SurfacelessMode0_RetainsFillStrength()
{
var g = new GfxState();
g.BindDraw(0x100, 1, 0, 0, 10, 10, 0, 0);
g.SetObjectColor(0x100, GfxState.PackColor(0x80, 0x102030));
var rendered = g.SnapshotVisibleObjects().Single();
Assert.Equal(255, rendered.Alpha);
Assert.Equal(0x80, rendered.TintStrength);
Assert.False(rendered.MultiplyTint);
}
[Fact]
public void Mode1_UsesArgbAlphaAsOpacityAndRgbAsMultiplicativeModulation()
{

View File

@@ -410,6 +410,17 @@ public sealed class GfxState
}
}
/// <summary>Op 0x1fd: immediately replace the object's current scale matrix. Script operands are
/// integer percentages; native divides them by 100 before matrix4_make_scale at obj+0x6c.</summary>
public void SetCurrentScale(long handle, (long X, long Y, long Z) percent)
{
lock (_lock)
{
var o = GetOrCreate(handle);
o.ScaleCurrent = (percent.X / 100.0, percent.Y / 100.0, percent.Z / 100.0);
}
}
/// <summary>Op 0x21e: normalized scale target (100 = identity), with independent delay/duration.</summary>
public void SetScaleChannel(long handle, long delayMs, long durationMs, (long X, long Y, long Z) percent)
{
@@ -520,8 +531,16 @@ public sealed class GfxState
// not a request to replace every texel with white.
alpha = a; strength = 0; blend = BlendKind.Alpha;
}
else if (resId != 0)
{
// Native mode 0 is the opaque textured path. RGB modulates the source and the
// packed alpha byte does not become tint strength; in particular 0xffffffff is
// identity after the foreground-transition cleanup write.
alpha = 255; strength = 0; blend = BlendKind.Alpha; multiplyTint = true;
}
else
{
// A surfaceless mode-0 object is a solid fill, whose high byte remains fill strength.
strength = a; blend = BlendKind.Alpha;
}
}

View File

@@ -404,6 +404,9 @@ public sealed class VirtualMachine
Write(a[0], Gfx.TryGet(Read(a[1])) != null ? 0 : -1); return pc + 1;
case "set-gfx-geom3-c": // 0x1ff (handle)(a)(b)(c) -> V16c
Gfx.GetOrCreate(Read(a[0])).V16c = (Read(a[1]), Read(a[2]), Read(a[3])); return pc + 1;
case "u00420620": // upstream ABI label
case "gfx-set-scale-current": // 0x1fd (handle)(sx%)(sy%)(sz%) -> current scale matrix
Gfx.SetCurrentScale(Read(a[0]), (Read(a[1]), Read(a[2]), Read(a[3]))); return pc + 1;
case "set-gfx-field64": // 0x212 (idx)(val)
Gfx.GetOrCreate(Read(a[0])).Field64 = Read(a[1]); return pc + 1;
case "set-gfx-xy": // 0x213 (idx)(x)(y)