From f6e895c06cd925ca17f1f373d659281b82ca0f49 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 30 Jul 2026 17:35:32 +0200 Subject: [PATCH] feat(diag): #266 - [stat-chain] apparatus across the vitae-to-movement chain Three permanent low-volume probes: installed vitae record (id/type/key/ value) at ReplaceManifest, any vitae record dropped by GetMod's spell-table prepass (retail's _vitae singleton never runs family stacking - if this fires, hoist the Bucket-4 branch), and the full recompute state (base skills, runMod, effective skills, active count). One instrumented relog decides the break point. Co-Authored-By: Claude Opus 5 --- src/AcDream.Core/Spells/EnchantmentMath.cs | 16 ++++++++++++++++ src/AcDream.Core/Spells/Spellbook.cs | 14 ++++++++++++++ .../Gameplay/RuntimeCharacterState.cs | 12 ++++++++++++ 3 files changed, 42 insertions(+) diff --git a/src/AcDream.Core/Spells/EnchantmentMath.cs b/src/AcDream.Core/Spells/EnchantmentMath.cs index 160ad228..d23c3938 100644 --- a/src/AcDream.Core/Spells/EnchantmentMath.cs +++ b/src/AcDream.Core/Spells/EnchantmentMath.cs @@ -100,7 +100,23 @@ public static class EnchantmentMath foreach (var ench in enchantments) { if (!table.TryGet(ench.SpellId, out var meta)) + { + // #266 apparatus (permanent, rare): a VITAE record silently + // dropped by the spell-table lookup would zero the whole + // stat-chain effect — surface it. (Retail keeps _vitae on a + // dedicated singleton slot that never runs family stacking; + // if this line ever fires the faithful fix is hoisting the + // Bucket-4 branch above the table lookup.) + if (ench.Bucket == 4) + { + System.Console.WriteLine( + System.FormattableString.Invariant( + $"[stat-chain] VITAE DROPPED by spell-table lookup: " + + $"spell={ench.SpellId} value=" + + $"{(ench.StatModValue is float dv ? dv.ToString("F4") : "NULL")}")); + } continue; + } // Family 0 means "no stacking bucket" — these don't dedup; // pass them through with a synthetic key per layer. uint bucket = meta.Family == 0 ? ench.LayerId | 0x80000000u : meta.Family; diff --git a/src/AcDream.Core/Spells/Spellbook.cs b/src/AcDream.Core/Spells/Spellbook.cs index 88377f46..d78a9d43 100644 --- a/src/AcDream.Core/Spells/Spellbook.cs +++ b/src/AcDream.Core/Spells/Spellbook.cs @@ -382,7 +382,21 @@ public sealed class Spellbook _vitalModCache.Clear(); _skillModCache.Clear(); foreach (ActiveEnchantmentRecord enchantment in enchantments) + { UpsertManifestEnchantment(enchantment); + // #266 apparatus (permanent, login-only volume): surface every + // installed VITAE record so the stat-chain break point is + // decidable from one log. Bucket 4 = vitae singleton class. + if (enchantment.Bucket == 4) + { + System.Console.WriteLine( + System.FormattableString.Invariant( + $"[stat-chain] login vitae installed: spell={enchantment.SpellId} " + + $"statModType=0x{enchantment.StatModType ?? 0:X} " + + $"key={enchantment.StatModKey ?? 0} " + + $"value={(enchantment.StatModValue is float v ? v.ToString("F4") : "NULL")}")); + } + } for (int i = 0; i < _favoriteSpells.Length; i++) { diff --git a/src/AcDream.Runtime/Gameplay/RuntimeCharacterState.cs b/src/AcDream.Runtime/Gameplay/RuntimeCharacterState.cs index e7fc9097..abcb9b38 100644 --- a/src/AcDream.Runtime/Gameplay/RuntimeCharacterState.cs +++ b/src/AcDream.Runtime/Gameplay/RuntimeCharacterState.cs @@ -183,6 +183,18 @@ public sealed class RuntimeCharacterState : IDisposable int jump = _jumpSkillBase >= 0 ? ApplySkillEnchantments(_jumpSkillBase, JumpSkillId) : -1; + // #266 apparatus (permanent, low-volume — fires only on skill-base or + // enchantment changes, the [snap] class): the full stat-chain state at + // each recompute. If vitae is active but runMod prints 1.0, the break + // is in GetSkillMod's record handling; if runMod is right but the + // felt speed doesn't change, the break is downstream of MovementSkills. + EnchantmentMath.VitalMod runMod = Spellbook.GetSkillMod(RunSkillId); + System.Console.WriteLine( + System.FormattableString.Invariant( + $"[stat-chain] base run={_runSkillBase} jump={_jumpSkillBase} " + + $"runMod={runMod.Multiplier:F4}x+{runMod.Additive:F1} " + + $"-> eff run={run} jump={jump} " + + $"(activeEnchantments={System.Linq.Enumerable.Count(Spellbook.ActiveEnchantments)})")); MovementSkills.Update(run, jump); }