diff --git a/docs/research/vtank-kb/01-settings-and-profiles.md b/docs/research/vtank-kb/01-settings-and-profiles.md index 21eb4780f..229da1248 100644 --- a/docs/research/vtank-kb/01-settings-and-profiles.md +++ b/docs/research/vtank-kb/01-settings-and-profiles.md @@ -35,7 +35,12 @@ gets serialized to a `.usd` file. The same database format is reused for `refs/vtank/decompiled/bd.cs:422-426`, used when a setting's *value* is a nested table) and a length-prefixed raw-text blob class (tag `"ba"`, `refs/vtank/decompiled/f6.cs:9-30`, `int` length line then that many raw - characters — not lines — allowing embedded newlines). + characters — not lines — allowing embedded newlines). If the tag is unknown + to the registry, `y.a(text)` returns null and `gy.a` consumes **exactly one + line** (the tag) and reads no value line (`gy.cs:50-55`) — the parse + silently desynchronizes from that point rather than erroring. A `.usd` + writer must never emit a tag the reader does not register (citation pass + 2026-09-06). - **`cw`** (`refs/vtank/decompiled/cw.cs:9-40`) is one row: a fixed-size list of `gy` cells, one per column of its owning table. - **`bd`** (`refs/vtank/decompiled/bd.cs`) is one table. Its text format @@ -177,8 +182,8 @@ is currently selected. | 56 | `RebuffTimeRemainingSeconds` | int | 300 | Buffing | Advanced Options list only (`lOptionList`, filter category Buffing) | `refs/vtank/decompiled/gw.cs:14 [j]` | | 57 | `RefillWornMana` | bool | True | Misc | Advanced Options list only (`lOptionList`, filter category Misc) | `refs/vtank/decompiled/ev.cs:108 [k]` | | 58 | `RefillWornMana-Item-ManaPercent` | int | 33 | Misc | Advanced Options list only (`lOptionList`, filter category Misc) | `refs/vtank/decompiled/ev.cs:113 [j]` | -| 59 | `BuffProfile-Prots` | string | "ALFCBPS" | Buffing | Advanced Options list only (`lOptionList`, filter category Buffing) | `refs/vtank/decompiled/eq.cs:161 [g]` | -| 60 | `BuffProfile-Banes` | string | "ALFCBPS" | Buffing | Advanced Options list only (`lOptionList`, filter category Buffing) | `refs/vtank/decompiled/eq.cs:248 [g]` | +| 59 | `BuffProfile-Prots` | string | "ALFCBPS" | Buffing | **Not exposed** — `tString` has no case in the Advanced Options list switch (`db.cs:132-166`) and the click handler no-ops for it (`db.cs:201-203`); no control binds it and no code writes it. Settable only via `/vt opt set`, read only when `BuffProfile_Prots == 1` (Custom). | `refs/vtank/decompiled/eq.cs:161 [g]` | +| 60 | `BuffProfile-Banes` | string | "ALFCBPS" | Buffing | **Not exposed** — `tString` has no Advanced Options case (`db.cs:132-166`, `:201-203`); `/vt opt set` only, read only when `BuffProfile_Banes == 1` (Custom). | `refs/vtank/decompiled/eq.cs:248 [g]` | | 61 | `BuffProfile_Prots` | enum | 2 | Buffing | Advanced Options list only (`lOptionList`, filter category Buffing) Enum: 1=Custom, 2=All, 3=None, 4=B, 5=BPS, 6=BPSA, 7=ALFC, 8=BPSAC | `refs/vtank/decompiled/eq.cs:160 [f]`; `refs/vtank/decompiled/eq.cs:161 [b]` | | 62 | `BuffProfile_Banes` | enum | 2 | Buffing | Advanced Options list only (`lOptionList`, filter category Buffing) Enum: 1=Custom, 2=All, 3=None, 4=B, 5=BPS, 6=BPSA, 7=BPSAC, 8=ALFC | `refs/vtank/decompiled/eq.cs:247 [f]`; `refs/vtank/decompiled/eq.cs:248 [b]` | | 63 | `DebuffEachFirst` | enum | 1 | MeleeCombat\|SpellCombat | Advanced Options list only (`lOptionList`, filter category MeleeCombat\|SpellCombat) Enum: 1=One, 2=Priority, 3=All | `refs/vtank/decompiled/dz.cs:712 [c]`; `refs/vtank/decompiled/dz.cs:712 [c]`; `refs/vtank/decompiled/dz.cs:713 [c]` | @@ -230,7 +235,7 @@ is currently selected. | 109 | `BuffCastRecast_Seconds` | int | 30 | Buffing | Advanced Options list only (`lOptionList`, filter category Buffing) | `refs/vtank/decompiled/fz.cs:83 [j]`; `refs/vtank/decompiled/gw.cs:17 [j]` | | 110 | `BuffCastRecastReset_Seconds` | int | 30 | Buffing | Advanced Options list only (`lOptionList`, filter category Buffing) | `refs/vtank/decompiled/fz.cs:118 [j]` | | 111 | `EnableMeta` | bool | False | Misc | `c1EnableMeta` (Checkbox, tab "Options"): "Enable Meta" | `refs/vtank/decompiled/a7.cs:91 [k]`; `refs/vtank/decompiled/ao.cs:79 [b]`; `refs/vtank/decompiled/cp.cs:148 [b]` | -| 112 | `BlacklistedSpellComps` | string | "" | Recharge\|SpellCombat\|Buffing | Advanced Options list only (`lOptionList`, filter category Recharge\|SpellCombat\|Buffing) | `refs/vtank/decompiled/uTank2/MySpell.cs:403 [g]`; `refs/vtank/decompiled/uTank2/PluginCore.cs:7734 [g]`; `refs/vtank/decompiled/uTank2/PluginCore.cs:7749 [b]` | +| 112 | `BlacklistedSpellComps` | string | "" | Recharge\|SpellCombat\|Buffing | **Not exposed** by the Advanced Options list (`tString` has no case, `db.cs:132-166`); settable via `/vt opt set` and by `PluginCore.cs:7749 [b]`. | `refs/vtank/decompiled/uTank2/MySpell.cs:403 [g]`; `refs/vtank/decompiled/uTank2/PluginCore.cs:7734 [g]`; `refs/vtank/decompiled/uTank2/PluginCore.cs:7749 [b]` | | 113 | `DropToPeaceModeRetryCount` | int | 34 | Misc\|MeleeCombat\|SpellCombat | Advanced Options list only (`lOptionList`, filter category Misc\|MeleeCombat\|SpellCombat) | `refs/vtank/decompiled/ga.cs:1511 [j]` | | 114 | `FollowAroundCorners` | bool | True | Navigation | Advanced Options list only (`lOptionList`, filter category Navigation) | `refs/vtank/decompiled/gl.cs:83 [k]`; `refs/vtank/decompiled/gl.cs:121 [k]`; `refs/vtank/decompiled/gl.cs:142 [k]` | | 115 | `BlacklistCorpseOpenAttemptCount` | int | 30 | Misc\|Looting | Advanced Options list only (`lOptionList`, filter category Misc\|Looting) | `refs/vtank/decompiled/fo.cs:339 [j]` | @@ -337,16 +342,17 @@ determine"). **Profile listing and selection UI** (`refs/vtank/decompiled/uTank2/PluginCore.cs`): four near-identical list-builder methods populate the four combo boxes — -`a0()` (settings, `cmbSettingsSet`, lines 7057-7115) always seeds +`a0()` (settings, `cmbSettingsSet`, lines 7054-7128) always seeds `"[Default]"` and `"[By char]"` first, then every non-`--` `*.usd` file (filtered to only the currently-selected one when the "Mine only" checkbox -`cSettingsShowAll`/field `a9` is checked, `7020-7024`) plus every +`cSettingsShowAll`/field `a9` is checked, `7072-7078`; the same test appears in the +display-name helper `h()` at `7020-7024`) plus every `dw`-matching `"[Char] "`-labeled file; `aa()` (loot, `cmbLootSet`, -lines 7127-7154) seeds only `"[None]"` and lists **every** non-`--` file +lines 7130-7166) seeds only `"[None]"` and lists **every** non-`--` file across all of `dz.ah.a()`'s loot-profile extensions with no "mine only" filter and no `"[By char]"` option; `l()` (nav, `cmbNavSet`, lines -7156-7185) seeds `"[None]"`/`"[By char]"` and filters out both `--` and -`~~`; `ac()` (meta, `cmbMetaSet`, lines 7187+) seeds `"[None]"`/`"[By char]"` +7168-7207) seeds `"[None]"`/`"[By char]"` and filters out both `--` and +`~~`; `ac()` (meta, `cmbMetaSet`, lines 7209-7248) seeds `"[None]"`/`"[By char]"` and filters only `--`. **Loot profiles are structurally the odd one out**: no auto per-character file, no "mine only" filtering, and (per Classic Looter's own file format — out of scope for this doc, see doc 05) a @@ -373,7 +379,7 @@ still tolerates. `refs/vtank/decompiled/da.cs:259-367` — the settings-profile loader): after loading a `.usd` file's `Settings`/`MyMonsters`/etc. tables, VTank checks the **column count** of the loaded `MyMonsters` table -(`this.c["MyMonsters"].d()`) against ten hardcoded thresholds +(`this.c["MyMonsters"].d()`) against eleven hardcoded thresholds (`da.cs:280-323`) and appends a new column with a hardcoded default value for every column added by a newer build than the one that saved the file (e.g. `if (...).d() <= 16) { ...a("Corrosion", gy.a(false)); }` at @@ -386,15 +392,24 @@ method also adds three whole tables outright if missing addition uses a row-count-like column-count threshold; neither uses an explicit schema-version field.** If the `.usd` file fails to parse at all, `da.k()` falls back to the **template** resource and immediately overwrites -the file on disk with defaults (`da.cs:347-358`) — a corrupt/foreign `.usd` +the file on disk with defaults (`da.cs:347-352`; `da.cs:354-359` is the +separate file-missing branch with the identical body) — a corrupt/foreign `.usd` is silently replaced, not preserved or backed up. -**`defaultsettings.usd` vs `defaultsettingstemplate.usd`**: both share the -identical 10-table/137-setting schema; `defaultsettings.usd` (2270 lines) -additionally seeds real starter content in the grid tables (`GemFoodItems` -has 2 rows: "Asheron's Benediction"→spell 3810, "Blackmoor's Favor"→spell -3811; `MyMonsters` has 1 row, the `` monster rule) while -`defaultsettingstemplate.usd` (141 lines) ships the same tables **empty**. +**`defaultsettings.usd` vs `defaultsettingstemplate.usd`**: they are NOT the +same schema (corrected by the citation pass 2026-09-06). `defaultsettings.usd` +(2270 lines) carries **10 tables**, including `Settings` (4 columns × 137 +rows), `SettingsCategories` (136 rows) and `SettingsEnumInfo` (33 rows). +`defaultsettingstemplate.usd` (141 lines) carries only **8 tables**: it drops +`SettingsCategories` and `SettingsEnumInfo` entirely, and its `Settings` table +has just **2 columns (`Setting`, `Value`) and zero rows**. The grid tables are +identical in both files — `GemFoodItems` has the same 2 rows ("Asheron's +Benediction"→3810, "Blackmoor's Favor"→3811) and `MyMonsters` the same +single 21-column `` row; the whole 2129-line delta is the three +settings tables. Consequence: a template-seeded profile (`da.f()`) starts +with an **empty** `Settings` table, so every scalar read falls through +`f3.e()`'s defaults fallback to `da.b` — an independent confirmation of the +fallback chain in section 1. `defaultsettings.usd` is the built-in fallback database (`da.b`, never written to disk on its own); `defaultsettingstemplate.usd` is what a brand new (or unparseable) profile is seeded from (`da.f()`, @@ -406,8 +421,10 @@ grammar, but no `"--"` prefix and not user-selectable — always exactly (`refs/vtank/decompiled/dm.cs:391`, class `dm`). Live inspection of `+Horan_sawato.ast` (65 bytes) shows one table, `Spells`, four columns (`SpellID`, `EndTime`, `Target`, `CastTime`), zero rows in the sample -inspected. `dm.g()` (`dm.cs:389-399`) falls back to seeding from the -embedded `defaultitemagedb.ugd` resource if the file is missing or +inspected. `dm.g()` (`dm.cs:389-414`) reseeds from the +embedded `defaultitemagedb.ugd` resource on two triggers — clock-rollback +detection (`dm.cs:396-401`) and the catch for a missing/corrupt file +(`dm.cs:408-413`) — i.e. if the file is missing or corrupt. This looks like a per-character cast/assist-tracking cache (tracking spell durations placed on other players) rather than a settings profile; its exact runtime semantics were not traced further as out of @@ -425,9 +442,9 @@ chat-command dispatcher — the surrounding `if` chain is a long | `/vt opt` (no args) | `opt` | prints usage | `"Usage: /vt opt [list/get/set/setinall]"` (`:5450`) | | `/vt opt list` | `opt list` | prints `"Available options: (" + + ")"` then every `Settings` row's name from the **built-in defaults** table (`dz.m.b["Settings"]`, not the loaded profile), four per line, `" "`-indented (`:5518-5538`) | `"Usage: /vt opt list"` if extra args (`:5514`) | | `/vt opt get ` | `opt get X` | prints `"Option " + name + " = " + f3.e(name)"` via the raw `gy.ToString()` (`:5539-5556`) | `"Option get: Invalid option specified."` if the name isn't found (`:5553`); `"Usage: /vt opt get [option name]"` for wrong arg count (`:5543`) | -| `/vt opt set ` | `opt set X V` | looks up the name's declared type in the **built-in defaults** table, converts `V` to that CLR type (`double`/`int`/`float`/`string`/`bool` — note **no case for a `tCustom` value**, so `RechargeHandlerSet` cannot be set this way), then calls `f3.b(name, gy)` (persists + refreshes) (`:5560-5602`) | `"Option set: Invalid option specified."` if name not found; `"Option set: Invalid value specified. Proper type of " + name + " is " + type + "."` on conversion failure or unrecognized type; `"Usage: /vt opt set [option name] [option value]"` for wrong arg count | -| `/vt opt setinall ` | `opt setinall X V` | same type resolution as `set`, but writes through `global::bk.a(name, gy)` instead of `f3.b` (`:5457-5498`) — `bk` was not traced further in this pass; likely writes to every loaded profile database rather than just the current one, but that inference is **not confirmed** from code read so far | `"Usage: /vt opt setinall [option name] [option value]"` if arg count != 4 (`:5462`) | -| *(default)* | any other `opt ` | | `"Usage: /vt opt [list/get/set]"` (`:5606`) — note this usage string omits `setinall` even though it is a real, dispatched subcommand | +| `/vt opt set ` | `opt set X V` | looks up the name's declared type in the **built-in defaults** table, converts `V` to that CLR type (`double`/`int`/`float`/`string`/`bool` — note **no case for a `tCustom` value**, so `RechargeHandlerSet` cannot be set this way), then calls `f3.b(name, gy)` (persists + refreshes) (`:5566-5615`; type ladder `:5588-5607`) | `"Option set: Invalid option specified."` if name not found; `"Option set: Invalid value specified. Proper type of " + name + " is " + type + "."` on conversion failure or unrecognized type; `"Usage: /vt opt set [option name] [option value]"` for wrong arg count | +| `/vt opt setinall ` | `opt setinall X V` | same type resolution as `set`, but writes through `global::bk.a(name, gy)` instead of `f3.b` (case `:5457-5511`, call at `:5504`). `bk.a` (`bk.cs:6-35`) enumerates **every `*.usd` file in `PluginCore.dq`** with no `--` filtering (other characters' profiles included): the currently-loaded file (`dz.m.m`) goes through `f3.b`, every other file is loaded into a fresh `y`, has its `Settings` row inserted-or-overwritten, and is written straight back to disk (`y2.b(text)`); it reports `"Done saving setting X to all profiles. (Changed N profiles)"` and writes the canonical name from the defaults row (`gy.b(cw3[0])`), not the typed casing. Quirk: the arg-count guard at `:5459-5462` prints usage but does not `break` — it falls through and still attempts the lookup | `"Usage: /vt opt setinall [option name] [option value]"` if arg count != 4 (`:5462`) | +| *(default)* | any other `opt ` | | `"Usage: /vt opt [list/get/set]"` (`:5617`) — note this usage string omits `setinall` even though it is a real, dispatched subcommand | The exact same `/vt` ladder also contains many non-settings debug commands (`dumptracker`, `deletemonster`, `getdb`, `obtest`, `nav save/load`, …, @@ -496,17 +513,22 @@ That said, real gaps and divergences were found: default `RechargeHandlerSet`, including its stance- and current-percentage-dependent order"). A user who customized their RechargeHandlerSet table in real VTank has no way to bring that - customization into MossTank today, and `_vitalSettings.RechargeHandlerSet` - does not appear to be read back by `VitalRechargePlanner` at all (not - fully confirmed — would need a full read of `VitalRecharge.cs`, which - this doc's scope did not require). + customization into MossTank today; `_vitalSettings.RechargeHandlerSet` IS + read back (`VitalRecharge.cs:59-63` → `Handlers(...)` → `TryParseHandlerSet`, + `VitalRecharge.cs:308-340`): the sentinel default `"RechargeHandlerSet"` + (`VitalPlan.cs:48`) and blank mean "no override" and fall through to the + hardcoded table, while a non-sentinel string parses as a `key=value` + segmented form keyed by `{stance}-{vital}-{band}` (`VitalRecharge.cs:305`) + with a `-normal` fallback. So a customization channel exists — it is just + not VTank's 5-column table (citation pass 2026-09-06; slice 1 replaces it + with the parsed table). 4. **Buff classification model is architecturally different, not just incomplete.** VTank's `BuffProfile_Prots`/`BuffProfile_Banes` are an 8-way named enum (`Custom`/`All`/`None`/`B`/`BPS`/`BPSA`/`ALFC`/`BPSAC`, section 2 rows 61-62) whose letters index into **fixed, hardcoded lists of specific spell families** (not decompiled further in this pass — doc 04's scope). MossTank instead classifies buffs at runtime from each - spell's own English description text (`BuffProfile.cs:1-70`, matching + spell's own English description text (`BuffProfile.cs:6-140`, incl. `Unknown = 0`, matching phrases like "Increases the caster's ... skill by"), producing a `BuffTargetKind` (`Skill`/`Attribute`/`Protection`/`Aura`/`Bane`/ `Regeneration`/`Other`) and exposing coarser boolean toggles @@ -529,7 +551,7 @@ That said, real gaps and divergences were found: existing `--Name_Server*.usd` files from `C:\Games\VirindiPlugins\VirindiTank\`. 6. **Minor naming difference, not a functional gap**: VTank's setting is `BlacklistedSpellComps`; `CombatSettings.BlacklistedSpellComponents` - (`CombatSettings.cs:106`) and `BuffSettings.BlacklistedSpellComponents` + (`CombatSettings.cs:113`) and `BuffSettings.BlacklistedSpellComponents` (`BuffPlan.cs:26`) spell the field out in full. `SetMetaOption`'s case label is still the exact VTank string (`"blacklistedspellcomps"`, `MossTankPanel.cs:3305`), so external-facing compatibility (`/vt`-style @@ -544,16 +566,12 @@ programmatic diff) — the gaps here are all about **persistence fidelity**, ## 6. Could not determine -- **The exact runtime semantics of `global::bk.a(name, gy)`** used by - `/vt opt setinall` (`uTank2/PluginCore.cs:5488`) — presumably "set this - value in every loaded profile database," inferred from the command name - and the fact it bypasses `f3.b`/`f3.a`, but `bk.cs` was not read in this - pass to confirm. -- **The `"~~"` filename prefix** filtered out of the nav-profile list - alongside `"--"` (`uTank2/PluginCore.cs:7182`) — no assignment or - producer of a `"~~"`-prefixed file was found in the code paths read for - this document; possibly an auto-backup or in-progress-save marker used - elsewhere in the nav system (doc 06's scope). +- **The `"~~"` filename prefix — RESOLVED as far as VTank goes** (citation + pass 2026-09-06): `uTank2/PluginCore.cs:7182` filters it but VTank never + produces it — `grep -rn '~~'` over the decompile returns only that line + and an unrelated string delimiter (`hw.cs:127`), and no `~~*` file exists in + the live profile directory. It is a filter for files written by a different + Virindi assembly, not by `utank2-i.dll`. - **Whether `RechargeHandlerSet` has *any* live UI editor at all.** No control in `mainView.xml` or `AdvancedOptionsView.xml` matches (no `lstRecharge`-style list), and both `/vt opt set` and the generic