fix(vtank): slice 7 round F item 4 — recall combo at VTank's terse-caption shape

Resemblance re-check: "the recall combo's full spell names overflow the
120 px box on most of the 27 entries — VTank's cmbRecallType uses terse
captions ('Primary', 'LS Sending', 'PtOIA', 'Fac. Hub'...): show those,
keep the full name in the waypoint and .af." Round E's D-2 had widened
the combo 120->300 to fit RecallDisplayName's full spell names; this
reverts the widening and shows VTank's own cmbRecallType captions
instead, restoring the box to VTank's real 120 width.

Added RouteWaypoint.RecallShortCaption(RouteRecallKind) — VTank's 27
terse cmbRecallType strings transcribed from docs/research/vtank-kb/
08-ui-views.md §1 (this worktree has no refs/vtank/ checkout to read
mainView.xml directly; the KB doc is its own faithful transcription of
that exact file, duplicate quirk included). The UI file's own caption
list carries a genuine VTank authoring duplicate — "Fac. Hub" then
"FacHub" again four slots later — that pushes every later caption one
position past the 26 real castable recalls it captions; removing that
duplicate restores an exact 1:1, in-order match onto RouteRecallKind's
26 real spells (confirmed by meaning, not just position — "Candeth" for
Return to the Keep is Candeth Keep, the literal destination; the three
"Soc. *" captions are the Society stronghold recalls). Marketplace was
never a cmbRecallType option (VTank's one slash-only recall) and keeps
its own literal caption since this combo still lists it.

RouteRecallNames/SelectedRouteRecall/SelectRouteRecall now read/parse
RecallShortCaption instead of RecallDisplayName — this is the ONLY
thing that changed. AddRouteRecallCore/RecallLabel still write the full
name (RecallDisplayName) onto the waypoint, the Route grid row, and the
.af file, completely untouched by this commit.

27 captions used, in RouteRecallKind order: Primary, Secondary, LS,
LS Sending, Portal, Aphus, Sanctuary, Caul, GW, Aerlinthe, Mt. Lethe,
Ulgrim's, Bur, PtOIA, Graveyard, Colosseum, Fac. Hub, Gear K. Camp,
Neftet, Candeth, Rynthid, VR Rocks, VR Tree, Soc. CH, Soc. RB, Soc. EW,
Marketplace.

Mutation shown to fail first: RouteRecallComboShowsVtanksTerseCaptionsButTheWaypointKeepsTheFullName
replaces RouteRecallComboShowsVtankNamesNotEnumIdentifiers with the new
expected terse-caption list. Verified by temporarily reverting
RouteRecallNames/SelectedRouteRecall back to RecallDisplayName (a
targeted mutation, then reverted) — the test failed with "Expected:
[Primary, Secondary, LS, ...] Actual: [Primary Portal Recall, Secondary
Portal Recall, Lifestone Recall, ...]" — then passed once
RecallShortCaption was restored.

MossTank suite 724/724 (one test replaced, net count unchanged); App
markup/plugin filter 258/258 (unaffected — no App-side markup change).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-07 18:54:54 +02:00
parent 179e6f3390
commit 6ef6eb7514
4 changed files with 118 additions and 40 deletions

View file

@ -1059,9 +1059,19 @@ internal sealed partial class MossTankPanel
// "ParadoxTouchedOlthoiInfestedAreaRecall", ...) instead of VTank's
// real recall names — RouteInsertModeNames/RouteModeNames above
// already route their own display captions through a dedicated
// string, not the enum's own ToString(); this brings Recall in line
// string, not the enum's own ToString(); this brought Recall in line
// via the same RecallDisplayName table AddRouteRecallCore/RecallLabel
// already use.
//
// Round F item 4 (resemblance re-check: "the recall combo's full
// spell names overflow the 120 px box on most of the 27 entries —
// VTank's cmbRecallType uses terse captions"): the combo itself now
// shows VTank's OWN terse cmbRecallType captions
// (RecallShortCaption) instead of RecallDisplayName's full spell
// names — the full name still drives the waypoint text, the grid
// row, and the .af file untouched, through RecallDisplayName/
// RecallLabel exactly as before; only this one combo's own display
// (and the string SelectRouteRecall parses back) changed.
public IReadOnlyList<string> RouteRecallNames
{
get
@ -1069,11 +1079,11 @@ internal sealed partial class MossTankPanel
RouteRecallKind[] values = Enum.GetValues<RouteRecallKind>();
var names = new string[values.Length];
for (int i = 0; i < values.Length; i++)
names[i] = RouteWaypoint.RecallDisplayName(values[i]);
names[i] = RouteWaypoint.RecallShortCaption(values[i]);
return names;
}
}
public string SelectedRouteRecall => RouteWaypoint.RecallDisplayName(_routeRecallKind);
public string SelectedRouteRecall => RouteWaypoint.RecallShortCaption(_routeRecallKind);
public IReadOnlyList<string> RouteProfileNames =>
_routeProfiles.AvailableNames;
public string SelectedRouteProfile => _routeProfiles.Selected;
@ -1154,14 +1164,18 @@ internal sealed partial class MossTankPanel
RefreshRouteEditor();
SaveRouteProfile();
};
// D-2: parses the SAME display string RouteRecallNames/SelectedRouteRecall
// now show (Enum.TryParse against the bare C# identifier no longer
// matches what the combo actually sends back).
// D-2: parses the SAME string RouteRecallNames/SelectedRouteRecall now
// show (Enum.TryParse against the bare C# identifier no longer matches
// what the combo actually sends back). Round F item 4 switched that
// string from RecallDisplayName's full name to
// RecallShortCaption's terse VTank caption — only the parsed FORMAT
// changed; the waypoint/grid/.af still read the recall's full name
// through RecallDisplayName/RecallLabel, unaffected by this method.
public Action<string> SelectRouteRecall => value =>
{
foreach (RouteRecallKind kind in Enum.GetValues<RouteRecallKind>())
{
if (RouteWaypoint.RecallDisplayName(kind).Equals(value, StringComparison.OrdinalIgnoreCase))
if (RouteWaypoint.RecallShortCaption(kind).Equals(value, StringComparison.OrdinalIgnoreCase))
{
_routeRecallKind = kind;
return;

View file

@ -240,6 +240,66 @@ internal sealed class RouteWaypoint
_ => value.ToString(),
};
/// <summary>
/// VTank's own <c>cmbRecallType</c> combo CAPTION (docs/research/
/// vtank-kb/08-ui-views.md §1's Route table, transcribed from
/// <c>refs/vtank/uTank2.ViewXML.mainView.xml</c>'s 27-string Choice
/// list) for each <see cref="RouteRecallKind"/> — used ONLY by the
/// combo's own display/parse round-trip (MossTankPanel.RouteRecallNames/
/// SelectedRouteRecall/SelectRouteRecall); the waypoint text, the
/// Route grid row, and the .af file keep showing <see
/// cref="RecallDisplayName"/>'s full name via <see cref="RecallLabel"/>,
/// untouched by this method (round F item 4 — resemblance re-check:
/// "the recall combo's full spell names overflow the 120 px box...
/// VTank's cmbRecallType uses terse captions").
///
/// The UI file's own 27 caption strings carry a genuine VTank
/// authoring duplicate — "Fac. Hub" at position 17 and "FacHub"
/// again at position 21 (KB's own noted "authoring duplicate") —
/// which pushes every later caption one slot further than the 26
/// real (unique) castable recalls it captions. Removing that
/// duplicate restores an exact 1:1, in-order match onto
/// <see cref="RouteRecallKind"/>'s 26 real spells: "Candeth" for
/// Return to the Keep (Candeth Keep IS the keep that spell returns
/// to), "Graveyard" for Call of the Mhoire Forge, and the three
/// "Soc. *" captions for the Society stronghold recalls.
/// <see cref="RouteRecallKind.Marketplace"/> was never a
/// cmbRecallType option at all (it is VTank's one slash-command-only
/// recall, per RecallDisplayName's own doc history) — it keeps its
/// own literal caption here since MossTank's combo still lists it.
/// </summary>
internal static string RecallShortCaption(RouteRecallKind value) => value switch
{
RouteRecallKind.PrimaryPortalRecall => "Primary",
RouteRecallKind.SecondaryPortalRecall => "Secondary",
RouteRecallKind.LifestoneRecall => "LS",
RouteRecallKind.LifestoneSending => "LS Sending",
RouteRecallKind.PortalRecall => "Portal",
RouteRecallKind.RecallAphusLassel => "Aphus",
RouteRecallKind.RecallTheSanctuary => "Sanctuary",
RouteRecallKind.RecallToTheSingularityCaul => "Caul",
RouteRecallKind.GlendenWoodRecall => "GW",
RouteRecallKind.AerlintheRecall => "Aerlinthe",
RouteRecallKind.MountLetheRecall => "Mt. Lethe",
RouteRecallKind.UlgrimsRecall => "Ulgrim's",
RouteRecallKind.BurRecall => "Bur",
RouteRecallKind.ParadoxTouchedOlthoiInfestedAreaRecall => "PtOIA",
RouteRecallKind.CallOfTheMhoireForge => "Graveyard",
RouteRecallKind.ColosseumRecall => "Colosseum",
RouteRecallKind.FacilityHubRecall => "Fac. Hub",
RouteRecallKind.GearKnightInvasionAreaCampRecall => "Gear K. Camp",
RouteRecallKind.LostCityOfNeftetRecall => "Neftet",
RouteRecallKind.ReturnToTheKeep => "Candeth",
RouteRecallKind.RynthidRecall => "Rynthid",
RouteRecallKind.ViridianRiseRecall => "VR Rocks",
RouteRecallKind.ViridianRiseGreatTreeRecall => "VR Tree",
RouteRecallKind.CelestialHandStrongholdRecall => "Soc. CH",
RouteRecallKind.RadiantBloodStrongholdRecall => "Soc. RB",
RouteRecallKind.EldrytchWebStrongholdRecall => "Soc. EW",
RouteRecallKind.Marketplace => "Marketplace",
_ => value.ToString(),
};
/// <summary>
/// The real retail spell id VTank casts for each recall (metaf's own
/// <c>NRecall</c> table). <see cref="RouteRecallKind.Marketplace"/> has

View file

@ -711,18 +711,19 @@
VTank's one slash-command entry) in VTank's own combo order —
see RouteRecallKind's own doc comment for the deviation from the
earlier 4-entry model. -->
<!-- D-2 (round E architecture re-check): widened from 120 to fit
VTank's real (unabbreviated, metaf-sourced) recall names — the KB's
own cmbRecallType is also 120 wide, but only because its captions
are hand-abbreviated ("LS", "Mt. Lethe", "PtOIA", "Fac. Hub");
RouteRecallNames deliberately keeps RecallDisplayName's full names
(round D item 3), so the box needs real room for the longest one,
"Paradox-touched Olthoi Infested Area Recall". Nothing else shares
this row past x=474, and the group floors at ~848 wide even at the
panel's minw, so 300 has ample clearance on both ends. -->
<menu x="474" y="60" w="300" h="16" items="{RouteRecallNames}"
<!-- Round F item 4 (resemblance re-check: "the recall combo's full
spell names overflow the 120 px box on most of the 27 entries —
VTank's cmbRecallType uses terse captions... show those, keep
the full name in the waypoint and .af"). D-2's 120->300 widening
is reverted: RouteRecallNames/SelectedRouteRecall now read
RecallShortCaption (VTank's own terse cmbRecallType strings —
"LS", "Mt. Lethe", "PtOIA", "Fac. Hub", ...), so the box is back
at VTank's real 120 width; AddRouteRecallCore still writes the
FULL name (RecallDisplayName via RecallLabel) onto the waypoint,
the grid row, and the .af, untouched by this. -->
<menu x="474" y="60" w="120" h="16" items="{RouteRecallNames}"
selected="{SelectedRouteRecall}" onchange="{SelectRouteRecall}"
rows="7" openupward="false" scroll="true"
rows="7" openupward="false"
tooltip="Choose the recall type added by Add Recall." />
<button x="380" y="82" w="88" h="16" text="Add Pause" onclick="{AddRoutePause}" />
<field x="474" y="82" w="68" h="16" text="{RoutePauseSecondsFieldText}"

View file

@ -2531,41 +2531,44 @@ public sealed class MossTankPanelTests
/// <summary>
/// D-2 (round E architecture re-check): the combo used to show
/// Enum.GetNames' bare C# identifiers ("PrimaryPortalRecall",
/// "ParadoxTouchedOlthoiInfestedAreaRecall", ...) — RouteRecallNames/
/// SelectedRouteRecall must show VTank's real recall names (the same
/// RecallDisplayName table AddRouteRecallCore already uses), in
/// VTank's own combo order, and SelectRouteRecall must parse THAT
/// display string back rather than the enum identifier.
/// "ParadoxTouchedOlthoiInfestedAreaRecall", ...) — round D fixed
/// that by showing RecallDisplayName's full names. Round F item 4
/// (resemblance re-check: "the recall combo's full spell names
/// overflow the 120 px box... VTank's cmbRecallType uses terse
/// captions") swapped the combo to VTank's OWN terse captions
/// (RecallShortCaption, transcribed from mainView.xml's cmbRecallType
/// Choice list, docs/research/vtank-kb/08-ui-views.md §1) — in
/// VTank's own combo order, deduplicated of the UI file's own
/// "Fac. Hub"/"FacHub" authoring duplicate — and SelectRouteRecall
/// must parse THAT terse string back. The full name must still be
/// what lands on the waypoint/grid row/.af (RecallDisplayName via
/// RecallLabel), unaffected by which string the combo itself shows.
/// </summary>
[Fact]
public void RouteRecallComboShowsVtankNamesNotEnumIdentifiers()
public void RouteRecallComboShowsVtanksTerseCaptionsButTheWaypointKeepsTheFullName()
{
var automation = new FakeAutomation { NavigationSnapshot = NavigationAt(0f) };
var panel = new MossTankPanel(new FakeHost(automation));
Assert.Equal(
[
"Primary Portal Recall", "Secondary Portal Recall", "Lifestone Recall",
"Lifestone Sending", "Portal Recall", "Recall Aphus Lassel",
"Recall the Sanctuary", "Recall to the Singularity Caul",
"Glenden Wood Recall", "Aerlinthe Recall", "Mount Lethe Recall",
"Ulgrim's Recall", "Bur Recall",
"Paradox-touched Olthoi Infested Area Recall",
"Call of the Mhoire Forge", "Colosseum Recall", "Facility Hub Recall",
"Gear Knight Invasion Area Camp Recall", "Lost City of Neftet Recall",
"Return to the Keep", "Rynthid Recall", "Viridian Rise Recall",
"Viridian Rise Great Tree Recall", "Celestial Hand Stronghold Recall",
"Radiant Blood Stronghold Recall", "Eldrytch Web Stronghold Recall",
"Marketplace Recall",
"Primary", "Secondary", "LS", "LS Sending", "Portal", "Aphus",
"Sanctuary", "Caul", "GW", "Aerlinthe", "Mt. Lethe", "Ulgrim's",
"Bur", "PtOIA", "Graveyard", "Colosseum", "Fac. Hub",
"Gear K. Camp", "Neftet", "Candeth", "Rynthid", "VR Rocks",
"VR Tree", "Soc. CH", "Soc. RB", "Soc. EW", "Marketplace",
],
panel.RouteRecallNames);
// Default selection reads as a real name, not "PrimaryPortalRecall".
Assert.Equal("Primary Portal Recall", panel.SelectedRouteRecall);
// Default selection reads as a terse caption, not the full name
// or the bare enum identifier.
Assert.Equal("Primary", panel.SelectedRouteRecall);
panel.SelectRouteRecall("Lifestone Recall");
Assert.Equal("Lifestone Recall", panel.SelectedRouteRecall);
panel.SelectRouteRecall("LS");
Assert.Equal("LS", panel.SelectedRouteRecall);
// The full name still lands on the notice and the grid row even
// though selection went through the terse caption above.
panel.AddRouteRecall();
Assert.Contains("Lifestone Recall", panel.RouteNotice, StringComparison.Ordinal);
Assert.Contains("Lifestone Recall", Assert.Single(panel.RouteWaypointTextColumn));