docs(vt): fold the citation-pass corrections into KB 02 (ga.a line map, gj/gs identity, fd.cs IdlePeace gate scope, VitalPlan.Threshold branch, entry counts)

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

View file

@ -66,8 +66,8 @@ ey.b()`) — i.e. it coalesces multiple timer/event firings within the same
render frame into one pass. render frame into one pass.
`SchedulePoke()` (`cLogic.cs:154-161`) is the fast-wake path: two Decal `SchedulePoke()` (`cLogic.cs:154-161`) is the fast-wake path: two Decal
game-state trackers, `gj` (spell-cast completion, `gj.cs`) and `gs` game-state trackers, `gj` (the SpellCaster cast-completion tracker, `gj.cs:269,326,442`) and `gs`
(attack/missile completion, `gs.cs`), each fire a `b`-enum "completed/idle" (the WandCaster cast-completion tracker, `gs.cs:178,194,233,442` — NOT attack/missile completion; citation pass 2026-09-06), each fire a `b`-enum "completed/idle"
event that `cLogic` subscribes to (`cLogic.cs:52-59, 125-139`); on that event that `cLogic` subscribes to (`cLogic.cs:52-59, 125-139`); on that
event `SchedulePoke()` stamps the dedup counter to the *current* frame and event `SchedulePoke()` stamps the dedup counter to the *current* frame and
re-arms `f` for **1 ms**, forcing a near-immediate re-poke on the very re-arms `f` for **1 ms**, forcing a near-immediate re-poke on the very
@ -121,7 +121,7 @@ is the actual pass, called as `a(l, n)` from `TryPokeMacro` and as
(in the second case) immediately calls `StopMacro()` on itself. (in the second case) immediately calls `StopMacro()` on itself.
- Sets `dz.o.c = true` (the single "is the macro running" flag almost - Sets `dz.o.c = true` (the single "is the macro running" flag almost
every `ValidNow`/`Running` body reads), clears the loot-wait table every `ValidNow`/`Running` body reads), clears the loot-wait table
(`dz.o.j`), resets the equip-retry counter (`dz.o.r = 0`), clears every (`dz.o.l()` and `dz.o.k()`, `cLogic.cs:319,321` — the field letter `j` in the first draft was wrong), resets the equip-retry counter (`dz.o.r = 0`), clears every
`ActionLockType` cooldown (`dz.o.n.b()`), resets Meta, arms the 293 ms `ActionLockType` cooldown (`dz.o.n.b()`), resets Meta, arms the 293 ms
timer and starts it, disables every item in the macro-disabled Always timer and starts it, disables every item in the macro-disabled Always
Rules list `m` (they're only meaningful while stopped — see §1.1), and Rules list `m` (they're only meaningful while stopped — see §1.1), and
@ -155,7 +155,7 @@ lock-target-requested, corpse-open-attempt, recharge-level-boost per
vital, random-helper-buff-lock, buff-cast-recast) — see the "Suppressors" vital, random-helper-buff-lock, buff-cast-recast) — see the "Suppressors"
column in §2. `ga.a(CombatState,...)` (the shared wield/mode gate, §3) column in §2. `ga.a(CombatState,...)` (the shared wield/mode gate, §3)
also returns `false` outright whenever `Actions.BusyState != 0` also returns `false` outright whenever `Actions.BusyState != 0`
(`ga.cs:1454-1458`), which starves every rule that routes through it (`ga.cs:1448-1451`), which starves every rule that routes through it
(BuffSelf, DispelSelf, DispelAllies, RechargeOther, UseHealersHeart, (BuffSelf, DispelSelf, DispelAllies, RechargeOther, UseHealersHeart,
RandomHelper, Attack's own wield step) for that tick without any explicit RandomHelper, Attack's own wield step) for that tick without any explicit
"paused" bookkeeping. "paused" bookkeeping.
@ -190,7 +190,7 @@ VTank schedules rules on two separate tracks:
### 2.2 Main-track priority order ### 2.2 Main-track priority order
All 39 entries of `l`, in the exact order `InitializeDefaultLogicRules` All 66 entries of `l`, in the exact order `InitializeDefaultLogicRules`
(`cLogic.cs:433-578`) adds them. Sentinel rows are markers only (their (`cLogic.cs:433-578`) adds them. Sentinel rows are markers only (their
`ValidNow` is hard-coded `false`, `LogicRuleSentinel.cs:11`); they never `ValidNow` is hard-coded `false`, `LogicRuleSentinel.cs:11`); they never
win, but their names are the section headers VTank's own debug UI/log win, but their names are the section headers VTank's own debug UI/log
@ -487,38 +487,38 @@ near-identical body to `cr`, including its own copy of the stuck-combat-
state handling for level-boost spells. No `new be(` appears anywhere in state handling for level-boost spells. No `new be(` appears anywhere in
the decompiled tree — it is superseded, unreachable code, not a rule any the decompiled tree — it is superseded, unreachable code, not a rule any
current VTank build ever runs. Flagged here so it is not mistaken for a current VTank build ever runs. Flagged here so it is not mistaken for a
27th live rule. 25th live rule.
--- ---
## 3. Combat-mode and equipment sequencing ## 3. Combat-mode and equipment sequencing
### 3.1 The shared gate: `ga.a(CombatState, int, bool, eDamageElement, ### 3.1 The shared gate: `ga.a(CombatState, int, bool, eDamageElement,
ePrismaticDamageBehavior, int)` (`ga.cs:1421-1568`) ePrismaticDamageBehavior, int)` (`ga.cs:1433-1573`)
Every rule that needs to cast a buff/heal/recharge/dispel spell — BuffSelf, Every rule that needs to cast a buff/heal/recharge/dispel spell — BuffSelf,
DispelSelf, DispelAllies, RechargeOther, UseHealersHeart, RandomHelper — DispelSelf, DispelAllies, RechargeOther, UseHealersHeart, RandomHelper —
calls through this **one** subroutine (via an overload chain, calls through this **one** subroutine (via an overload chain,
`ga.cs:1417-1447`) instead of switching mode itself. Per call: `ga.cs:1415-1431`) instead of switching mode itself. Per call:
1. **Busy check.** If `Actions.BusyState != 0`, return `false` 1. **Busy check.** If `Actions.BusyState != 0`, return `false`
immediately — `ga.cs:1454-1458`. immediately — `ga.cs:1448-1451`.
2. **Resolve the primary item to wield.** 2. **Resolve the primary item to wield.**
- Start from the explicit override `A_1` (an item id), if the caller - Start from the explicit override `A_1` (an item id), if the caller
passed one, and validate it's owned by the player and currently passed one, and validate it's owned by the player and currently
wieldable (`b(WorldFilter[num])`); if invalid, fall back to `0` and wieldable (`b(WorldFilter[num])`); if invalid, fall back to `0` and
log a warning — `ga.cs:1459-1470`. log a warning — `ga.cs:1452-1461`.
- **Prefer whatever is already wielded**: if auto-select is on (`A_2`) - **Prefer whatever is already wielded**: if auto-select is on (`A_2`)
or no override was given, *and* something is already wielded whose or no override was given, *and* something is already wielded whose
weapon-class already maps to the requested `CombatState` (via weapon-class already maps to the requested `CombatState` (via
`a(ObjectClass)`, `ga.cs:1573-1607`), keep using that item instead of `a(ObjectClass)`, `ga.cs:1573-1607`), keep using that item instead of
switching — `ga.cs:1471-1475`. switching — `ga.cs:1462-1465`.
- Otherwise, call the private `a()` helper (`ga.cs:1398-1411`): scan - Otherwise, call the private `a()` helper (`ga.cs:1398-1411`): scan
`dz.k.j()` (a tracked item-id list — see §0; **its population order `dz.k.j()` (a tracked item-id list — see §0; **its population order
is not established from this file alone**, see §6) for the first is not established from this file alone**, see §6) for the first
owned item classified `fi.b` ("is a wand"); if none, post **"You owned item classified `fi.b` ("is a wand"); if none, post **"You
must add at least one wand to your profile."**, `StopMacro()`, return must add at least one wand to your profile."**, `StopMacro()`, return
`false``ga.cs:1477-1481`. `false``ga.cs:1466-1475` (message at 1471).
3. **Resolve a secondary/held item** the same way (`A_5`), and whether a 3. **Resolve a secondary/held item** the same way (`A_5`), and whether a
spell/element re-selection is needed (`bv.a(...)`, an spell/element re-selection is needed (`bv.a(...)`, an
element/prismatic-compatibility check) — `ga.cs:1482-1499`. element/prismatic-compatibility check) — `ga.cs:1482-1499`.
@ -536,8 +536,8 @@ calls through this **one** subroutine (via an overload chain,
- Once in Peace mode (`r` reset to 0, `ga.cs:1527`): if the target item - Once in Peace mode (`r` reset to 0, `ga.cs:1527`): if the target item
is a wand type (`fi.h`) *and* a secondary must also change, unequip is a wand type (`fi.h`) *and* a secondary must also change, unequip
the current primary first if one is worn, else equip the secondary the current primary first if one is worn, else equip the secondary
`ga.cs:1528-1547`; else if the primary itself needs to change, `ga.cs:1530-1539`; else if the primary itself needs to change,
equip it — `ga.cs:1548-1551`; else if only the spell/element equip it — `ga.cs:1540-1544`; else if only the spell/element
selection needs updating, do that (`bv.a()`) — `ga.cs:1552-1554`. selection needs updating, do that (`bv.a()`) — `ga.cs:1552-1554`.
- Any of the above branches returns `false` for this tick — the wield - Any of the above branches returns `false` for this tick — the wield
step always costs at least one extra tick before it can report ready. step always costs at least one extra tick before it can report ready.
@ -550,7 +550,7 @@ calls through this **one** subroutine (via an overload chain,
cast/use inside their own `if (ga.a(...)) { ... }` branch — see e.g. cast/use inside their own `if (ga.a(...)) { ... }` branch — see e.g.
`c8.cs:114-121`, `af.cs:185-189`, `ba.cs:152-156`. `c8.cs:114-121`, `af.cs:185-189`, `ba.cs:152-156`.
`ga.a(ObjectClass)` (`ga.cs:1573-1607`) is the class→mode map: `ga.a(ObjectClass)` (`ga.cs:1581-1613`) is the class→mode map:
`MeleeWeapon → Melee(2)`, class `9` (missile launcher) `→ Missile(4)`, `MeleeWeapon → Melee(2)`, class `9` (missile launcher) `→ Missile(4)`,
class `31` and everything else `→ Magic(8)` (the default). class `31` and everything else `→ Magic(8)` (the default).
@ -565,12 +565,10 @@ back to the first `fi.b`-classified item found while scanning `dz.k.j()`
verification 2026-09-06): `eq.j()` (`eq.cs:83-94`) walks `m_e` in list verification 2026-09-06): `eq.j()` (`eq.cs:83-94`) walks `m_e` in list
order and emits each distinct item id (skipping `-1` and self); `m_e` is order and emits each distinct item id (skipping `-1` and self); `m_e` is
appended by `eq.a(c)` (`eq.cs:54-57`, a plain `List.Add`), whose only appended by `eq.a(c)` (`eq.cs:54-57`, a plain `List.Add`), whose only
producers are the Items-tab Add buttons — `PluginCore.cs:8422-8434` producers are the Items-tab Add buttons — `PluginCore.cs:8348-8434` (five switch cases)
(`dz.k.a(new eq.c(item, spell))`, one row per element for "Add", a single (`dz.k.a(new eq.c(item, spell))`, one row per element for "Add", a single
`-1`-spell row for "Add (no buffs)") — and the profile loader. So the `-1`-spell row for "Add (no buffs)") — and the profile loader (`da.cs:628`). So the
first wand a player added is the fallback caster. (`ga.cs:1462-1464` is first wand a player added is the fallback caster.
the wielded-first branch; the line numbers `1471-1475` above refer to the
same function's error path.)
### 3.2 (b) Fighting with the rule weapon ### 3.2 (b) Fighting with the rule weapon
@ -652,9 +650,10 @@ carefully:
`fd.cs:103-110`, the `nav minimum distance` referenced in the campaign `fd.cs:103-110`, the `nav minimum distance` referenced in the campaign
brief). brief).
- If that "stop" condition is true **and** `Actions.CombatMode == Peace` - If that "stop" condition is true **and** `Actions.CombatMode == Peace`
(`fd.cs:129`): if `IdlePeaceMode` is on, log **"Warning: Idle peace (`fd.cs:129`): if `IdlePeaceMode` is on (`fd.cs:131`) log **"Warning: Idle peace
selected with low waypoint minimum distance. Will switch to magic selected with low waypoint minimum distance. Will switch to magic
mode."** (`fd.cs:133`), then attempt to force Magic mode via mode."** (`fd.cs:133`) — the warning is the ONLY thing that setting gates —
then, regardless of the setting, attempt to force Magic mode via
`dz.o.a((CombatState)8, 0, true)` (the shared gate, §3.1) — and if that `dz.o.a((CombatState)8, 0, true)` (the shared gate, §3.1) — and if that
fails, cancel the "stop" decision for this tick, i.e. keep walking fails, cancel the "stop" decision for this tick, i.e. keep walking
instead (`fd.cs:135-138`). instead (`fd.cs:135-138`).
@ -687,7 +686,7 @@ Files read: `MossTankPanel.cs` (`OnTick`, `TickAutomaticBuffing`,
VTank's `l` is a declarative list of independent `ILogicRule` objects; one VTank's `l` is a declarative list of independent `ILogicRule` objects; one
pass picks the first whose `ValidNow` is true and marks it `Running`, and pass picks the first whose `ValidNow` is true and marks it `Running`, and
every other rule is explicitly told `Running = false` (§1.2). MossTank's every other rule is explicitly told `Running = false` (§1.2). MossTank's
`OnTick` (`MossTankPanel.cs:3732-3936`) is an imperative chain of ~16 `OnTick` (`MossTankPanel.cs:3732-4022`) is an imperative chain of ~16
named controllers, each called unconditionally every tick with a `canAct` named controllers, each called unconditionally every tick with a `canAct`
argument that is the logical AND of "not owned by any higher controller argument that is the logical AND of "not owned by any higher controller
so far." The net *suppression* behavior is similar (a higher controller so far." The net *suppression* behavior is similar (a higher controller
@ -732,7 +731,7 @@ observably different: retail VTank always buffs allies with the weakest
version of Tier-I self-buffs, cycling randomly; MossTank always buffs version of Tier-I self-buffs, cycling randomly; MossTank always buffs
with the strongest known tier, cycling in a fixed order. The 18 m range with the strongest known tier, cycling in a fixed order. The 18 m range
constant (VTank's hardcoded `0.075` landblock units) is correctly ported constant (VTank's hardcoded `0.075` landblock units) is correctly ported
(`< 18d`, line 4071, with an explicit comment tying it to the conversion). (`< 18d`, line 4071; no comment ties it to the conversion).
### 5.4 BuffCasterPreparer vs. `ga.a(...)`: same shape, one real ### 5.4 BuffCasterPreparer vs. `ga.a(...)`: same shape, one real
difference difference
@ -769,11 +768,12 @@ VTank re-checks self-vital recharge at two separate priority tiers
(`cr` "Recharge-Norm-*" before Attack, "Recharge-NoTarg-*" after target- (`cr` "Recharge-Norm-*" before Attack, "Recharge-NoTarg-*" after target-
approach fails, §2.2 rows 4 and 59) — two independent settings, gated approach fails, §2.2 rows 4 and 59) — two independent settings, gated
purely by *list position* relative to Attack. MossTank's `VitalPlan. purely by *list position* relative to Attack. MossTank's `VitalPlan.
Threshold` (`VitalPlan.cs:79-88`) keeps both `Normal*` and `NoTarget*` Threshold` (`VitalPlan.cs:79-91`) keeps both `Normal*` and `NoTarget*`
settings but combines them as `Math.Max(Normal, NoTarget)` regardless of settings and evaluates `noTarget ? Math.Max(Normal, NoTarget) : Normal`
whether a target is present, then only the `noTarget` bool with a target present the NoTarget setting is ignored entirely, and only
(`VitalRechargeController.Tick`'s third parameter, in the no-target branch does the Max apply (the `noTarget` bool is
`VitalRecharge.cs:887, 933-939`) selects which pair applies. This `VitalRechargeController.Tick`'s third parameter, `VitalRecharge.cs:887,
933-939`). This
converges on similar behavior in the common case (both thresholds usually converges on similar behavior in the common case (both thresholds usually
agree in intent — top off more eagerly while idle) but is not a literal agree in intent — top off more eagerly while idle) but is not a literal
port: VTank's two rules can disagree in *which* vitals they check (a port: VTank's two rules can disagree in *which* vitals they check (a
@ -848,9 +848,9 @@ fight.
likely covered by a combat-math KB doc. likely covered by a combat-math KB doc.
- **`gj`/`gs` (the two Decal state trackers that drive `SchedulePoke`'s - **`gj`/`gs` (the two Decal state trackers that drive `SchedulePoke`'s
fast-wake path)** were read only enough to confirm what they represent fast-wake path)** were read only enough to confirm what they represent
in shape (spell-cast completion and attack/missile completion, in shape (resolved by the citation pass: `gj` = SpellCaster, `gs` =
`gj.cs`/`gs.cs` headers); their own internal state machines were not WandCaster, both cast-completion trackers); their own internal state
traced. machines were not traced.
- **Meta FSM** (`dz.at.h()`, `a7.e()`, `cLogic.cs:191-196`) is explicitly - **Meta FSM** (`dz.at.h()`, `a7.e()`, `cLogic.cs:191-196`) is explicitly
out of scope — it is evaluated once per pass alongside the main rule out of scope — it is evaluated once per pass alongside the main rule
list but is its own subsystem (VTank's expression-language meta layer), list but is its own subsystem (VTank's expression-language meta layer),