docs(vt): fold the citation-pass corrections into KB 04 (Armor Self row, stance legend swap, cr registered twice, G4 global pool, resolved handler-table and HP-yield bullets, MossTankPanel line sweep)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-06 20:04:10 +02:00
parent 887242cd15
commit 2e36ea8268

View file

@ -80,10 +80,10 @@ sequence, not a generic "everything self-targeted" scan:
| 5 | `Life Magic Mastery Self I` | `eq.cs:147` |
| 6 | one self-buff per **trained skill** in `PluginCore.dz.q.x` (skill→spell-name map), only for skill classes 2 or 3 (i.e. an actual skill, not an attribute); unknown skill → warns "Unknown skill detected!" and drops it | `eq.cs:96-110,148-151` |
| 7 | `Strength/Endurance/Coordination/Quickness/Regeneration/Rejuvenation/Mana Renewal Self I` | `eq.cs:152-158` |
| 8 | `Impenetrability I` (physical bane, i.e. Armor Self's item-enchant sibling) | `eq.cs:159` |
| 8 | `Armor Self I` (the physical **prot**, `fk.b`, `fk.cs:424`; `Impenetrability I` is `fk.a` and belongs only to `eq.g()` at `eq.cs:246` — citation pass 2026-09-06) | `eq.cs:159` |
| 9 | the elemental **prots** named by `BuffProfile-Prots`/`BuffProfile_Prots` (letter string `ALFCBPS`, or a numeric legacy mode) | `eq.cs:160-165` |
| 10 | the user's **extra self buffs** list (`PluginCore.dz.m.i`, populated by the `e7.cs` picker) | `eq.cs:166-169` |
| 11 | any **untargeted** profile row (`eq.m_e`, item id -1 entries with a spell whose `isUntargeted` is true) | `eq.cs:170-181` |
| 11 | any **untargeted** profile row (`eq.m_e` rows whose spell has `isUntargeted` true — the code filters on `isUntargeted` alone) | `eq.cs:170-181` |
Then two filters remove entries whose *family* is already covered by
something in the **blacklist** (`PluginCore.dz.m.j`, populated by
@ -159,8 +159,8 @@ sets `m_h = true` to signal "this is a consumable", `eq.cs:554`).
`Build()` reads skills/attributes/active enchantments **once**, decides
every wanted line, picks a tier, and returns the whole ordered queue.
`MossTankPanel.StartBuffPass` calls `BuildPlan` (→ `BuffPlan.Build`)
exactly once per pass (`acdream:MossTankPanel.cs:3607-3656,3679-3698`);
`TryCast` (`acdream:MossTankPanel.cs:4265-4303`) then walks the fixed
exactly once per pass (`acdream:MossTankPanel.cs:3614-3656,3687-3705`);
`TryCast` (`acdream:MossTankPanel.cs:4317-4354`) then walks the fixed
queue index-by-index and never re-invokes `BuildPlan` mid-pass. Retail
re-evaluates `eq.a()` **every logic tick** (~293 ms,
`uTank2/cLogic.cs:145`) against freshly-read server skill/quality data
@ -188,7 +188,7 @@ Nothing marks completion; the rule simply stops returning `true` from
exhausted.
**acdream** has an explicit pass object (`_queue`/`_queueIndex`,
`acdream:MossTankPanel.cs:3607-3656`) with its own progress/stall
`acdream:MossTankPanel.cs:3614-3656`) with its own progress/stall
tracking (`_castThisPass`, `_sinceProgress`) not present in retail —
a deliberate modernization, not a port; see §5.
@ -287,7 +287,7 @@ casting.", `uTank2.Resources.defaultsettings.usd:1139-1143`) at
`gj.cs:171,233,237` — movement is only attempted for non-War/non-Void
schools while an instant-cast spell is in flight.
**acdream** (`acdream:MossTankPanel.cs:4348-4360`,
**acdream** (`acdream:MossTankPanel.cs:4400-4412`,
`IsVtankInstantCast`) reproduces this **exactly**: same
difficulty<50 branch, same untargeted/non-fellowship/60s/school-{31
(Creature),33 (Life)} branch (AC skill ids 31/33 = VTank's internal
@ -305,7 +305,7 @@ default enchant rows:
| `LongValueKey 218103822` value | Auto-added spells (StandardBuffs) | "No buffs" fallback | Cite |
|---|---|---|---|
| `1048576` or `33554432` | Aura of Defender, Blood Drinker, Swift Killer, Heart Seeker Self I | `eq.c(itemId, -1)` | `PluginCore.cs:8355-8375` |
| `1048576` or `33554432` | Aura of Defender Self I, Aura of Blood Drinker Self I, Aura of Swift Killer Self I, Aura of Heart Seeker Self I | `eq.c(itemId, -1)` | `PluginCore.cs:8355-8375` |
| `4194304` (excluded if `dz.o.c(itemId) == fi.h`) | Aura of Defender, Blood Drinker, Swift Killer Self I | `eq.c(itemId, -1)` | `PluginCore.cs:8377-8397` |
| `16777216` | Aura of Defender, Hermetic Link, Spirit Drinker Self I | `eq.c(itemId, -1)` | `PluginCore.cs:8399-8419` |
| `2097152` (shield) | all 7 elemental prots (`eDamageElement 0..6`) + Physical (`Armor Self I`) | `eq.c(itemId, -1)` | `PluginCore.cs:8421-8438` |
@ -377,13 +377,13 @@ spell, but structurally fires early on the *opposite* condition). This
is quoted, not paraphrased away, per project convention on decomp
predicates; flagged again in §5/§6.
**acdream** (`acdream:MossTankPanel.cs:3996-4071`,
**acdream** (`acdream:MossTankPanel.cs:4047-4120`,
`TickRandomHelper`): same distance constant (`< 18d`,
`MossTankPanel.cs:4019-4020`, matching retail's 0.075 unit band), same
11-name stem list verbatim (`MossTankPanel.cs:4026-4033`), same
"resolve to best known tier" (`OrderByDescending(Quality).ThenBy(Tier)`,
`MossTankPanel.cs:4044-4046`), same interval lock semantics
(`_randomHelperRemaining`, floor 0.25s, `MossTankPanel.cs:4062-4064`).
`MossTankPanel.cs:4071`, matching retail's 0.075 unit band), same
11-name stem list verbatim (`MossTankPanel.cs:4079-4087`), same
"resolve to best known tier" (`OrderByDescending(Quality).ThenByDescending(Tier)`,
`MossTankPanel.cs:4094-4096`), same interval lock semantics
(`_randomHelperRemaining`, floor 0.25s, `MossTankPanel.cs:4112-4114`).
It does **not** reproduce the 100-random-draws/early-`true`-on-miss
structure: instead it round-robins **deterministically** through every
(player × stem) combination from a rolling cursor and only fires on
@ -483,10 +483,10 @@ diffed line-for-line against `af.cs:117-141` in this pass.
### 4.1 The trigger (`cr.cs` / `cg.cs`)
`cr` ("RechargeSelf2") is registered **three times** in `cLogic.cs`
`cr` ("RechargeSelf2") is registered **twice** in `cLogic.cs`
(main position before `BuffSelf`, using `Recharge-Norm-*`; idle
position after target-approach, using `Recharge-NoTarg-*`;
`cLogic.cs:467,558`) — helper recharge (`gu`, §3.3) is the analogous
`cLogic.cs:470,566`) — helper recharge (`gu`, §3.3) is the analogous
rule for `Recharge-Helper-*`. Gate: `ItemUse` lock free, then any of
`cg.c(healthSetting) || cg.b(stamSetting) || cg.a(manaSetting)`
(`cr.cs:75-91`) — note the deliberate **Health > Stamina > Mana**
@ -528,17 +528,17 @@ Seven registered `IRechargeHandler`s (`cRechargeManager.cs:26-36`):
come from the `RechargeHandlerSet` settings TABLE
(`uTank2.Resources.defaultsettings.usd`, `RechargeHandlerSet` block),
parsed row = `(Vital 1/2/3=HitP/Stam/Mana, HandlerString, MinPercent,
MaxPercent, Stance 1=Other/2=MagicMode)`. Default table (26 rows,
MaxPercent, Stance 1=MagicMode/2=Other — `uTank2/eRechargeStance.cs`: `MagicMode = 1, Other`)`. Default table (26 rows,
decoded from the `.usd` TABLE literal):
| Vital | Stance | Ordered handlers (name, minmax %) |
|---|---|---|
| Health | Other | Stamina→Health (015), Mana→Health (015), Regular Spell (015), Recharge With Food (015), Kit Recharge (0100), Stamina→Health (0100), Mana→Health (0100), Regular Spell (0100), Recharge With Food (0100) |
| Stamina | Other | Kit Recharge (0100), Regular Spell (0100), Recharge With Food (0100) |
| Mana | Other | Kit Recharge (0100), Recharge With Food (0100), Regular Spell (0100) |
| Health | MagicMode | Recharge With Food (015), Kit Recharge (0100), Recharge With Food (0100), Stamina→Health (010), Regular Spell (0100) |
| Stamina | MagicMode | Kit Recharge (0100), Recharge With Food (0100), Regular Spell (0100) |
| Health | MagicMode | Stamina→Health (015), Mana→Health (015), Regular Spell (015), Recharge With Food (015), Kit Recharge (0100), Stamina→Health (0100), Mana→Health (0100), Regular Spell (0100), Recharge With Food (0100) |
| Stamina | MagicMode | Kit Recharge (0100), Regular Spell (0100), Recharge With Food (0100) |
| Mana | MagicMode | Kit Recharge (0100), Recharge With Food (0100), Regular Spell (0100) |
| Health | Other | Recharge With Food (015), Kit Recharge (0100), Recharge With Food (0100), Stamina→Health (010), Regular Spell (0100) |
| Stamina | Other | Kit Recharge (0100), Recharge With Food (0100), Regular Spell (0100) |
| Mana | Other | Kit Recharge (0100), Recharge With Food (0100), Regular Spell (0100) |
Dispatch (`cRechargeManager.Recharge`, `cRechargeManager.cs:47-78`):
filter rows to the current stance, then walk in table order; a row is
@ -576,7 +576,7 @@ fields present → `fs.g`.
**acdream** (`acdream:VitalRecharge.cs`, `VitalRechargeMethod` enum
`RegularSpell/StaminaToHealth/ManaToHealth/HealthToStamina/
HealthToMana/Kit/Food`, `VitalRecharge.cs:25-34`) mirrors the same six
HealthToMana/Kit/Food`, `VitalRecharge.cs:25-34`) mirrors the same seven
handler shapes plus the constant `HealingSkill = 21` (matching
`CharFilterSkillType 21`, `VitalRecharge.cs:43`); the item
classification rules (kit/food name-or-flag matching) were not
@ -623,8 +623,11 @@ once `q.a(item, actions)` reports it no longer needs charge,
usable-food flagged item" — the gems/food-panel mana charges),
partitioned by whether they're stacked (`item2.g()`) — rebuilt whenever
the profile changes (`dy.b()` private, `dy.cs:50-73`) by scanning
`PluginCore.dz.q.p[worn-item-name]`, i.e. **the charge candidates are
keyed to the specific worn item's own name**, not a global pool.
`PluginCore.dz.q.p[name]` for every name that `dz.m.h` (item-name →
classification, `da.cs:29`) classifies `fs.i` (`dz.q.p` is name → inventory
instances, `g6.cs:58`), i.e. **a global pool of charge consumables keyed by
the consumable's own name** — NOT scoped per worn item (corrected by the
citation pass 2026-09-06).
`h()` = "queue non-empty AND a matching charge item exists"
(`dy.cs:309-315`); `i()` = `ApplyItem(chargeItemId, dy.c[0])` — a
direct item-on-item apply, **oldest-queued worn item first**, not
@ -634,15 +637,11 @@ sorted by current charge percent (`dy.cs:318-322`).
Plan` (`ItemManaRecharge.cs:17-54`) instead (a) matches charge
consumables by a **global** `ItemType & ManaStoneItemType` flag plus a
configured name allowlist (`consumableNames`, not auto-derived from
`fs.i`'s per-worn-item keying), and (b) picks the **lowest current
`fs.i`'s classification), and (b) picks the **lowest current
mana-percent** equipped item under the configured threshold, not the
oldest-queued one. Flagged as gap G4 in §5: retail's charge-item
pool is scoped per *specific worn item name* (`q.p[name]`), so a charge
consumable that only works on item A is never offered for item B;
acdream's flat name-allowlist + generic item-type flag does not encode
that per-item scoping, which could mean either mismatching a charge to
an incompatible worn item or failing to find a match retail would have
found via the correct per-name bucket.
oldest-queued one. Flagged as gap G4 in §5: since retail's pool is
global too, the surviving difference is selection order only (retail
applies to `c[0]`, the oldest-queued worn item; acdream the lowest-%).
### 4.6 Dispel self / dispel items
@ -681,7 +680,7 @@ re-verified item-for-item against `cx.cs:74-114` in this pass.
| **G1** | `BuffPlan.Build` snapshots skills/attributes/active-enchantments **once** per pass and queues fixed spell picks; retail's `eq.a()` re-reads skill/quality **every logic tick** (~293 ms) and re-picks fresh, so a mid-pass skill raise (e.g. casting a mastery buff) immediately unlocks a higher tier for the *next* candidate in the *same* pass. acdream cannot realize that cascade — it locks in tier choices from before the pass started. | `eq.cs:477-527` (fresh per call), `uTank2/cLogic.cs:145` (293 ms tick) | `BuffPlan.cs:104-213` (one `Build` call), `MossTankPanel.cs:3607-3698` (`BuildPlan` called once at pass start, never mid-pass) | **High** for any character whose unbuffed skill sits close to a tier's difficulty threshold — retail casts a stronger buff line than acdream will, specifically for spells downstream of the Creature-Enchantment-Mastery→Focus/Willpower→Item-Enchantment/Life-Magic dependency chain the acdream code's own comment (`BuffPlan.cs:305-336`) describes but the implementation doesn't dynamically realize. |
| **G2** | Retail auto-populates default enchant-spell candidates (auras / prots / banes) for a wand/shield/pet the **moment it's added to the profile**, keyed off the item's `LongValueKey 218103822` mastery mask (§2). No equivalent "profile item → default spell rows" pipeline was found in `BuffPlan.cs`/`BuffProfile.cs`; `BuffProfile.Build` only classifies the character's **own spellbook**. | `PluginCore.cs:8327-8445` | not located in the six named files | **High** if genuinely absent — this is how a fresh VTank user gets working weapon/shield enchants with zero configuration; if it lives elsewhere in the plugin it should be cited, otherwise it's a real feature gap for item-enchantment buffing specifically (as opposed to self-buffing, which G1 covers). |
| **G3** | Retail's helper-heal target pool includes **non-fellowship tracked players** (`ai.m`, populated from general server/client vitals echo dispatch, independent 5s-per-vital re-cast cooldown, 2-minute staleness) in addition to fellowship members. acdream's `TryPlanHelper` gates entirely on `settings.HelpOthers && automation.Fellowship.IsInFellowship` and was not seen to consult a non-fellowship tracked pool. | `ai.cs:246-375` (two pools, `l` fellowship + `m` non-fellowship) | `VitalRecharge.cs:94-140` (`TryPlanHelper`, fellowship-gated only) | **Medium** — narrows "help nearby strangers" to "help fellowship only"; likely intentional scoping (`DoHelp`'s own retail description even says "The fellowship window must be open to help fellows", `uTank2.Resources.defaultsettings.usd:499-503`, suggesting the non-fellow pool may be a secondary/legacy path) but worth a deliberate decision rather than an unnoticed drop. |
| **G4** | Worn-item mana-charge matching: retail keys charge candidates to the **specific worn item's own name** (`q.p[itemName]`) and drains the **oldest-queued** worn item first; acdream uses a **global** consumable-name allowlist + generic `ManaStoneItemType` flag and picks the **lowest-mana-percent** item first. | `dy.cs:50-73,309-322` | `ItemManaRecharge.cs:17-54` | **Medium** — could either apply an incompatible charge to a worn item that doesn't accept it, or simply pick a different (lower-%) target than retail would on the same tick; low severity in practice since AC mana-charge items are broadly compatible by charge-type, but the per-item keying is a real mechanism difference. |
| **G4** | Worn-item mana-charge ordering: both retail and acdream draw charge consumables from a **global** pool (retail: every `fs.i`-classified name via `dz.m.h`/`dz.q.p`; acdream: `ManaStoneItemType` + name allowlist); retail drains the **oldest-queued** worn item first (`c[0]`), acdream the **lowest-mana-percent** one. | `dy.cs:50-73,309-322` | `ItemManaRecharge.cs:17-54` | **Low** — a different target on the same tick, never an incompatible apply (the per-worn-item keying claimed in the first draft was wrong; citation pass 2026-09-06). |
| **G5** | `ba.cs`'s `RandomHelper` fires `ValidNow = true` on the *first* of up to 100 random draws that resolves to an **uncastable** spell (doing nothing that tick), and only returns `false` if all 100 draws happen to be castable (§3.1). acdream instead deterministically round-robins every (player × stem) pair and only fires on an actually-castable one. | `ba.cs:116-125,152` | `MossTankPanel.cs:4034-4068` | **Low/positive** — acdream's version helps allies strictly more often and more predictably than retail's likely-buggy sampling loop; flagged as a behavioral divergence worth a conscious call (replicate the retail quirk vs. keep the fix) rather than an oversight, since the retail behavior reads as unintended. |
## 6. Could not determine
@ -697,16 +696,16 @@ re-verified item-for-item against `cx.cs:74-114` in this pass.
object rather than a "real" tradeable AC item, or may key off a
specific quest/consumable whose purpose isn't documented in this
build's strings).
- **Exact retail `RechargeHandlerSet` table values vs. acdream's
ported default table** — the 26-row table was fully decoded from
`uTank2.Resources.defaultsettings.usd` (§4.2), but acdream's own
default table in `VitalRecharge.cs` was not diffed cell-by-cell
against it in this pass.
- **`he.a`'s HP-yield sizing table vs. any acdream equivalent** — no
matching sizing/estimate function was located in
`VitalRecharge.cs` during this pass; may exist under a name not
grepped, or may be a genuine gap (vital-transfer handlers casting
without an HP-net-gain pre-check).
- RESOLVED (citation pass 2026-09-06): acdream's default handler table
(`VitalRechargePlanner.Handlers`, `VitalRecharge.cs:208-288`, selected by
`magicMode: mode == PluginCombatMode.Magic`) reproduces the 26 retail rows
cell-for-cell — the `magicMode` arm is stance 1 (MagicMode), the `else` arm
stance 2 (Other). And `he.a`'s HP-yield sizing has an exact equivalent:
`VitalRechargePlanner.EstimatedTransfer` (`VitalRecharge.cs:725-746`;
I 0.9/cap 50 … VI 1.5/uncapped, else 1.75), the Mana 30 floor at
`VitalRecharge.cs:699-701`, `EstimatedOrdinaryHeal` (`:712-723`,
17/25/32/45/67/87/115/135, default 10) and the net-gain pre-check at
`:708-709`.
- **`af.cs`'s vuln-severity scoring vs. acdream's `DispelController`
ally-dispel target selection**, and **`fb.cs`'s exact two-skill/
two-item-name gates vs. `TryHealersHeart`** — both confirmed present