Commit graph

2696 commits

Author SHA1 Message Date
Erik
41fc1d88d1 docs(vt): slice 7 ledger — S7.4-S7.6 landed, fresh live screenshots
Recaptured Items/Consumables/Buffs/Route/Meta screenshots (stale since
before this sub-slice) plus the new buff picker popup against a live
local ACE, isolated ACDREAM_CONFIG_DIR/ACDREAM_DATA_DIR (fresh authored
window positions, no stale-persisted-layout override), and an
ACDREAM_UI_PROBE_SCRIPT route through the five changed tabs. All six
confirm plain controls, no overlapping captions, correct grid rendering,
and real DAT move-icon art. Records the three S7.4-S7.6 commit SHAs and
their deviations in the ledger.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 10:51:55 +02:00
Erik
66b070def4 docs(vt): slice 7 ledger — fix round A landed, four commits recorded
Records the merge + three fix-round-A commits (grid scaling/Profiles
cleanup/popup split, the App.csproj plugin-copy-target bug, the
StartVisible visibility fix + duplicate-title cleanup), the real
DAT-font measurement that superseded the 07:55 lead's row-pitch theory,
and the carried CopyTo-naming deviation for the next reviewer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 09:50:54 +02:00
Erik
78b42a519e fix(vtank): popup panels actually show; drop duplicate popup titles; fresh screenshots
Live-verified two real defects the previous commit's design didn't catch
without a running client:

1. Both new popups (mosstank-advanced.xml, mosstank-loot-editor.xml) were
   registered with StartVisible=false, on the theory that the markup's own
   visible="{AdvancedOptionsVisible}"/"{LootEditorVisible}" binding would be
   the sole gate. It is NOT: RetailUiRuntime.MountPlugins wraps every
   plugin window's markup availability in a PluginWindowVisibilityController
   that ANDs it with a SEPARATE "requested visible" axis seeded from
   StartVisible, flipped back to true only by OnShown() — the shelf's
   un-minimize click. A ShowInSidePanel=false window has no shelf entry to
   click, so StartVisible=false left "requested visible" permanently false
   regardless of the markup binding, and both popups never rendered no
   matter how many times the checkbox was toggled (confirmed live: checkbox
   showed checked/green, no window appeared). Fix: StartVisible=true, which
   keeps "requested visible" true from construction (OnHidden only ever
   latches it back to false on a genuine minimize, explicitly no-opping
   when the hide was caused by availability itself going false — exactly
   what closing via "Back" does), leaving the markup binding as the sole
   effective gate, same as the main panel's own always-true
   ShowInSidePanel=true default.

2. Once visible, both popups showed garbled overlapping text at their top
   edge ("AdvancedOptionsced Options — complete VTank settings table",
   "Loot profileRules(first match wins)"): each file's own in-content
   descriptive label duplicated the window's OS-level title bar
   (MarkupDocument.Build auto-renders <panel title="..."> at a fixed (8,4)
   — the exact corner the in-content label used to own back when these
   were in-panel groups with no title bar of their own). Fix: dropped both
   redundant labels; the window's own title ("Advanced Options" caption
   duplicated as "MossTank Advanced Options"/"MossTank Loot Editor") is now
   the only thing there.

Also repositioned both popups' default (x,y) from the placeholder
(440,60) — which overlapped the main panel — to (253,405)/(660,405),
below the main panel, side by side, both fitting within a 720-tall client.

Screenshots recaptured end to end against a live local ACE
(127.0.0.1:9000, testaccount/+Acdream) with the rebuilt Release exe from
this worktree, using an isolated ACDREAM_CONFIG_DIR/ACDREAM_DATA_DIR (no
stale persisted window layout from earlier probe runs — RetailWindowLayoutPersistence
keys plugin windows by name with authoredGeometryRevision=0, so a
previously-opened window's persisted x/y wins over any new authored
default forever; using a scratch config directory sidesteps that rather
than editing the real %APPDATA%\acdream\settings.json). tab-options.png,
tab-profiles.png, tab-vitals.png, and tab-monsters.png confirm the fix
round A grid-scaling and Profiles-cleanup commit's fixes hold on a live
client (no overlap, no gold buttons, no stacked New buttons, no dead
window space); popup-advanced.png and popup-loot-editor.png confirm both
popups now open as genuinely separate windows with clean single titles and
working move-up/move-down DAT icons (no blank button slots).

MossTank suite still 654/654; App markup/plugin/menu filter still
237/237 (no test changes in this commit — the earlier commit's
SecondaryPopupPanelsFitTheirOwnBoundsAndEveryBindingResolves already
covers both files' structure and bindings, and neither the visibility
seeding fix nor the label removal changes what those pins check).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 09:49:32 +02:00
Erik
045cd0a195 merge(vt): plain <menu> style from latest-main-sync into slice7 panel work
Brings cfa703065's owner-driven fix (plugin <menu> renders VTank's plain
flat combo by default; style="retail" opts back into the gold pushbutton
art) into the S7.1-S7.3 window/tabs work so the fix round below can build
on both. Resolved conflicts: the ledger keeps both worktrees' entries in
chronological order (07:55/08:05 owner-read entries appended after this
worktree's own S7.1/S7.2/S7.3 entries); plugin-ui-markup.md's <menu> row
lists both new attributes (scroll, style); MarkupDocumentTests.cs keeps
both new test blocks (slider min/max + menu scroll from this worktree,
menu style from latest-main-sync).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 08:23:29 +02:00
Erik
47a1fe555c docs(vt): slice 7 ledger — Monsters landed; fix round A dispatched
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 08:20:10 +02:00
Erik
c3b4f78624 docs(vt): slice 7 ledger — S7.3 Monsters grid landed
Records commit 57ced0aff (the VTank 23-column Monsters grid) in the
slice-7 ledger, including the Weapon/Offhand roster-cycle deviation and
the full-suite pass counts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 08:18:37 +02:00
Erik
d8846d7a2d docs(vt): document <menu style> in plugin-ui-markup.md
Adds style to the <menu> attribute row and one sentence explaining why
plain is now the default (owner report: retail's gold pushbutton art
read as an out-of-place button next to a plugin's own dark list boxes)
and what style="retail" opts back into.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 08:15:32 +02:00
Erik
1637bae31d docs(vt): slice 7 ledger — owner's live look: overlaps and the gold dropdowns must go
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 07:56:49 +02:00
Erik
d1fe52368d docs(vt): slice 7 — live screenshots of S7.1/S7.2 (cropped) and the lead's read for the fix round
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 07:54:27 +02:00
Erik
9ec145173e docs(vt): slice 7 ledger — ACE was up; the TCP probe was wrong (ACE is UDP)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 07:37:21 +02:00
Erik
8681a25e6c docs(vt): slice 7 ledger — S7.1/S7.2 landed, popup-height note, screenshots owed (ACE down)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 07:28:59 +02:00
Erik
89a49836ca docs(vt): slice 7 ledger — S7.1/S7.2 landed, screenshots skipped (ACE down)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 07:27:03 +02:00
Erik
e72a64a311 feat(ui-markup): slice 7 step 1 — <slider min max> and <menu scroll>
Campaign VT slice 7 needs to transcribe VTank's own Vitals sliders
(minimum=0/maximum=100) and its long Profiles/Route named-item combos
control-for-control (docs/research/vtank-kb/08-ui-views.md §3's two
markup gaps). acdream's <slider> was hardwired to a 0.0-1.0 scalar and
<menu> always wrapped overflow into extra columns instead of VVS's
single scrolling column.

<slider min max>: optional literal attributes declaring the range the
bound value/onchange speak in (e.g. min="0" max="100" for a percent),
while UiScrollbar itself keeps its existing 0.0-1.0 internal math
untouched — MarkupDocument rescales at the binding boundary. Omitting
both (every pre-existing <slider>) keeps the exact historical identity
range.

<menu scroll="true">: wires UiMenu.Scrollable plus the same
track/thumb/up/down chrome sprites ConfigOptionsPageController and
VendorUiController already apply to their own Scrollable menus,
previously only reachable from C#. Omitting scroll keeps the
historical column-wrapping default.

Both are additive — no existing <slider>/<menu> markup changes
behavior. New pins in MarkupDocumentTests.cs shown to fail against the
prior MarkupDocument.cs (5 failures: Build_SliderWithNoMinMax_*,
Build_SliderWithMinMax_*, Slider_MinMax_Draws*,
Build_MenuWithScrollAttribute_*, Menu_Scroll_Draws*) before this
change, all green after.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 07:07:42 +02:00
Erik
fbdbbc7f90 docs(vt): slice 1c review D1 — the profile-dir override row and AD-122 say the first load moves flat .af files
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 06:54:00 +02:00
Erik
7cfe57827c merge(vt): slice 1c — metas/ and navs/ folders, no nav_ prefix (review-closed)
Owner decision 2026-09-07. Folder-scoped listing, one-time flat-file
migration, content sanity on load. Narrow Opus review: MERGE-READY.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 06:53:43 +02:00
Erik
615a401994 docs(vt): slice 7 visual gate script (draft, filled from KB 08)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 06:46:39 +02:00
Erik
812a533b83 feat(vtank): slice 1c step 1 — two-folder metas/navs layout, no nav_ marker
Owner decision 2026-09-07: Meta and Nav profiles both use metaf .af and are
told apart by living in two dedicated VtankProfiles subfolders (metas/,
navs/) instead of the flat-directory nav_/--nav_ marker scheme from slice 1
Part A, which was only ever how the owner happened to name files in their
own metas repo. VtankProfileDirectory.ListMetaProfiles/ListNavigationProfiles
now enumerate metas/ and navs/ respectively via a new folder-scoped
EnumerateFolderFileNames helper; the NavMarker constant and the marker
overload of AutoCharacterFileName are deleted. MossTankMetaProfileStore and
MossTankRouteProfileStore build every real storage key with their folder
prefix (CurrentFileName, Select, Create, TryImportLegacy, the legacy-roster
sweep) and strip it back off for display (StripAf/Strip). The .cdf's Nav/Meta
lines (4-5) now carry the folder-relative key ("metas/Name.af",
"navs/Name.af"); AD-122 and the ACDREAM_VTANK_PROFILE_DIR launch-option row
are updated to describe this.

Mutation demonstrated: reverting VtankProfileDirectory.cs,
MossTankMetaProfileStore.cs, and MossTankRouteProfileStore.cs to HEAD~ (the
flat nav_-marker layout) while keeping the updated tests reproduces 8 test
failures (KeyNotFoundException / Assert.True(false) against the new
"metas/…"/"navs/…" keys the tests now expect, e.g.
MetaSaveAcceptsAnAfSuffixedNameWithoutDoublingIt,
MetaAndRouteProfilesWithTheSameNameDoNotCollide,
NavCommandsImportLegacyAndExportAf, MetaCommandsImportLegacyAndExportAf,
MetaRosterSweepConvertsEveryNamedLegacyProfileOnce,
MetaStoreLeavesLegacyJsonUntouchedWhenAfCounterpartExists,
MetaStoreRefusesToSaveADisabledRuleAndKeepsThePriorAfContent,
NavSaveAcceptsAnAfSuffixedNameWithoutDoublingIt) — confirmed by running the
suite immediately after the production-code edit, before the test-file
updates landed. All 625 tests pass after both sides of the change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 06:32:56 +02:00
Erik
a427d7db05 docs: plan the headless console (interactive chat/command CLI for the bot host)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 06:32:47 +02:00
Erik
43abc32edf docs(vt): owner direction — one plugin, two hosts; slice 2 gains headless autostart + a headless MossTank smoke test
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 06:29:46 +02:00
Erik
074762dd36 docs(vt): slice 7 plan — the nine tabs at VTank's geometry on the column markup; pulled forward after slice 1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 06:23:28 +02:00
Erik
aafa66c821 docs(vt): owner decision — metas/ and navs/ subfolders, no nav_ prefix
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 06:20:16 +02:00
Erik
8080a99eef docs(vt): owner confirmed — binary .met/.nav readers stay for convert-on-import only
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 06:16:54 +02:00
Erik
55e0f12ae6 docs(vt): slice 1 closeout — Part A merged at f680bf234; owner items; next = slice 7
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 02:26:46 +02:00
Erik
b71bbe82f4 docs: file #488 — .utl expression block length prefix vs newline normalization (latent, from the slice-1 re-check)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 02:26:36 +02:00
Erik
f680bf234a merge(vt): slice 1 Part A — VTank .usd/.utl drop-in and metaf .af for metas and routes (review-closed)
Campaign VT slice 1 Part A: the .usd document model + 137-setting
serializer with declared type tags and exact compare, metaf .af reader/
writer for metas and nav routes with real byte identity against the
owner's fixtures, .utl gate fixes, the VtankProfiles host storage
(ACDREAM_VTANK_PROFILE_DIR), and the cutover of all four profile stores
to real VTank files with one-time JSON migration. Two Opus lenses, three
fix rounds, two narrow re-reviews, final re-check: MERGE-READY.
Contract-doc ledger conflict resolved by keeping the campaign branch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 02:25:28 +02:00
Erik
f74f7c3787 docs(vt): slice-1 ledger — Part A round 3 complete, final re-check dispatched
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 02:11:06 +02:00
Erik
f58e997b19 fix(vt): round 3 item 12 — cleanup pass (stale refs, silent swallows, comments)
Five small fixes bundled per the round's cleanup item:
- VtankNavRouteSerializer.cs's doc comment cited a "WriteBinaryNavBlob"
  method that no longer exists anywhere in the codebase (MetaEngine's
  embedded-navigation contract moved to the typed MetaAction.EmbeddedRoute
  NavigationSettings, saved/loaded through MetafSerializer.SaveNav/
  TryLoadNav, back at round 2 step B) — corrected to name the real
  mechanism.
- MossTankCommands.cs:274's comment referenced an "exports/nav/" mirror
  directory that stopped existing when route profiles cut over to writing
  their real .af file directly (round 2 steps 2-3) — corrected.
- docs/research/vtank-kb/07-meta-and-expressions.md section 5.2 row 6
  described the pre-cutover "MossTankMetaProfileStore.WriteLegacyExport
  convenience mirror" design; .af is now the SOLE authoritative Meta
  store, so a disabled rule's save refusal now blocks the profile itself
  — the row now says a disabled rule makes the profile file genuinely
  unsaveable, not that a mirror goes stale.
- The two bare `catch (FormatException) { }` blocks that silently dropped
  a corrupt monster-rule expression (one in SideCarDocument.Apply, reached
  from a corrupt side-car; one in LegacyCombatProfileDocument.Apply,
  reached during legacy-JSON migration) now log a warning via the host's
  IPluginLogger, threaded through as an optional parameter from every call
  site.
- VtankDatabase.Render()'s table-sort doc comment now states explicitly
  that StringComparer.Ordinal matching .NET Framework's SortedDictionary
  default order is confirmed only for the plain-ASCII table names VTank
  ships (AntiExtraBuffSpells, MyMonsters, Settings, …), not as a general
  claim for any string — comment only, no behavior change.

Added CorruptSideCarMonsterRuleIsLoggedNotSilentlySwallowed (FakeLogger
now captures Warn() calls via a new FakeHost.Logger property) pinning the
swallow-to-log fix.

Mutation: reverted MossTankProfileStore.cs to HEAD (keeping only the new
test) and ran it — failed with an empty Warnings collection, confirming
the silent-swallow bug before the fix.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 02:09:22 +02:00
Erik
55010f92c7 docs(vt): round 3 item 11 — record the MyMonsters persistence gap
CombatSettings.Rules lives only in MossTank's JSON side-car;
VtankSettingsProfileSerializer preserves the real .usd MyMonsters table
byte-for-byte but never parses it into MonsterRules or regenerates it from
them (VtankSettingsProfileSerializer.cs:24-30). MonsterRules.cs/
MonsterExpression.cs (the rule-grammar evaluator itself) is a faithful,
well-cited port with no material gap — only the real-file round trip is
missing.

Filed TS-86 (temporary stopgap; slice 3 ports the table) and added it as
gap item 6 in docs/research/vtank-kb/03-combat.md section 8 (previously
absent — the existing five gaps are about rule-grammar/priority fidelity,
not about whether the real table round-trips at all). Corrected the TS
section header's stale active-row count (was undercounting by one before
this row) to the actual count. Documentation-only; the parse is
deliberately NOT implemented this round, per the task's explicit scope.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 02:00:19 +02:00
Erik
43482c1380 feat(vt): round 3 item 9 — loot cutover to real .utl (consistency with the other three)
MossTankLootProfileStore now reads/writes real .utl files through
VtankLootProfileSerializer.TryRead/Write in the VtankProfiles storage,
matching the Settings/.usd, Route/.af, and Meta/.af cutovers already
landed: directory-backed listing (new VtankProfileDirectory.ListLootProfiles,
*.utl, "--" hidden rule), .cdf participation (LootFileName, already present
in VtankCharacterBinding but never populated by this store), and a one-time
JSON migration (SweepLegacyRosterIfNeeded) that converts BOTH this
character's own "By char" document and every other named profile the
pre-cutover roster still lists — unlike Meta/Route (whose rosters were
already abandoned pre-round-2), loot's roster was still the LIVE mechanism
right up to this commit, so there is no separate "selected vs the rest"
split the way Settings/Meta/Route each have. WriteLegacyExport is deleted;
exports/ has no remaining loot writer.

Found and fixed a real representational-loss bug the cutover would
otherwise have introduced: VtankLootProfileSerializer.ExportRequirements
replaces an empty VtankRequirements list with a "safely disabled"
VTClassic placeholder requirement — correct when .utl was only ever a
courtesy export mirror alongside the authoritative JSON store, but
silently destructive once .utl becomes the SOLE store, since every
MossTank-authored rule's Expression text would be permanently discarded on
its first save/reload cycle. Added a MossTank-owned length-delimited
"MossTankRuleExpressions" block (using the serializer's own existing
UnknownBlocks round-trip contract — a real VTClassic reader just ignores it
as an unrecognized block, the same as any other extension) that restores
each affected rule's exact Expression text on load.

Filed AD-123: MossTank's own ByCharacter auto-.utl-file convention for loot
(kept for consistency with the other three stores) versus retail's real
loot picker, which seeds only [None] and has no per-character auto file at
all (docs/research/vtank-kb/01-settings-and-profiles.md section 3).

Updated four pre-existing tests for the new storage shape/behavior:
ProfileGiveControllerTests' FakeHost now wires VtankProfiles (the loot
store no longer uses Storage at all); LootProfilesAreIndependentNamedDocuments
and LootCommandsImportAndExportExactVtclassicUtlFiles now assert against
the real file/no-exports-mirror shape instead of the deleted hashed-JSON/
exports-mirror one. Added LootRosterSweepConvertsByCharacterAndEveryNamedLegacyProfileOnce
pinning the migration + its idempotence.

Mutation: reverted MossTankLootProfileStore.cs/VtankProfileDirectory.cs to
HEAD (keeping only the new/changed tests) and ran the three
cutover-dependent tests — all three failed (no real .utl file, no
ListLootProfiles, exports/ mirror still expected) — confirming they
exercise the bug/gap before the fix. LootingUsesVtankDefaultsAndPersistsTheOrderedRuleEditor
(pre-existing, unchanged) independently caught the representational-loss
bug during development before the MossTankRuleExpressions block was added.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 01:51:34 +02:00
Erik
d98bf4a139 docs(vt): round 3 item 7 — register MossTank's .cdf Nav/Meta format divergence
Filed AD-122: VtankProfileDirectory.WriteCharacterBinding writes a real VTank
.cdf's Nav/Meta lines (4-5) as MossTank's own .af (metaf text) names, not
VTank's native binary .nav/.met. When ACDREAM_VTANK_PROFILE_DIR points at a
real installed VirindiTank profile folder for direct interop, that .cdf
names files a real VTank instance cannot load — Settings (.usd) and Loot
(.utl) stay real/binary-compatible; only Nav/Meta went .af-only for slice 1.

Added a warning sentence to the ACDREAM_VTANK_PROFILE_DIR row in
docs/launch-options.md pointing at the register row. Documentation-only;
no code or test changes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 01:33:24 +02:00
Erik
f8c4149ca3 docs(vt): slice-1 ledger — Part A re-review verdicts, round 3 dispatched
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 00:56:23 +02:00
Erik
44fd5ce580 docs(vt): slice-1 ledger — Part A round 2 landed, re-reviews dispatched
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 00:38:14 +02:00
Erik
a6c2e3918e docs(vt): VT1 complete, VT2 FINAL — every catalog doc citation-verified and folded
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 23:53:41 +02:00
Erik
7ac43cee63 docs(vt): fold the citation-pass corrections into KB 01 (tString rows not in Advanced Options, template schema is 8 tables with an empty Settings table, PluginCore list-builder ranges, bk.a semantics, RechargeHandlerSet read-back, ~~ prefix resolved)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 23:53:41 +02:00
Erik
b9029082ae docs(vt): slice-1 ledger — Part A round 1b complete, round 2 dispatched
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 23:39:27 +02:00
Erik
a3b4a8e2e5 fix(vt): H test-restoration + disabled-rule refusal + double-side gate
Item H (slice-1 fix round), six sub-parts:

1. Restored VtankMetaProfileSerializerTests.LoadsKnownTypedCondActRecord
   and SignedHighBitLandblockIdRoundTripsExactly (the latter adapted from
   the deleted RoundTripPreservesEveryVtankConditionActionAndEmbeddedNav's
   LandblockEquals/LandcellEquals coverage of unchecked((int)0x8B370000u)),
   both deleted as collateral damage of an unrelated file move in commit
   0d10399e0. Neither calls the deleted VtankMetaProfileSerializer.Save
   writer (demoted to import-only in 3ff9461ef) — both are pure reader
   assertions.

2. MetafSerializer.SaveMeta now refuses (throws InvalidOperationException
   naming the count) to silently drop a disabled MetaRule: real VTank/
   metaf has zero concept of "disabled" (confirmed: metaf_monolithic.py
   has no "enabled"/"disabled" occurrences anywhere), so
   MetaRule.Enabled is a MossTank-only extension with no metaf-compatible
   marker. A new SaveMeta(profile, dropDisabledRules: true) overload lets
   a caller accept the loss explicitly.
   MossTankMetaProfileStore.WriteLegacyExport (the .af convenience mirror
   beside MossTank's own fully-fidelity JSON storage) deliberately does
   NOT opt in — it leaves that mirror stale and logs a warning via its
   existing try/catch rather than losing the rule. Recorded as gap 6 in
   docs/research/vtank-kb/07-meta-and-expressions.md section 5.

3. New VtankProfilesDefault (src/AcDream.App/Plugins/): the graphical
   host's default VtankProfiles root (<DataDirectory>/vtank), extracted
   out of Program.cs's inline Path.Combine call into its own pure,
   injectable-root function specifically so the "Path.Combine only, never
   a hard-coded Windows path" guarantee is a real, failable Linux-path
   unit test (VtankProfilesDefaultTests.ResolveIsBuiltWithPathCombineOnly)
   rather than something only checkable by reading the source — the
   pattern item F's VtankProfileDirectory rewrite removed when
   Resolve/PortableDefault moved out of the plugin.

4. New BuffedDoubleRequirementDoesNotApplyBonusWhenBaseKeyIsAbsent: the
   KeyExistsDouble gate (ComputedItemInfo.cs:234) already existed in
   BuffedDouble, but only the int side
   (BuffedIntRequirementDoesNotApplyBonusWhenBaseKeyIsAbsent) had a
   pinning test.

5/6. DoubleSpellBonuses gained an explicit Change field (KB doc 05
   section 2.2: "additive unless the static table's Change==1, in which
   case multiplicative", ComputedItemInfo.cs:244), replacing
   BuffedDouble's prior `(int)bonus.Bonus == 1` magnitude-based proxy —
   that proxy only worked because every multiplicative bonus in the
   current 19 rows happens to fall in [1.0, 2.0) and every additive one
   happens to be under 1.0; it would have silently mis-branched on a
   future row like an additive 1.5 or a multiplicative 2.0+. Every
   existing row's Change value was derived mechanically from its old
   proxy result (no behavior change for the current table), and
   BuffedDoubleRequirementAppliesAdditiveBonusWhenBaseKeyExists/
   AppliesMultiplicativeBonusWhenChangeIsSet pin both branches through
   the real named field.

Full MossTank suite: 574 -> 581. App.Tests
(Plugin|LaunchOptions|RuntimeOptions filter): 135 -> 137. Core.Tests
(Plugin filter): 50/50 (no change, no Core-side edits this item).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 23:20:20 +02:00
Erik
2040f2bcfb fix(vt): F host-composed VtankProfiles storage replaces raw path string
Item F (slice-1 fix round). IPluginHost.VtankProfileDirectory handed the
plugin a raw string path and told it to fall back to its own
System.IO-based portable default when null — a plugin reading and
resolving filesystem paths itself, which is exactly the seam the rest of
IPluginHost.Storage deliberately avoids (Core.Plugins.ScopedPluginHost
scopes/validates every key; the plugin never sees a path).

- IPluginHost: VtankProfileDirectory (string?) deleted; new VtankProfiles
  (IPluginStorage, defaults to NoOpPluginStorage) added — a second,
  UNSCOPED storage instance (unlike Storage, which Core scopes per
  plugin manifest id) rooted at a host-composed VTank-compatible
  directory.
- ScopedPluginHost.VtankProfiles forwards _inner.VtankProfiles directly
  (no scoping — it names one shared external location, not per-plugin
  data). New PluginSessionTests.ScopedHostForwardsVtankProfilesUnscoped
  proves the forwarded instance is the exact same object (Assert.Same),
  not a wrapper.
- AppPluginHost/Program.cs: new vtankProfiles constructor parameter,
  composed as FilePluginStorage(runtimeOptions.VtankProfileDirectoryOverride
  ?? Path.Combine(applicationPaths.DataDirectory, "vtank")).
- RuntimeOptions.VtankProfileDirectoryOverride: new init-only property
  parsed from ACDREAM_VTANK_PROFILE_DIR (row added to
  docs/launch-options.md, side-effects column states the redirect is the
  only effect and documents the NullIfEmpty whitespace-not-special-cased
  quirk it shares with every other path-override flag). New
  RuntimeOptionsTests.VtankProfileDirectoryOverrideIsNullUnlessSet.
- FilePluginStorage.List(prefix): empty prefix now means "the storage
  root itself" instead of throwing (Resolve() rejects empty/whitespace
  keys, which is correct for every OTHER caller but wrong for "list
  everything" — VtankProfileDirectory needs exactly that).
- Headless: HeadlessPluginHost gained the same VtankProfiles
  property/constructor param, threaded through HeadlessPluginSession.Create
  -> HeadlessSessionHost -> HeadlessProcessHost, composed from the new
  HeadlessPathSet.VtankProfilesDirectory (<DataDirectory>/vtank, no
  ACDREAM_VTANK_PROFILE_DIR-equivalent override — Headless path overrides
  are HeadlessPathOverrides/CLI flags, not env vars). A small
  AcDream.Headless.Plugins.FilePluginStorage duplicates the App
  implementation byte-for-byte (Headless does not reference AcDream.App
  and no shared "platform plugins" library exists yet to host one copy;
  documented as a reasonable future consolidation, not required here).
- VtankProfileDirectory.cs rewritten: Resolve/PortableDefault deleted
  outright (no more System.IO, no plugin-owned portable-default fallback);
  ListSettingsProfiles/ListNavigationProfiles/ListMetaProfiles now take
  IPluginStorage and enumerate through EnumerateFileNames, which calls
  storage.List(string.Empty) and skips any key containing '/' (VTank's
  profile directory is flat; a nested key from some other IPluginStorage
  implementation is not a profile file). VtankProfileDirectoryTests
  rewritten against an in-memory IPluginStorage fake instead of real
  temp directories; new NestedPathKeysAreNotTreatedAsProfileFiles pins
  that skip. The prior Resolve/PortableDefault-specific tests (Linux-path
  guarantee, host-override-vs-portable-default) are superseded by
  RuntimeOptionsTests.VtankProfileDirectoryOverrideIsNullUnlessSet plus
  the RuntimeOptions.FromEnvironment Path.Combine-only composition in
  Program.cs.
- docs/architecture/acdream-architecture.md: one sentence in the
  Storage/List(prefix) paragraph naming VtankProfiles as the second,
  unscoped storage.

No production caller of VtankProfileDirectory's listing methods exists
yet (A2's foundation is not wired into MossTankProfileStore/
MossTankMetaProfileStore/MossTankRouteProfileStore's own selection —
per that slice's own ledger note), so this is a contract + plumbing
change with no MossTank runtime behavior change.

MossTank suite: 562/562. Core.Tests (Plugin filter): 50/50. App.Tests
(Plugin|LaunchOptions|RuntimeOptions filter): 135/135. Headless.Tests:
173/174 (the one failure, HeadlessCredentialResolverTests.
LinuxRejectsGroupOrOtherCredentialPermissions, is a pre-existing
Linux-only lane gate that throws PlatformNotSupportedException on this
Windows host — unrelated to this change).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 23:03:04 +02:00
Erik
0219c6e03d fix(vt): A ptl/tlk carry two coordinate triples, jmp direction loss recorded
Item A (slice-1 fix round). VTank/metaf's Portal2/UseNPC nav nodes carry
TWO coordinate triples (metaf_monolithic.py:356-357,11482,11618 —
"FORMAT: ptl/tlk myx myy myz tgtx tgty tgtz tgtObjectClass tgtName"): the
outer header ("myxyz", retail's own dead-weight last-save player position
per docs/research/vtank-kb/06-navigation-and-nav.md section 1.2) and the
embedded d-record ("tgtxyz", the real target coordinate used to match a
live world object by name+class+proximity). The prior port's
RouteWaypoint had a single Position field, so both the .af reader
(MetafSerializer.ReadNavNode) and the binary .nav reader
(VtankNavRouteSerializer.ReadWaypoint, case 6/7) overwrote "myxyz" with
"tgtxyz" on load, and the .af writer echoed the same Position value for
BOTH triples on save — a real .af round trip of the same waypoint was
lossy, which is why aphus/augments/lockandkey/neftet were excluded from
the byte-identity proof.

- RouteWaypoint: new ReferencePosition field (Position stays "myxyz",
  ReferencePosition is "tgtxyz"); included in Clone().
- MetafSerializer.ReadNavNode/RenderNavNode: ptl/tlk read/write both
  triples distinctly. WriteBinaryNavBlob's embedded-route writer (the
  MossTank runtime blob EmbedNav actions carry) fixed the same way — it
  was echoing Position for the reference triple too.
- VtankNavRouteSerializer.ReadWaypoint case 6/7: keep the header triple in
  Position, read the trailing triple into ReferencePosition instead of
  overwriting Position.
- Navigation.TickUse: TryFindObject now searches near ReferencePosition
  (the real target coordinate) instead of Position, preserving the
  correct runtime search behavior now that Position no longer aliases it.
- MossTankPanel.AddSelectedObjectWaypoint: new Portal2/UseNPC waypoints
  now set Position from the live snapshot (matching retail's own
  "wherever the character stood") and ReferencePosition from the selected
  object's live position (the real search anchor) — previously both were
  set from the object's position.
- MossTankRouteProfileStore's WaypointDocument DTO carries the reference
  triple too, so MossTank's own JSON-persisted routes round-trip it.
- MetafSerializerTests: un-excluded aphus/augments/lockandkey/neftet.af
  from the byte-identity proof (they all embed a ptl/tlk node and now
  round-trip correctly) and added example_sort_meta.af, which also
  passes. bore_quest.af was NOT added despite the slice-1 contract's
  ask: it is hand-edited the same way as the already-excluded
  bore_enhanced.af (space instead of tab between "IF:"/"DO:" and the
  following keyword, confirmed at bore_quest.af line 9 — metaf's own
  Rule.ExportToMetAF always joins with a tab, metaf_monolithic.py:12371),
  so it can never byte-match; documented alongside bore_enhanced's
  existing exclusion note instead. New PtlNodeKeepsBothCoordinateTriplesDistinct
  test pins the two-triple split directly (failed before this change:
  Position held the second triple with nowhere to read the first triple
  back from). VtankNavRouteSerializerTests updated to assert the split
  instead of the old collapsed value.
- jmp direction: metaf's NJump class has no strafe-direction field at all
  (metaf_monolithic.py:11708-11821, confirmed reading ImportFromMetAF/
  ExportToMetAF end to end) — the .af format cannot represent
  RouteWaypoint.JumpDirection, full stop. ReadNavNode no longer assigns
  JumpDirection = Forward explicitly (the model's own default), and the
  loss is now recorded as gap 9 in docs/research/vtank-kb/
  06-navigation-and-nav.md section 6.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 22:13:48 +02:00
Erik
68627a873b docs(vt): slice-1 ledger — Part A fix round 1 (3/12) and round 1b dispatched
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 21:55:10 +02:00
Erik
208599fae7 docs(vt): slice-1 ledger — full App suite on the merged branch (36 pre-existing env-gated failures)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 21:43:47 +02:00
Erik
467cb1c381 docs(vt): slice-1 ledger — Part B merged at da0fcb374
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 21:42:20 +02:00
Erik
da0fcb3741 merge(vt): slice 1 Part B — multi-column <list> markup (review-closed)
Campaign VT slice 1 Part B: <list><column type=text|check|icon> with
per-column bindings, text onclick, width="*" auto share, row-bound
callback guards, shared UiCheckLamp, Meta/Monsters-shaped end-to-end
tests. Two Opus lenses + fix round + narrow re-review: MERGE-READY.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 21:40:54 +02:00
Erik
b44282071c docs(vt): owner clarification — VTank has no real API; acdream may create its own plugin API later
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 21:35:18 +02:00
Erik
4ce6ffe6ff docs(vt): KB index counts match the verified docs (49 instantiations / 66 entries; 11 waypoint types)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 21:34:41 +02:00
Erik
bda8745448 docs(vt): owner decision — no interop; slice 9 dropped, all plugins rewritten against acdream's own API
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 21:34:30 +02:00
Erik
d078863306 docs(vt): owner sharpened the UI decision — resemblance + same interactions, not VVS internals
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 21:32:02 +02:00
Erik
026b512074 fix(vt): list column fix round 11/11 — docs for the whole fix round
docs/plugin-ui-markup.md's Columns section is rewritten to match every
production change from items 1-9:

- New "Width semantics" section: width="*", the last-column-always-auto
  rule (and how it combines with an earlier explicit "*"), the
  non-last-column Build-time throw, and the runtime overflow clamp.
- The <column> attribute grammar table gains column onclick
  (type="text", optional, Action<int>) and points width at the new
  section instead of the retired "last column ignores its own width"
  one-liner.
- New "Short columns past their own row count" section: text/icon draw
  nothing, check draws unchecked.
- Check-column glyph section updated for the shared UiCheckLamp
  primitive and its horizontal centering.
- New "The PITCH convention for transcribing a VTank column table"
  section: VVS's WPaddingOuter=3px/WPadding=7px/16px scrollbar reserve/
  forced-13px-check-columns have no direct acdream equivalent, so
  transcribing a real mainView.xml column table means declaring each
  column's PITCH (fixedwidth+7, or 13+7 for a check column regardless
  of its real fixedwidth) and reserving 16px on the last column.
- The worked example now uses rowheight="18" (VVS's own Padding*2+
  ControlHeight pitch, already the widget's default) and PITCH-computed
  widths (20 for a check column, 127 for a 120px text column) instead
  of arbitrary numbers, with a text onclick and a width="*" icon
  column added to demonstrate both new attributes in place.
- The bindings truth table gains column onclick (type="text") and both
  column width throw/silent rows (non-last vs. the list's last column).
- Testing conventions paragraph updated to mention width semantics, the
  row-bound click guard, and the two end-to-end tests.

Verification: dotnet build AcDream.slnx -c Release (green), dotnet test
tests/AcDream.App.Tests -c Release --filter
"FullyQualifiedName~Markup|FullyQualifiedName~UiMarkupList|FullyQualifiedName~PluginSidePanel"
(130/130), dotnet test tests/AcDream.Plugins.MossTank.Tests -c Release
--filter "FullyQualifiedName~Markup" (9/9) — closes the fix round.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 21:31:58 +02:00
Erik
92f9b36b8f docs(vt): slice-1 ledger — Part A landed, both review verdicts, two-round fix plan
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 21:26:21 +02:00
Erik
8fa70c3e4a docs(vt): KB 01 RechargeHandlerSet seeds 26 rows, not 24 (found by the Part A implementer against the fixture)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 21:06:37 +02:00
Erik
17f2143a71 docs(vt): append Part A closeout ledger line to the slice-1 contract 2026-09-06 21:04:18 +02:00