Skip to content

Let Walk run until something stops it - #79

Open
Gaetarra wants to merge 5 commits into
soymods:mainfrom
Gaetarra:feature/walk-sustained
Open

Let Walk run until something stops it#79
Gaetarra wants to merge 5 commits into
soymods:mainfrom
Gaetarra:feature/walk-sustained

Conversation

@Gaetarra

@Gaetarra Gaetarra commented Aug 29, 2026

Copy link
Copy Markdown

What This Changes

Stacked on #78. This branch is based on feature/alert-node, so GitHub
shows that PR's commits here too. The two commits belonging to this PR are
25d5111 and 4fea00e. If #78 lands first this rebases to nothing; if you'd
rather I rebase onto main and drop the dependency, say so.

Walk could only run for a fixed duration or distance. "Walk until X" had to be
built out of Repeat Until, which re-ran the node every iteration and released
the forward key between them, so the player stuttered instead of walking, and a
second Walk could not extend the first without ending it.

  • WALK gains modes. Walk For is the existing timed/distance behaviour
    and stays the default. Start Walking holds forward and completes
    immediately, the way SPRINT already does. Stop Walking releases it.
  • WalkHold owns the forward key and counts holds. Previously each Walk
    wrote client.options.keyUp directly, so any walk finishing released the key
    out from under every other walk still running.
  • Walk's Direction slot is removed. LOOK already aims the player, and
    carrying a second way to do it only added a slot to fill.
  • Both remaining slots stop being hard requirements. A required slot fails
    the node outright at execution time (NodeExecutionCoordinator), which is why
    an unbounded walk was not expressible at all.

Verification

Check Status
./gradlew :common:test -Pmc_version=1.21.11 Not run locally. Relying on CI. WalkHoldTest is added but has never been executed; the hold counter's semantics were checked standalone instead.
verifyCompatibilityManifest verifyCompatibilityStructure verifyBuildGenerationRouting Not run locally. Relying on CI.
Compiles on 1.21 / 1.21.8 / 1.21.10 / 1.21.11 Not run locally. Relying on CI.
Compiles on 26.1 / 26.2 (-p mc26, both loaders) Fabric passes on both. NeoForge passes on 26.1.2. NeoForge on 26.2 fails with Could not find net.neoforged:neoforge:unsupported — pre-existing, see #78.
Clicked through the affected UI in a dev client Yes — selector opens, Start/Stop Walking show no slot, Walk For still takes a duration, walking with an empty Direction works.

Notes For Review

Backwards compatibility. Two things change under saved presets, and both are
handled:

  • Presets saved before Walk had modes carry no mode, so NodeGraphPersistence
    skips setMode and the node keeps the constructor default of WALK_FOR,
    whose parameters have the same names and defaults as the old type parameters.
    They restore unchanged.
  • Removing the Direction slot shifts duration/distance from slot 1 to slot 0.
    Attachments persist by explicit slot index, so old presets would silently lose
    their duration node. NodeGraphPersistence remaps 1 -> 0 for WALK on load
    and drops whatever was in the old slot 0, which could only have been a
    direction. This is the part most worth a second pair of eyes.

Known limitation, deliberately not fixed here. WalkHold arbitrates Walk
against Walk only. NavigatorPrimitiveExecutor drives keyUp as a per-tick
servo loop at roughly fourteen sites and will still overwrite a hold while
pathfinding is active. That conflict predates this change — a Walk node running
alongside pathfinding already fought over the forward key — and making the
navigator a hold participant is a much larger patch. There is a comment on
WalkHold saying so.

Semantics deliberately preserved. Walk For with neither duration nor
distance still completes as a successful no-op, unchanged, rather than becoming
an unbounded walk. Making empty mean "forever" would have changed the meaning of
existing saved presets silently; the explicit mode avoids that.

Stop-all releases the sustained hold explicitly, since by design it outlives the
node that started it.

Gaetarra and others added 5 commits August 29, 2026 14:08
Automation runs while the player is tabbed out or AFK, so a graph needs a way
to reach someone who is not looking at the screen. Send Message only reaches
chat, which nobody sees while a farm runs unattended.

Alert has two modes. Play Sound takes any vanilla sound id plus a volume, and
plays it through the sound manager with SimpleSoundInstance.forUI so the alert
is not attenuated by where the player happens to be standing. Send Webhook
POSTs the node's text to an endpoint configured in settings, which is what
reaches a phone when nobody is at the machine.

Non-obvious calls made here:

- The node completes its future as soon as the notification is dispatched
  rather than awaiting the response. An alert must never stall the graph it is
  reporting on, so webhook failures are logged rather than propagated.
- Webhook sends are rate limited to one per three seconds. An Alert placed
  inside Forever would otherwise hammer the endpoint, and Discord kills
  webhooks that are hammered.
- Payload shape is chosen by host: Discord needs a JSON body, ntfy takes the
  raw text. This is a deliberate two-case heuristic rather than another mode.
- The URL is validated once in SettingsManager.sanitizeWebhookUrl and https is
  required. It is a user-supplied string driving an outbound request from the
  game client, so it is checked at the boundary instead of at each call site.
- Alert reuses the multi-line text fields that Send Message and Calculate
  already have, by joining NodeTextContent, so message text supports runtime
  variable interpolation with no new UI.
- Toast was considered and left out: it only helps someone already watching
  the screen, which is not the case this node exists for.

Not yet wired: the webhook URL has no settings-popup field, so it is set by
editing pathmind/settings.json. That control lives in a Stonecutter source
file and is left for a follow-up so this change stays reviewable.

Verified: Fabric compiles on 26.2 and NeoForge on 26.1.2, and the generated
mc26 source is byte-identical to the authored file, so no string transform was
needed. The escaping and URL-validation logic was checked separately against
the same inputs as NodeAlertTest. Not verified locally: :common:test and the
1.21.x targets, which need a JDK 21 toolchain; CI covers both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NodeMode has getModesForNodeType, not getAvailableModesForNodeType, so the
assertion would not have compiled.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Alert shipped unusable: it rendered only its message field, so the Sound and
Volume parameters were invisible and there was no way to switch to Send
Webhook. Its mode names also displayed as raw keys.

Two causes, both missed because the editor has no automated coverage:

- NodeGraph.rendersInlineParameters decides whether a node draws its parameter
  strip, and the mode selector is drawn inside that strip. It is true only for
  parameter nodes and for types tagged RENDER_INLINE_PARAMETERS. Alert was
  neither, so the strip that would have held both never rendered.

- NodeMode.getDisplayName ignores the display strings in the enum constructor
  and builds "pathmind.node.mode.<name>" as a translation key. Without lang
  entries the modes rendered as pathmind.node.mode.alert_sound.

Verified in a dev client: the mode selector opens, Sound and Volume are
editable, and both modes read as English.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Walk could only run for a fixed duration or distance. "Walk until X" had to be
built out of Repeat Until, which re-ran the node every iteration and released
the forward key between them, so the player stuttered instead of walking, and a
second Walk could not extend the first without ending it.

Three changes:

- WALK gains modes. Walk For is the existing timed/distance behaviour and stays
  the default. Start Walking holds forward and completes immediately, the way
  Sprint already does. Stop Walking releases it. Presets saved before this land
  with no mode, so they take the constructor default of WALK_FOR, whose
  parameters have the same names and defaults as the old type parameters and
  restore unchanged.

- Both Walk parameter slots stop being hard requirements. A required slot fails
  the node outright at execution time (NodeExecutionCoordinator), which is why
  Walk demanded a Direction even though Look already aims the player, and why an
  unbounded walk was not expressible. Walk For with neither duration nor
  distance still completes as a no-op, unchanged.

- WalkHold owns the forward key for Walk nodes and counts holds. Previously each
  Walk wrote client.options.keyUp directly, so any walk finishing released the
  key out from under every other walk still running.

Stop-all releases the sustained hold explicitly, since by design it outlives the
node that started it.

Known limitation, called out in a comment on WalkHold: this arbitrates Walk
against Walk only. NavigatorPrimitiveExecutor drives keyUp as a per-tick servo
loop and still overwrites a hold while pathfinding is active. That conflict
predates this change; making the navigator a hold participant is a larger patch
and belongs on its own.

Verified: Fabric compiles on 26.2 and NeoForge on 26.1.2, the mc26 source
transform leaves WalkHold byte-identical, and the hold counter's semantics were
checked standalone. Not verified locally: :common:test and the 1.21.x targets,
which need a JDK 21 toolchain; CI covers both. The editor behaviour, in
particular the mode selector on Walk and the now-optional slots, has not been
clicked through in a dev client.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follow-up to the Walk modes, from testing the editor.

- Walk no longer has a Direction slot. Look already aims the player, so the
  second way to do it only added a slot to fill. Presets saved with the old
  layout are migrated on load: duration/distance moves from slot 1 to slot 0,
  and whatever sat in the old slot 0 aimed the player and is dropped. The two
  orientPlayerTowardsRuntimeTarget calls go with it.

- The Duration/Distance slot only exists in Walk For. Start and Stop Walking
  have nothing to time, and Node.getParameterSlotCount now returns 0 for them,
  so the slot is not drawn.

- Walk is tagged RENDER_INLINE_PARAMETERS. Without it NodeGraph.rendersInline
  Parameters is false, the parameter strip is never drawn, and the mode selector
  that lives inside that strip is unreachable, which is what made the modes
  invisible when they were first added.

- Mode display names get lang entries. NodeMode.getDisplayName ignores the
  strings in the enum constructor and builds "pathmind.node.mode.<name>" as a
  translation key, so the modes rendered as pathmind.node.mode.walk_for.

Verified in a dev client: the selector opens, Start/Stop Walking show no slot,
Walk For still takes a duration, and walking with an empty Direction works.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant