fix(player): apply AttributeFormula to wire-derived Run/Jump skill — root cause of short jumps
Found the underlying cause of the user's persistent "jumps don't reach retail height" complaint. The wire's SkillEntry `init` field is ONLY the InitLevel (training/specialized chargen bonus, per ACE GameEventPlayerDescription.cs:317 "init_level, for training/specialized bonus from character creation"). It does NOT include the AttributeFormula contribution. ACE's CreatureSkill.Current is computed as: AttributeFormula(skill, attrs) + InitLevel + Ranks + augs + multipliers - vitae Pre-fix13 we used `init + ranks` only — dropping the AttributeFormula term, which is the DOMINANT component for movement skills (50-100 points typical). For our character that meant Jump skill 208 instead of the actual ~280-310, giving a 3.11 m peak instead of the retail ~4 m peak. Hence "feels like the upward acceleration is too slow and we don't reach the same height". Fix: - GameWindow caches portal.dat's SkillTable (0x0E000004u) at WireAll time. Each entry has a SkillFormula with attr1/ attr2/multipliers/divisor/additive constants (formula: bonus = (attr1*M1 + attr2*M2)/Div + Additive). - GameEventWiring.WireAll gains a `resolveSkillFormulaBonus(skillId, attrCurrents)` callback. GameWindow plugs in a resolver that looks up SkillTable.Skills[skillId].Formula, applies the formula using the player's current attribute values from PD. - The PD handler builds attrId→current map (ranks+start) from the parsed attributes before iterating skills, then passes it to the resolver for Run (24) and Jump (22). - Total skill = formulaBonus + InitLevel + Ranks. Matches ACE Current minus augs/multipliers/vitae (close enough — those add maybe ±10 % at most). ACDREAM_DUMP_VITALS=1 logs add a per-skill line: "vitals: PD-skill id=22 init=N ranks=N formulaBonus=N total=N" so live testing can confirm the formula is applied. Tests stay 1222 green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4b6fcffa01
commit
a060f4fc98
2 changed files with 83 additions and 11 deletions
|
|
@ -43,12 +43,25 @@ public static class GameEventWiring
|
|||
// or Jump (22) entries — we only care about those two for
|
||||
// movement physics. Caller (GameWindow) plumbs this into the
|
||||
// active PlayerMovementController + caches it for the next
|
||||
// EnterPlayerModeNow construction. Each value is `init + ranks`
|
||||
// (the holtburger-named "init" field is already the
|
||||
// attribute-derived initial component, ranks is XP-bought
|
||||
// additions; matches retail's GetCreatureSkill.Current minus
|
||||
// augs / multipliers / vitae).
|
||||
Action<int /*runSkill*/, int /*jumpSkill*/>? onSkillsUpdated = null)
|
||||
// EnterPlayerModeNow construction.
|
||||
//
|
||||
// K-fix13 (2026-04-26): the wire's `init` field is ONLY
|
||||
// InitLevel (training tier from chargen, per ACE
|
||||
// GameEventPlayerDescription.cs:317 "init_level, for
|
||||
// training/specialized bonus"). The AttributeFormula
|
||||
// contribution (Strength/Quickness/etc-derived) is computed
|
||||
// by ACE at runtime via portal.dat's SkillTable + attribute
|
||||
// currents. Without it our totals undershoot the real
|
||||
// Current skill by 50-100 points for movement skills, which
|
||||
// is why jumps looked too short. The optional
|
||||
// <c>resolveSkillFormulaBonus</c> callback lets the caller
|
||||
// (GameWindow) plug in the AttributeFormula contribution
|
||||
// using its cached SkillTable + attribute currents — when
|
||||
// present, total skill = formulaBonus + init + ranks
|
||||
// (matching ACE's CreatureSkill.Current minus
|
||||
// augs/multipliers/vitae which we still don't model).
|
||||
Action<int /*runSkill*/, int /*jumpSkill*/>? onSkillsUpdated = null,
|
||||
Func<uint /*skillId*/, IReadOnlyDictionary<uint, uint> /*attrCurrents*/, uint /*formulaBonus*/>? resolveSkillFormulaBonus = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dispatcher);
|
||||
ArgumentNullException.ThrowIfNull(items);
|
||||
|
|
@ -271,6 +284,23 @@ public static class GameEventWiring
|
|||
Console.WriteLine($"vitals: PlayerDescription body.len={e.Payload.Length} parsed={(p is null ? "NULL" : $"vec={p.Value.VectorFlags} attrs={p.Value.Attributes.Count} spells={p.Value.Spells.Count}")}");
|
||||
if (p is null) return;
|
||||
|
||||
// K-fix13 (2026-04-26): build attrId → current map while
|
||||
// iterating attributes so the skill-formula resolver below
|
||||
// can apply (attr1.current * mult1 + attr2.current * mult2)
|
||||
// / divisor + additive. "current" here = ranks + start
|
||||
// (the formula-relevant attribute level pre-augs / pre-buffs).
|
||||
// Built unconditionally (not inside the localPlayer guard)
|
||||
// because the skill-formula resolver needs it even if no
|
||||
// LocalPlayerState is wired.
|
||||
var attrCurrents = new Dictionary<uint, uint>();
|
||||
foreach (var attr in p.Value.Attributes)
|
||||
{
|
||||
// PD-attr ids 1-6 are primary attributes (Str / End
|
||||
// / Coord / Quick / Focus / Self). 7/8/9 are vitals.
|
||||
if (attr.AtType >= 1 && attr.AtType <= 6)
|
||||
attrCurrents[attr.AtType] = attr.Ranks + attr.Start;
|
||||
}
|
||||
|
||||
if (localPlayer is not null)
|
||||
{
|
||||
foreach (var attr in p.Value.Attributes)
|
||||
|
|
@ -321,16 +351,30 @@ public static class GameEventWiring
|
|||
int jumpSkill = -1;
|
||||
foreach (var s in p.Value.Skills)
|
||||
{
|
||||
int total = (int)(s.Init + s.Ranks);
|
||||
if (s.SkillId != 22u && s.SkillId != 24u) continue;
|
||||
|
||||
// K-fix13: total = AttributeFormula(skill, attrs)
|
||||
// + InitLevel (s.Init from wire)
|
||||
// + Ranks (s.Ranks from wire)
|
||||
// matches ACE CreatureSkill.Current minus
|
||||
// augs/multipliers/vitae. The attribute-formula
|
||||
// contribution is the dominant term for movement
|
||||
// skills (typically 50-100 points) and was being
|
||||
// dropped pre-fix13 — that's the root cause of
|
||||
// jumps being too short relative to retail.
|
||||
uint formulaBonus = resolveSkillFormulaBonus is not null
|
||||
? resolveSkillFormulaBonus(s.SkillId, attrCurrents)
|
||||
: 0u;
|
||||
int total = (int)(formulaBonus + s.Init + s.Ranks);
|
||||
if (s.SkillId == 24u) runSkill = total;
|
||||
else if (s.SkillId == 22u) jumpSkill = total;
|
||||
|
||||
if (dumpPd)
|
||||
Console.WriteLine(
|
||||
$"vitals: PD-skill id={s.SkillId} init={s.Init} ranks={s.Ranks} formulaBonus={formulaBonus} total={total}");
|
||||
}
|
||||
if (runSkill >= 0 || jumpSkill >= 0)
|
||||
{
|
||||
if (dumpPd)
|
||||
Console.WriteLine($"vitals: PD-skills run={runSkill} jump={jumpSkill}");
|
||||
onSkillsUpdated(runSkill, jumpSkill);
|
||||
}
|
||||
}
|
||||
|
||||
// Issue #7 — enchantment block: feed each entry into the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue