MossTankMetaProfileStore and MossTankRouteProfileStore now store their
profiles as real .af files through IPluginHost.VtankProfiles, named and
listed by VtankProfileDirectory's rules, instead of a hashed JSON
document plus a stale "exports/" mirror (WriteLegacyExport deleted from
both). Named Meta profiles are plain shared files (matching VTank's own
ac() picker, which has no per-character sub-profile carve-out); named
route profiles carry a "nav_" prefix (metaf's own observed convention
for a stand-alone nav .af, confirmed against the committed nav_*.af
fixtures) so a route and a Meta profile sharing a user-typed name never
collide in the shared VtankProfiles directory. Both stores' Selected/
AvailableNames strip the file extension (and the route store's "nav_"
prefix) for display, matching the bare names users type at /vt meta|nav.
The route store now persists only the fields metaf's NAV: grammar
actually carries (Mode, Waypoints, FollowTarget) — Enabled/Priority/
MinimumDistanceMeters/FollowAroundCorners/OpenDoors/Door* are real VTank
Settings-table rows already owned end-to-end by MossTankProfileStore's
.usd profile (round 2 step 1), matching real VTank's own split between
global nav prefs and the per-route file; ClearCurrent and LoadCurrent
were narrowed to match. LoadCurrent gained an ISpellCatalog parameter
(TryLoadNav's own requirement); both call sites now pass
host.Automation.Spells.
Both stores gained the same one-time legacy-JSON migration as the
Settings store: first load converts a not-yet-migrated JSON profile to
.af and deletes the JSON key, leaving an existing .af counterpart (and
its stale JSON) untouched. The Meta store's SaveCurrent/Create now
refuse (return false, set SaveNotice, leave the prior .af content in
place) rather than silently drop a disabled rule that metaf/.af cannot
represent — MossTankPanel's four rule-editing call sites were updated to
prefer that refusal notice over their own generic success message.
Mutations shown to fail: MossTankMetaProfileStore.MigrateLegacyIfNeeded
stubbed to a no-op made MetaStoreMigratesLegacyJsonProfileToAfAndDeletesTheJsonKey
fail (legacy key was not deleted); SaveCurrent's SaveMeta call switched to
dropDisabledRules:true made MetaStoreRefusesToSaveADisabledRuleAndKeepsThePriorAfContent
fail (the disabled rule was silently written). Both restored and green.
NavigationTests.RouteProfilesRoundTripEveryWaypointField (asserting the
pre-cutover JSON-carries-everything behavior) was split into
RouteProfilesRoundTripWaypointFieldsButLeaveSettingsOwnedFieldsAlone and
FollowModeRouteRoundTripsTheFollowTargetThroughAf, and now also
documents two pre-existing, already-recorded metaf representational
gaps discovered by routing this path through .af for the first time:
JumpDirection has no .af representation at all (MetafSerializer.cs:924)
and a "jmp" node carries no cell id (six bare fields, no hex component).
MossTankPanelTests' nav/meta export-path tests were updated from the
retired "exports/meta|nav/" mirror to the real file locations.
591 MossTank tests passing (was 588 after step 1's commit, +3 new tests
net of the two renamed/retired ones).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>