Commit graph

2685 commits

Author SHA1 Message Date
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
Erik
054bf32605 docs(vt): slice-1 contract amended from the Part B reviews (text onclick, auto width, no header); review ledger
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 20:34:18 +02:00
Erik
dc91245ba8 docs(vt): KB 05 second citation pass — 31 requirement types, f0.c categories and dz.o.h readers resolved
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 20:25:19 +02:00
Erik
959a694823 feat(vt): Campaign VT slice 1 Part B — multi-column <list> markup
VVS HudList parity (docs/research/vtank-kb/08-ui-views.md 2-3): <list>
accepts <column type="text|check|icon" width=... [iconkind] [colors]/>
children, each binding its own per-row source parallel to the row count.
Row count is the longest bound column; a click in a text column selects
(the list's own selected/onchange, unchanged), a click in a check/icon
column fires that column's own required onchange/onclick with the row
index and does NOT change selection. Last column always absorbs the
remaining width, recomputed live off the list's own Width rather than
baked in at Build. Per-cell horizontal clipping via UiRenderContext's
existing PushClip/PopClip. The check-column glyph reuses <toggle>'s own
five-band lamp primitive (UiMarkupToggle.DrawLamp/colors promoted from
private to internal) so it looks like every other checkbox. Unknown
column type, a missing required column binding, <column> combined with
the legacy items/icons/colors list attributes, or any non-<column> child
of <list> all throw FormatException at Build. A column-less <list> is
byte-for-byte the original single-text-column widget (new if/Columns
branch in OnDraw/OnEvent; the legacy branch's code is untouched).

New UiMarkupListColumn model (src/AcDream.App/UI/UiMarkupListColumn.cs)
carries each column's kind/width/bindings; MarkupDocument's `list` case
now builds either the legacy single-column fields or a Columns list,
never both. MarkupListColumnsTests (27 new tests) cover parse/binding
validation, draw-level column-offset/clipping/check-glyph/icon pins
against the recording renderer, hit-test routing, and a backward-
compatibility proof (a column-less list built through MarkupDocument
produces a byte-identical draw record to a hand-built UiMarkupList with
equivalent fields). Every new assertion was verified to fail first via
targeted temporary mutations (hit-test isolation, clip removal, last-
column-absorbs-remainder, required onchange/onclick, row-count = max),
each reverted after confirming failure.

docs/plugin-ui-markup.md gets a full Columns section (attribute grammar,
a Monsters-tab-style example, the no-header-row idiom, backward
compatibility) and the old single-text-column LIMITATION note is
retired; the bindings truth table gains the six new column-attribute
rows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 20:21:16 +02:00
Erik
4549eb02af docs(vt): VT2 ledger records the folded citation pass and the re-judged rows
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 20:19:33 +02:00
Erik
caf7307522 docs(vt): fold the citation-pass corrections into KB 06 (0.01° turn threshold, OpenVendor busy paths, cm sites vs instantiations, resolved f9.j and item.c meanings, path/count nits)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 20:19:19 +02:00
Erik
d8f85848b6 docs(vt): KB 08 gap table gains the menu-overflow row the citation pass asked for
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 20:18:18 +02:00
Erik
69bb164845 docs(vt): fold the citation-pass corrections into KB 08 (icon-id decode, menu wraps not scrolls, slider fixed 0-1, HudList press-point click, dc.xml, resolved clRings and loot-editor call site); repair KB 09 path
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 20:18:07 +02:00
Erik
19a32cde2e docs(vt): KB 09 header points at the real UtilityBelt tree; slice-1 A3 carries the Change-vs-Bonus fix
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 20:17:11 +02:00
Erik
ee901b7f26 docs(vt): fold the citation-pass corrections into KB 05 and 09
05: recognized-vs-unrecognized rule payloads, 25 materials, only 3-arg
SpellInfo entries are live, Read/User1..5 unreachable from any loot plugin,
hv.a.e is a dead field, id.a.j = shareLoot, the Change-vs-Bonus branch
selector mis-port in the evaluator.
09: obtest location, the broken params dedup overload, the real ah call
sites, internal relay ctor, the next-waypoint handler at 3621-3639,
TryHandle is host-only, Classic Looter registry narrowed, UtilityBelt's
Harmony bypass route resolved.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 20:16:56 +02:00
Erik
642eacc13c docs(vt): fold the citation-pass corrections into KB 07 (hn.cs line map, dh/q hierarchy, 74-function count, ReturnFromCall guard, resolved fm/a shapes)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 20:15:22 +02:00
Erik
b7ed65bc64 docs(vt): VT2 slice 4 re-judged after the KB 04 citation pass (G4 is ordering only)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 20:04:24 +02:00