feat(vtank): slice 7 fix round B item 8 — Route tab collapses to VTank's 2-across grid

Transcribed VTank's own Route tab almost verbatim (docs/research/
vtank-kb/08-ui-views.md §1 "Tab: Route", 20 controls, +4px left margin for
our own convention): the waypoint list, a bottom row (nav-type menu +
insert-mode menu + three nav image buttons), and a right-hand 2-across
button grid in VTank's own row order (Add/Open Vendor; Add Portal-NPC/Add
NPC Talk; Add Recall+its recall-type menu; Add Pause+field+"seconds"
label; Add Chat+field).

- cmbNavInsertMode is now a real 3-option <menu> (RouteInsertMode.AddToEnd/
  InsertAbove/InsertBelow, new enum in Navigation.cs) instead of the old
  2-state ToggleRouteAddPosition button — InsertAbove/InsertBelow are now
  genuinely distinct (before vs. after the selected waypoint), not both
  collapsed into "not append".
- The three nav image buttons (advance/regress/nearest-point, DAT ids
  0x060028FD/0x060028FC/0x060011F7) move from the waypoint-actions row to
  the bottom row beside the nav-type menu, matching VTank's own layout.
- "Use NPC" is recaptioned "Add NPC Talk" (VTank's own cmdNavUseNPC text),
  same AddRouteUseSelected binding.
- The pause duration is now a real editable field
  (RoutePauseSecondsFieldText, default "5", parsed/clamped 0-3600 by
  SetRoutePauseSecondsText) plus a "seconds" label, matching VTank's own
  txtPauseWaypointTime/Label52 — replaces the RoutePauseDown/RoutePauseUp
  stepper (removed).
- MossTank-only controls with no VTank Route-tab counterpart — Checkpoint,
  Jump, Remove, Set Follow Target + its status label, Follow Corners, Open
  Doors, Nav Priority (a real second copy of Options' own "Boost Nav.
  Priority"), and the Follow/Nav Min Distance +/- stepper (a real
  duplicate of Options' own editable field) — move to
  mosstank-advanced.xml's new "MossTank Extras" section (popup height
  300 -> 392 to fit them without touching the retail editor above).

New tests: RouteInsertModeControlsWhereANewWaypointLands (AddToEnd/
InsertAbove/InsertBelow each produce the correct insertion index),
RoutePauseSecondsFieldParsesAndClampsInput. Both mutation-checked:
hardcoding AddRouteWaypoint's insertion to always-append turned the first
red ("Point: (0N, 0E)" instead of containing "99"); removing the Math.Clamp
in SetRoutePauseSecondsText turned the second red ("99999" instead of the
clamped "3600"). Both restored to green.

EveryInteractiveControlDeclaresARealHandlerBinding: 166 -> 156 (mosstank.xml
Route tab 28 -> 18 controls; the 10 relocated/removed controls are either
gone or moved into mosstank-advanced.xml, a separate file this scan
doesn't cover).
SecondaryPopupPanelsFitTheirOwnBoundsAndEveryBindingResolves: mosstank-
advanced.xml's expected height 300 -> 392.

tests/AcDream.Plugins.MossTank.Tests: 668/668 (was 666/666, +2 new tests).
tests/AcDream.App.Tests --filter Markup|Plugin|UiMenu|Slider: 276/3 skipped/279 (unchanged).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-07 12:31:01 +02:00
parent e7ed603e6e
commit 95c93858a1
6 changed files with 224 additions and 93 deletions

View file

@ -203,7 +203,7 @@ internal sealed partial class MossTankPanel
private string _routeChatDraft = "/ls";
private int _routePauseSeconds = 5;
private RouteRecallKind _routeRecallKind = RouteRecallKind.PrimaryPortal;
private bool _routeAddToEnd = true;
private RouteInsertMode _routeInsertMode = RouteInsertMode.AddToEnd;
private IReadOnlyList<string> _metaRows = Array.Empty<string>();
private int _selectedMetaRule;
private string _metaProfileNameDraft = string.Empty;
@ -819,7 +819,16 @@ internal sealed partial class MossTankPanel
public string RouteProfileNameDraft => _routeProfileNameDraft;
public string RouteNotice => _routeNotice;
public string RouteChatDraft => _routeChatDraft;
public string RoutePauseText => $"{_routePauseSeconds} seconds";
// Fix round B item 8: VTank's own txtPauseWaypointTime is a real
// editable field defaulting to "5" (docs/research/vtank-kb/
// 08-ui-views.md §1's Route table), not a "-"/"+" stepper.
public string RoutePauseSecondsFieldText =>
_routePauseSeconds.ToString(CultureInfo.InvariantCulture);
public Action<string> SetRoutePauseSecondsText => value =>
{
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int seconds))
_routePauseSeconds = Math.Clamp(seconds, 0, 3600);
};
public string RouteMinimumDistanceText => string.Create(
CultureInfo.InvariantCulture,
$"Follow/Nav Min Distance: {_navigationSettings.MinimumDistanceMeters:0.0}m");
@ -827,9 +836,24 @@ internal sealed partial class MossTankPanel
_navigationSettings.FollowTargetObjectId == 0u
? "Follow target: [None]"
: $"Follow target: {_navigationSettings.FollowTargetName}";
public string RouteAddPositionText => _routeAddToEnd
? "Add to End"
: "Insert After Selection";
// Fix round B item 8: VTank's own cmbNavInsertMode is a 3-option
// <menu> (Add to End / Insert Above / Insert Below), not the old
// 2-state toggle button.
public IReadOnlyList<string> RouteInsertModeNames { get; } =
["Add to End", "Insert Above", "Insert Below"];
public string SelectedRouteInsertMode => _routeInsertMode switch
{
RouteInsertMode.InsertAbove => "Insert Above",
RouteInsertMode.InsertBelow => "Insert Below",
_ => "Add to End",
};
public Action<string> SelectRouteInsertMode => value =>
_routeInsertMode = value switch
{
"Insert Above" => RouteInsertMode.InsertAbove,
"Insert Below" => RouteInsertMode.InsertBelow,
_ => RouteInsertMode.AddToEnd,
};
public Action ToggleNavigation => () =>
{
@ -891,12 +915,6 @@ internal sealed partial class MossTankPanel
public Action RemoveRouteWaypoint => RemoveRouteWaypointCore;
public Action MoveRouteWaypointUp => () => MoveRouteWaypoint(-1);
public Action MoveRouteWaypointDown => () => MoveRouteWaypoint(1);
public Action ToggleRouteAddPosition => () =>
_routeAddToEnd = !_routeAddToEnd;
public Action RoutePauseDown => () =>
_routePauseSeconds = Math.Max(0, _routePauseSeconds - 1);
public Action RoutePauseUp => () =>
_routePauseSeconds = Math.Min(3600, _routePauseSeconds + 1);
public Action RouteMinimumDistanceDown => () =>
{
_navigationSettings.MinimumDistanceMeters = Math.Max(
@ -2200,11 +2218,13 @@ internal sealed partial class MossTankPanel
private void AddRouteWaypoint(RouteWaypoint waypoint)
{
int insertion = _routeAddToEnd
? _navigationSettings.Waypoints.Count
: Math.Min(
_navigationSettings.Waypoints.Count,
_selectedRouteWaypoint + 1);
int count = _navigationSettings.Waypoints.Count;
int insertion = _routeInsertMode switch
{
RouteInsertMode.InsertAbove => Math.Clamp(_selectedRouteWaypoint, 0, count),
RouteInsertMode.InsertBelow => Math.Min(count, _selectedRouteWaypoint + 1),
_ => count,
};
_navigationSettings.Waypoints.Insert(insertion, waypoint);
_selectedRouteWaypoint = insertion;
_navigation.Reset();

View file

@ -40,6 +40,21 @@ internal enum RouteJumpDirection
StrafeRight,
}
/// <summary>
/// VTank's own cmbNavInsertMode (docs/research/vtank-kb/08-ui-views.md §1
/// "Tab: Route": "options: Add to End / Insert Above / Insert Below").
/// Replaces the old two-state _routeAddToEnd bool (fix round B item 8) —
/// InsertAbove/InsertBelow are both relative to the currently selected
/// waypoint, distinguishing "before" from "after" rather than collapsing
/// both non-append cases into one.
/// </summary>
internal enum RouteInsertMode
{
AddToEnd,
InsertAbove,
InsertBelow,
}
internal sealed class RouteWaypoint
{
public RouteWaypointType Type { get; set; }

View file

@ -30,8 +30,29 @@
overlapped and read as garbled text on screen once this became a
top-level window (live-verified, screenshot before the fix showed
"AdvancedOptionsced Options — complete VTank settings table").
-->
<panel x="253" y="405" w="392" h="300" title="MossTank Advanced Options"
Fix round B item 8: below the retail key-value editor is a "MossTank
Extras" section — controls with no VTank-tab counterpart, moved here per
the owner's silhouette rule (VTank's own controls are never
shrunk/moved/reordered to make room for MossTank extras) rather than
crowding the Route tab: Checkpoint/Jump (AddRouteCheckpoint/
AddRouteJump), Remove (RemoveRouteWaypoint), Set Follow Target +
its status label, Follow Corners/Open Doors (navigation toggles with no
Route-tab VTank equivalent), Nav Priority (a real second copy of
Options' own "Boost Nav. Priority" — same NavigationPriorityEnabled/
ToggleNavigationPriority binding, kept here too rather than assuming the
user remembers Options has it), and the Follow/Nav Min Distance +/-
stepper (a real duplicate of Options' own editable
FollowNavMinimumValueText field — kept for symmetry with the other
extras, not deleted, since both write the same
NavigationSettings.MinimumDistanceMeters). Panel height grows 300->392
to fit this section without touching the retail editor above it; item 9
is expected to reshuffle the retail portion further (clVal column,
lFilterList checklist, description readout) without moving this section.
Bold text isn't representable in the plain retail UI font (0x40000000
has no bold face) — the section header uses the same bright caption
color other headers use instead. -->
<panel x="253" y="405" w="392" h="392" title="MossTank Advanced Options"
visible="{AdvancedOptionsVisible}" resize="none">
<list x="4" y="22" w="260" h="160" rowheight="17"
items="{AdvancedOptionNames}" selected="{SelectedAdvancedOptionIndex}"
@ -47,4 +68,22 @@
maxlength="160" clearonsubmit="false" background="#E6000000"
tooltip="Edit the selected advanced-option value and press Enter or Apply." />
<label x="4" y="216" w="384" h="16" text="{AdvancedOptionNotice}" color="#FFC7B98F" />
<label x="4" y="236" w="200" h="16" text="MossTank Extras" color="#FFE8DEC3" />
<button x="4" y="254" w="90" h="18" text="Checkpoint" onclick="{AddRouteCheckpoint}" />
<button x="100" y="254" w="90" h="18" text="Jump" onclick="{AddRouteJump}" />
<button x="4" y="276" w="90" h="18" text="Remove" onclick="{RemoveRouteWaypoint}" />
<button x="100" y="276" w="140" h="18" text="Set Follow Target" onclick="{SetFollowTarget}" />
<label x="4" y="298" w="376" h="16" text="{RouteFollowTargetText}" color="#FFC7B98F" />
<toggle x="4" y="318" w="140" h="18" text="Follow Corners"
checked="{FollowAroundCornersEnabled}" onclick="{ToggleFollowAroundCorners}" />
<toggle x="200" y="318" w="140" h="18" text="Open Doors"
checked="{OpenDoorsEnabled}" onclick="{ToggleOpenDoors}" />
<toggle x="4" y="340" w="140" h="18" text="Nav Priority"
checked="{NavigationPriorityEnabled}" onclick="{ToggleNavigationPriority}" />
<label x="4" y="362" w="180" h="16" text="{RouteMinimumDistanceText}" color="#FFC7B98F" />
<button x="190" y="362" w="24" h="18" text="-"
onclick="{RouteMinimumDistanceDown}" tooltip="Decrease the route arrival distance." />
<button x="218" y="362" w="24" h="18" text="+"
onclick="{RouteMinimumDistanceUp}" tooltip="Increase the route arrival distance." />
</panel>

View file

@ -553,29 +553,65 @@
<label x="332" y="178" w="184" h="16" text="{Coverage}" color="#FF9B9072" />
</group>
<!-- 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";
<!-- Route: VTank's own 2-across button grid, row order, and bottom
nav-control row (fix round B item 8, docs/research/vtank-kb/
08-ui-views.md §1 "Tab: Route" — 20 controls, transcribed almost
verbatim, +4px left margin for our own convention). The waypoint
list is VTank's own clWP/clWPc 2-column grid (Campaign VT S7.5;
PluginCore.cs:3576-3599 — any cell click deletes the waypoint).
PITCH: text 330+7=337, count (last column) auto. -->
PITCH: text 330+7=337, count (last column) auto.
MossTank-only extras that have no VTank Route-tab counterpart moved
to the Advanced Options popup's own "MossTank Extras" section:
Checkpoint, Jump, Remove, Set Follow Target + its status label,
Follow Corners, Open Doors, Nav Priority (also duplicates Options'
own "Boost Nav. Priority"), and the Follow/Nav Min Distance +/-
stepper (a real duplicate of Options' own editable field — kept for
symmetry with the other extras rather than deleted, see
mosstank-advanced.xml's own comment).
cmbNavInsertMode is now a real 3-option <menu> (RouteInsertMode:
AddToEnd/InsertAbove/InsertBelow, Navigation.cs) replacing the old
2-state toggle button; the three nav image buttons (advance/regress/
nearest, DAT ids 0x060028FD/0x060028FC/0x060011F7) sit beside it on
the same bottom row instead of the waypoint-actions row. "Use NPC"
is recaptioned "Add NPC Talk" (VTank's own cmdNavUseNPC text) with
the same AddRouteUseSelected binding. The pause duration is now a
real editable field (RoutePauseSecondsFieldText, default "5") plus
a "seconds" label, matching VTank's txtPauseWaypointTime/Label52
instead of the old "-"/"+" stepper. -->
<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"
<label x="4" y="0" w="370" h="16" text="Navigation Waypoints" color="#FFE8DEC3" />
<list x="4" y="16" w="370" h="116" rowheight="17"
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}" />
<button x="478" y="18" w="116" h="21" text="Open Vendor"
onclick="{AddRouteOpenVendor}" />
<button x="600" y="18" w="116" h="21" text="Add Portal/NPC"
onclick="{AddRoutePortal}" />
<menu x="4" y="136" w="80" h="16" items="{RouteModeNames}"
selected="{SelectedRouteMode}" onchange="{SelectRouteMode}"
rows="4" openupward="true" tooltip="Choose Circular, Linear, Follow, or Once route behavior." />
<menu x="90" y="136" w="120" h="16" items="{RouteInsertModeNames}"
selected="{SelectedRouteInsertMode}" onchange="{SelectRouteInsertMode}"
rows="3" openupward="true" tooltip="Where Add buttons insert the new waypoint." />
<button x="214" y="136" w="16" h="16" icon="0x060028FD" iconkind="did"
onclick="{MoveRouteWaypointDown}" tooltip="Advance the current point." />
<button x="230" y="136" w="16" h="16" icon="0x060028FC" iconkind="did"
onclick="{MoveRouteWaypointUp}" tooltip="Regress the current point." />
<!-- 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="246" y="136" w="16" h="16" icon="0x060011F7" iconkind="did"
onclick="{SelectNearestRouteWaypoint}" tooltip="Select the nearest waypoint." />
<button x="380" y="45" w="92" h="21" text="Add Recall"
onclick="{AddRouteRecall}" />
<button x="380" y="16" w="88" h="16" text="Add" onclick="{AddRoutePoint}" />
<button x="474" y="16" w="88" h="16" text="Open Vendor" onclick="{AddRouteOpenVendor}" />
<button x="380" y="38" w="88" h="16" text="Add Portal/NPC" onclick="{AddRoutePortal}" />
<button x="474" y="38" w="88" h="16" text="Add NPC Talk" onclick="{AddRouteUseSelected}" />
<button x="380" y="60" w="88" h="16" 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/
@ -583,72 +619,24 @@
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}"
<menu x="474" y="60" w="120" h="16" items="{RouteRecallNames}"
selected="{SelectedRouteRecall}" onchange="{SelectRouteRecall}"
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}" />
<toggle x="636" y="74" w="142" h="20" text="Open Doors"
checked="{OpenDoorsEnabled}" onclick="{ToggleOpenDoors}" />
<button x="380" y="72" w="92" h="21" text="Add Pause"
onclick="{AddRoutePause}" />
<label x="478" y="76" text="{RoutePauseText}" color="#FFC7B98F" />
<button x="570" y="72" w="28" h="21" text="-"
onclick="{RoutePauseDown}" tooltip="Decrease the pause duration." />
<button x="604" y="72" w="28" h="21" text="+"
onclick="{RoutePauseUp}" tooltip="Increase the pause duration." />
<button x="380" y="99" w="92" h="21" text="Add Chat"
onclick="{AddRouteChat}" />
<field x="478" y="99" w="150" h="21" text="{RouteChatDraft}"
<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}"
onchange="{SetRoutePauseSecondsText}" onsubmit="{SetRoutePauseSecondsText}"
maxlength="4" clearonsubmit="false" background="#E6000000"
color="#FFE8DEC3" tooltip="Pause duration in seconds, added by Add Pause." />
<label x="547" y="82" w="50" h="16" text="seconds" color="#FFE8DEC3" />
<button x="380" y="104" w="88" h="16" text="Add Chat" onclick="{AddRouteChat}" />
<field x="474" y="104" w="120" h="16" text="{RouteChatDraft}"
onchange="{SetRouteChatDraft}" onsubmit="{SetRouteChatDraft}"
maxlength="128" clearonsubmit="false" background="#E6000000"
color="#FFE8DEC3" tooltip="Chat command added by Add Chat." />
<button x="636" y="99" w="80" h="21" text="Use NPC"
onclick="{AddRouteUseSelected}" />
<button x="380" y="126" w="92" h="21" text="Checkpoint"
onclick="{AddRouteCheckpoint}" />
<button x="478" y="126" w="72" h="21" text="Jump"
onclick="{AddRouteJump}" />
<button x="556" y="126" w="72" h="21" text="Remove"
onclick="{RemoveRouteWaypoint}" />
<button x="636" y="126" w="28" h="21" icon="0x060028FC" iconkind="did"
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, Follow, or Once route behavior." />
<toggle x="102" y="150" w="132" h="20" text="Nav Priority"
checked="{NavigationPriorityEnabled}"
onclick="{ToggleNavigationPriority}" />
<button x="242" y="148" w="136" h="22" text="{RouteAddPositionText}"
onclick="{ToggleRouteAddPosition}" />
<button x="386" y="148" w="112" h="22" text="Set Follow Target"
onclick="{SetFollowTarget}" />
<label x="506" y="152" text="{RouteFollowTargetText}"
color="#FFC7B98F" />
<label x="4" y="174" text="{RouteMinimumDistanceText}"
color="#FFC7B98F" />
<button x="196" y="170" w="28" h="21" text="-"
onclick="{RouteMinimumDistanceDown}" tooltip="Decrease the route arrival distance." />
<button x="230" y="170" w="28" h="21" text="+"
onclick="{RouteMinimumDistanceUp}" tooltip="Increase the route arrival distance." />
<label x="270" y="174" text="{NavigationStatus}" color="#FF9B9072" />
<label x="520" y="174" text="{RouteNotice}" color="#FF9B9072" />
<label x="4" y="160" w="300" h="16" text="{NavigationStatus}" color="#FF9B9072" />
<label x="310" y="160" w="300" h="16" text="{RouteNotice}" color="#FF9B9072" />
</group>
<!-- Meta: ordered VTank state-machine rules. Rules fire once per state

View file

@ -220,7 +220,19 @@ public sealed class MossTankMarkupContractTests
// Apply/Remove/MoveUp/MoveDown, 14). "Add" is renamed "Create" and
// stays in place (same slot, no count change). Added: the settable
// cmbMetaCurrentState menu (+1). Net 186 -> 166 (-7 -14 +1).
Assert.Equal(166, controls.Length);
// Fix round B item 8: the Route tab collapses to VTank's own
// 2-across button grid + bottom nav-control row (docs/research/
// vtank-kb/08-ui-views.md §1 "Tab: Route"), 28 -> 18 interactive
// controls within mosstank.xml. Checkpoint/Jump/Remove/Set Follow
// Target/Follow Corners/Open Doors/Nav Priority/the Follow-Nav-Min-
// Distance stepper (no VTank Route-tab counterpart) moved to
// mosstank-advanced.xml's own new "MossTank Extras" section — a
// separate file this scan doesn't cover. The 2-state
// ToggleRouteAddPosition button became the real 3-option
// cmbNavInsertMode <menu>, and the pause "-"/"+" stepper became one
// editable field, matching VTank's own txtPauseWaypointTime. Net
// 166 -> 156.
Assert.Equal(156, controls.Length);
foreach (XElement control in controls)
{
@ -431,7 +443,7 @@ public sealed class MossTankMarkupContractTests
/// give the main panel, now split across three files instead of one.
/// </summary>
[Theory]
[InlineData("mosstank-advanced.xml", 392f, 300f)]
[InlineData("mosstank-advanced.xml", 392f, 392f)]
[InlineData("mosstank-loot-editor.xml", 268f, 300f)]
[InlineData("mosstank-buffpicker.xml", 268f, 236f)]
[InlineData("mosstank-metaeditor.xml", 630f, 160f)]

View file

@ -2055,6 +2055,63 @@ public sealed class MossTankPanelTests
Assert.Equal(["1"], panel.RouteWaypointCountColumn);
}
[Fact]
public void RouteInsertModeControlsWhereANewWaypointLands()
{
// Fix round B item 8: VTank's own cmbNavInsertMode is a real
// 3-option <menu> (Add to End / Insert Above / Insert Below),
// replacing the old 2-state ToggleRouteAddPosition button.
var automation = new FakeAutomation { NavigationSnapshot = NavigationAt(0f) };
var panel = new MossTankPanel(new FakeHost(automation));
Assert.Equal("Add to End", panel.SelectedRouteInsertMode);
automation.NavigationSnapshot = automation.NavigationSnapshot with
{ Position = automation.NavigationSnapshot.Position with { EastWest = 0d } };
panel.AddRoutePoint(); // index 0 @ 0
automation.NavigationSnapshot = automation.NavigationSnapshot with
{ Position = automation.NavigationSnapshot.Position with { EastWest = 20d } };
panel.AddRoutePoint(); // index 1 @ 20 (default Add to End)
Assert.Equal(2, panel.RouteWaypointTextColumn.Count);
Assert.Contains("20", panel.RouteWaypointTextColumn[1]);
panel.SelectRouteInsertMode("Insert Above");
Assert.Equal("Insert Above", panel.SelectedRouteInsertMode);
panel.SelectRouteWaypoint(0); // the @0 waypoint
automation.NavigationSnapshot = automation.NavigationSnapshot with
{ Position = automation.NavigationSnapshot.Position with { EastWest = 99d } };
panel.AddRoutePoint(); // must land BEFORE index 0, not appended
Assert.Equal(3, panel.RouteWaypointTextColumn.Count);
Assert.Contains("99", panel.RouteWaypointTextColumn[0]);
panel.SelectRouteInsertMode("Insert Below");
Assert.Equal("Insert Below", panel.SelectedRouteInsertMode);
panel.SelectRouteWaypoint(0); // the @99 waypoint
automation.NavigationSnapshot = automation.NavigationSnapshot with
{ Position = automation.NavigationSnapshot.Position with { EastWest = 77d } };
panel.AddRoutePoint(); // must land right AFTER index 0
Assert.Equal(4, panel.RouteWaypointTextColumn.Count);
Assert.Contains("77", panel.RouteWaypointTextColumn[1]);
}
[Fact]
public void RoutePauseSecondsFieldParsesAndClampsInput()
{
// Fix round B item 8: VTank's own txtPauseWaypointTime is a real
// editable field defaulting to "5", replacing the old "-"/"+"
// stepper (RoutePauseDown/RoutePauseUp, now removed).
var panel = new MossTankPanel(new FakeHost(new FakeAutomation()));
Assert.Equal("5", panel.RoutePauseSecondsFieldText);
panel.SetRoutePauseSecondsText("12");
Assert.Equal("12", panel.RoutePauseSecondsFieldText);
panel.SetRoutePauseSecondsText("99999");
Assert.Equal("3600", panel.RoutePauseSecondsFieldText);
panel.SetRoutePauseSecondsText("not a number");
Assert.Equal("3600", panel.RoutePauseSecondsFieldText); // unchanged on bad input
}
[Fact]
public void MetaTabEditsAndExecutesTheLiveStateMachine()
{