feat(vtank): slice 7 S7.5 — Route tab's real waypoint grid

VTank's own clWP/clWPc 2-column waypoint grid (text + a 1-based position
counter standing in for VTank's own count column) replaces the padded
single-column list, with any-cell-click delete matching
PluginCore.cs:3576-3599. Also: the recall menu gets scroll="true" for
grammar parity (still 4 real recall kinds — see the deviation note
below); the nav-type menu now shows VTank's own "Follow" caption for the
Target mode via a pure display remap (SelectedRouteMode/SelectRouteMode
translate the string, RouteMode.Target itself is unchanged, zero
behavior change); and a third nav image button (icon 0x060011F7, "Select
Nearest Point") is added.

Deviations, documented at their own binding site:
- VTank's cmbRecallType lists 27 named retail recalls; MossTank's
  RouteRecallKind models 4 (Lifestone/Marketplace/Primary/Secondary
  Portal). Expanding to 27 needs real per-recall spell-id data, which is
  casting-algorithm behavior out of this UI-parity slice's scope.
- VTank's btnNavResetPoint reassigns the LIVE navigation cursor mid-route
  (a mutable index MossTank's navigation controller never exposes to a
  plugin); SelectNearestRouteWaypoint instead moves the tab's own EDIT
  selection to the closest waypoint by horizontal distance — real,
  testable UI-only behavior that stays out of live-navigation territory.

The new pin (contract control count 177->180) and the new panel test
were shown to fail against a targeted mutation before being confirmed
green. MossTank suite 658 -> 659; App markup/plugin filter holds 192/192.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-07 10:33:49 +02:00
parent f5409530f2
commit 6118062a7a
4 changed files with 138 additions and 9 deletions

View file

@ -773,8 +773,28 @@ internal sealed partial class MossTankPanel
public string NavigationStatus => _navigation.Status;
public IReadOnlyList<string> RouteRows => _routeRows;
public int SelectedRouteWaypointIndex => _selectedRouteWaypoint;
public IReadOnlyList<string> RouteModeNames => Enum.GetNames<RouteMode>();
public string SelectedRouteMode => _navigationSettings.Mode.ToString();
// Campaign VT S7.5: VTank's own clWP/clWPc 2-column grid
// (docs/research/vtank-kb/08-ui-views.md §1 "Tab: Route";
// PluginCore.cs:3576-3599 — any cell click deletes the waypoint).
public IReadOnlyList<string> RouteWaypointTextColumn => _routeRows;
public IReadOnlyList<string> RouteWaypointCountColumn => Enumerable
.Range(1, _routeRows.Count)
.Select(static n => n.ToString(CultureInfo.InvariantCulture))
.ToArray();
public Action<int> DeleteRouteWaypointAt => DeleteRouteWaypointAtCore;
public Action SelectNearestRouteWaypoint => SelectNearestRouteWaypointCore;
// VTank's own cmbNavType captions read Circular/Linear/Follow/Once
// (KB §1's Route table); our RouteMode enum keeps the real value
// named Target (what the mode actually does — navigate to/follow a
// captured object) since that name predates this slice and nothing
// else in the codebase needs to change. This is a pure DISPLAY
// relabel — the same 4 enum values, same SelectRouteMode parsing,
// zero behavior change — not a rename of the underlying mode.
public IReadOnlyList<string> RouteModeNames =>
["Circular", "Linear", "Follow", "Once"];
public string SelectedRouteMode => _navigationSettings.Mode == RouteMode.Target
? "Follow"
: _navigationSettings.Mode.ToString();
public IReadOnlyList<string> RouteRecallNames =>
Enum.GetNames<RouteRecallKind>();
public string SelectedRouteRecall => _routeRecallKind.ToString();
@ -822,7 +842,10 @@ internal sealed partial class MossTankPanel
};
public Action<string> SelectRouteMode => value =>
{
if (!Enum.TryParse(value, ignoreCase: true, out RouteMode mode))
string normalized = string.Equals(value, "Follow", StringComparison.OrdinalIgnoreCase)
? nameof(RouteMode.Target)
: value;
if (!Enum.TryParse(normalized, ignoreCase: true, out RouteMode mode))
return;
_navigationSettings.Mode = mode;
if (mode == RouteMode.Target)
@ -2119,6 +2142,57 @@ internal sealed partial class MossTankPanel
_routeNotice = $"Removed {removed}.";
}
private void DeleteRouteWaypointAtCore(int row)
{
if ((uint)row >= (uint)_navigationSettings.Waypoints.Count)
return;
string removed = _navigationSettings.Waypoints[row].DisplayText;
_navigationSettings.Waypoints.RemoveAt(row);
_selectedRouteWaypoint = ClampRow(
_selectedRouteWaypoint,
_navigationSettings.Waypoints.Count);
_navigation.Reset();
RefreshRouteEditor();
SaveRouteProfile();
_routeNotice = $"Removed {removed}.";
}
/// <summary>
/// VTank's btnNavResetPoint (icon 0x060011F7, "Select Nearest Point",
/// docs/research/vtank-kb/08-ui-views.md §1 "Tab: Route"). VTank's own
/// button reassigns the LIVE navigation cursor mid-route; MossTank's
/// navigation controller exposes no such mutable cursor to a plugin
/// (only a read-only CurrentWaypointIndex), so this instead moves the
/// tab's own EDIT selection to the closest waypoint by horizontal
/// distance — real, testable UI behavior that stays inside this
/// slice's scope rather than adding new live-navigation control flow.
/// </summary>
private void SelectNearestRouteWaypointCore()
{
PluginNavigationSnapshot player = _host.Automation.Navigation.Snapshot;
if (!player.IsAvailable || _navigationSettings.Waypoints.Count == 0)
{
_routeNotice = "Current position is unavailable.";
return;
}
int best = 0;
double bestDistance = double.PositiveInfinity;
for (int i = 0; i < _navigationSettings.Waypoints.Count; i++)
{
RouteWaypoint waypoint = _navigationSettings.Waypoints[i];
if (waypoint.Position.CellId == 0u)
continue;
double distance = player.Position.HorizontalDistanceMeters(waypoint.Position);
if (distance < bestDistance)
{
bestDistance = distance;
best = i;
}
}
_selectedRouteWaypoint = best;
_routeNotice = $"Selected nearest waypoint ({_navigationSettings.Waypoints[best].DisplayText}).";
}
private void MoveRouteWaypoint(int direction)
{
if (_navigationSettings.Waypoints.Count < 2)

View file

@ -510,12 +510,19 @@
<label x="4" y="186" text="{Coverage}" color="#FF9B9072" />
</group>
<!-- Route: VTank's navigation-waypoint editor and execution surface. -->
<!-- Route: VTank's navigation-waypoint editor and execution surface.
The waypoint list is VTank's own clWP/clWPc 2-column grid (Campaign
VT S7.5, docs/research/vtank-kb/08-ui-views.md §1 "Tab: Route";
PluginCore.cs:3576-3599 — any cell click deletes the waypoint).
PITCH: text 330+7=337, count (last column) auto. -->
<group x="8" y="42" w="848" h="194" visible="{RouteVisible}">
<label x="4" y="0" text="Navigation Waypoints" color="#FFE8DEC3" />
<list x="4" y="18" w="366" h="120" rowheight="17"
items="{RouteRows}" selected="{SelectedRouteWaypointIndex}"
onchange="{SelectRouteWaypoint}" tooltip="Select an ordered navigation waypoint." />
selected="{SelectedRouteWaypointIndex}" onchange="{SelectRouteWaypoint}"
tooltip="Click a waypoint to remove it.">
<column type="text" width="337" items="{RouteWaypointTextColumn}" onclick="{DeleteRouteWaypointAt}" />
<column type="text" width="30" items="{RouteWaypointCountColumn}" onclick="{DeleteRouteWaypointAt}" />
</list>
<button x="380" y="18" w="92" h="21" text="Add"
onclick="{AddRoutePoint}" />
@ -526,9 +533,17 @@
<button x="380" y="45" w="92" h="21" text="Add Recall"
onclick="{AddRouteRecall}" />
<!-- VTank's own cmbRecallType lists 27 named retail recalls
(docs/research/vtank-kb/08-ui-views.md §1's Route table);
MossTank's RouteRecallKind models 4 (Lifestone/Marketplace/
Primary/Secondary Portal) — expanding to 27 needs real per-recall
spell-id data, out of this UI-parity slice's scope. scroll="true"
is applied for grammar parity even though 4 items never need to
scroll. -->
<menu x="478" y="45" w="150" h="21" items="{RouteRecallNames}"
selected="{SelectedRouteRecall}" onchange="{SelectRouteRecall}"
rows="4" openupward="false" tooltip="Choose the recall type added by Add Recall." />
rows="4" openupward="false" scroll="true"
tooltip="Choose the recall type added by Add Recall." />
<toggle x="636" y="47" w="142" h="20" text="Follow Corners"
checked="{FollowAroundCornersEnabled}"
onclick="{ToggleFollowAroundCorners}" />
@ -562,10 +577,17 @@
onclick="{MoveRouteWaypointUp}" tooltip="Move the selected waypoint up." />
<button x="670" y="126" w="28" h="21" icon="0x060028FD" iconkind="did"
onclick="{MoveRouteWaypointDown}" tooltip="Move the selected waypoint down." />
<!-- VTank's own btnNavResetPoint (icon 0x060011F7, "Select Nearest
Point") reassigns the LIVE navigation cursor; MossTank has no
mutable cursor to expose to a plugin, so this moves the tab's
own edit selection to the closest waypoint instead — see
SelectNearestRouteWaypoint's own doc comment. -->
<button x="704" y="126" w="28" h="21" icon="0x060011F7" iconkind="did"
onclick="{SelectNearestRouteWaypoint}" tooltip="Select the nearest waypoint." />
<menu x="4" y="148" w="90" h="22" items="{RouteModeNames}"
selected="{SelectedRouteMode}" onchange="{SelectRouteMode}"
rows="4" openupward="true" tooltip="Choose Circular, Linear, Target, or Once route behavior." />
rows="4" openupward="true" tooltip="Choose Circular, Linear, Follow, or Once route behavior." />
<toggle x="102" y="150" w="132" h="20" text="Nav Priority"
checked="{NavigationPriorityEnabled}"
onclick="{ToggleNavigationPriority}" />