Skip to content

Merge dev into main (#445-#455) - #456

Merged
pjerra merged 148 commits into
mainfrom
dev
Nov 13, 2026
Merged

pjerra merged 148 commits into
mainfrom
dev

Conversation

pjerra added 30 commits November 8, 2026 06:43
…_cancel and trade_status RPCs and cast trade_slot
…tep windows released, unaccept on timeout, changed result, exact arm name, live arm blocks, own-side set confirmation
turtle-agent: step trade RPCs and Eluna smoke coverage
… point

Cause: the spread=5 place drops seats onto a disconnected navmesh island beside
the ZG Jin'do place point, where every move fails with NOPATH. Rule: under 2 yd
moved in 10 s, inside spread+6 yd of the point, over 35 yd from the MT, MT over
15 yd from the point, judged on the poll's time. Action: .go xyz onto the MT at
z+2, landing confirmed, then .summon; at most 2 re-places per hire, with failure
caps. Re-armed on every placement move. --no-place-unfreeze turns it off.
raid-walk: re-place hires frozen on a navmesh island at the placement point
Allow tortoise-test3 for --place/--gm-clear/--gm-instance-data; never lease slot 3 there
The core's realmd throttle compares last_login > NOW() - '300' as numeric
date maths, so in minutes :00-:02 of every hour it counts every account ever
seen from the IP and refuses logins with code 0x08. login and the reconnect
path now wait until hh:03:05 and log one line. TA_LOGIN_HOUR_HOLD=0 turns it off.
.bot role only changes a bot's strategies; a hunter hired with no spec keeps the
default Survival premade. For a hunter batch the walk now sends TB's
'.bot command <bot> talents <path>' before the role, reads the build back with a
bare 'talents' and checks the main tree. A build that does not take logs
'hire: spec <x> not applied to <bot> (<reason>)' and the walk carries on.
'sv' is accepted for survival. The allow-list takes only that command form.
…clock moves on

TestAreaTriggerStepEndsAtOnceAndTheBossStepOwnsTheFight failed under -race with a
trigger step of 61 s. agentWalk runs Follower.WalkSelf in a goroutine while the
driver ticks; the fake clock advanced virtual time without ever yielding, so under
load 60 virtual seconds (triggerWalkMax) passed before the goroutine got a turn.
Clock gets an optional Settle hook, called once after a walk goroutine starts; the
nearGame's fake clock uses it to wait until that walk has returned or blocked.
Reproduced 2 in 300 runs under CPU load before, 0 in 1500 after.

Fixes #441
…dow from hh:59:57, jitter

The hold sits in the login child, so every parent that waits for the child to reach the world now adds it:
loginDeadline(now, base) in raid-walk's phaseLogin and plan-scout's loginPhase (they log 'waiting for the realmd
hour-start window'). The fleet books a member at the end of the window (bookFleetLogin) and waits before it starts the
child; a reconnect attempt plans its wait to the window's end and notes its realmd window after the hold. After the hold
a per-process 0-20 s jitter (injectable). The window starts at hh:59:57 for clock skew; the README notes the whole-hour
time zone assumption. Tests pin the hold's wiring in loginCmd and reconnector.login, the fleet bookings and phaseLogin.
… settle

The fake clock's Settle used to give up after 30 s and carry on silently; it now fails the test, so a stuck walk goroutine shows as a failure and not as a wrong time.
…read-back

The walk still carries on when a hunter's talent build does not take (one bot must not abort a raid), but each failure
is now in summary.json as spec_failed [{bot, spec, reason}], summary.txt and the walk's last stdout line say
'WARN: hunter spec not applied: N (<bots>)', and the exit code stays 0. A refusal in TB's apply answer (talent link is
invalid, you may only command, has no ai yet, plus the two already known) counts as not applied even when the read-back
looks right. The read-back is accepted only from that bot's own chat or whisper, not from system or addon lines or
another bot. waitBotWords puts the events cursor after the matched event, so the rest of the page is read by the next
wait. A fixture carries TB's exact coloured read-back text.
…e window, reconnect rechecks, real elapsed time

The fleet booked exactly at the window's end and started the child then, so held members all fired at hh:03:05 and the
child added no jitter. bookFleetLogin now samples the jitter and books window end + jitter; the child starts at that time.
Parents (phaseLogin, loginPhase, fleet) look 5 s ahead of their clock for the window, so a parent at hh:59:56.99 still
allows for the hold. reconnector.wait checks the hold again after the budget or window pushed the start. The login
timeout messages name the elapsed seconds. README and the roster doc name the talent read-back limit (tree with the most
points, not the exact premade spread).
…elf to return

The previous Settle waited until the fake's WalkSelf had returned, but agentWalk's goroutine still had to send its
result; under heavy load that send could be starved for 60 virtual seconds, so TestAreaTriggerStepEndsAtOnce still
failed (4 of 3000 runs with 14 busy loops). Clock.Settle now gets a function that says whether the result is delivered,
and the fake waits for that, or for a walk that blocks on its context. 0 failures in 8000 runs under the same load.

Fixes #441
…e the booking

reconnector.wait booked a slot at the time the budget allowed and then added the hour-start hold and jitter on top, so
the stored slot stayed minutes early and expired while the real login was still inside realmd's 300 s window.
loginBudget.reserveAdjusted(earliest, adjust) finds the earliest allowed time, applies the adjustment, and looks again
from the adjusted time until it is stable, all under the budget's lock, and stores that time. The supervisor's /login
applies the hold and the jitter this way (jitter sampled once per booking); bookFleetLogin uses the same call. The
reconnect's wait is the supervisor's final answer; without a budget it applies the hold once to its own final time.
Hold realmd logins at the hour start, hunter roster spec sets talents, fix the area-trigger test flake
zulfarrak's deployed migration ends in hand-set rows (20261102190000 line 399: UPDATE ai_dungeon_plan, then UPDATE
ai_dungeon_step for the area trigger). ParseMigration called that line unexpected and read no rows at all, so plan-check --sql
and plan-probe --sql could not read the file. It now reads the INSERTs above, stops at the statement, and says which line it
stopped at in the notes the commands print. Strict Parse still refuses it.

Test: TestParseMigrationStopsAtATrailingHandEdit.

Route audit 2026-11-12, batch B0 item 3.
…the mmaps cell line

A cut point whose x or y is an exact multiple of the server's 5.3333 yd mmaps cell stands on a tile seam, and the server's
PathFinder answers the hop to it NOPATH: 14 rows in 11 plans in the route audit of 2026-11-12 (finding 3). plan-probe on each of
the 14, against ~/turtle-agent-mmaps with the point moved 0.5 yd back along its hop: 13 cleared (server NOPATH gone), naxxramas
step 637 (3520.0 -5269.3 138.0) stayed NOPATH at 0.25, 0.5, 1, 2 and 4 yd, so that one is a steep ledge and not a seam.

The cutter asks the server's own query (navmesh.Planner.ServerPath) about a seam point, and moves it SeamNudgeYd (0.5 yd) back
toward the point its hop came from only when the server refuses the hop to it and reaches the moved one, with a note on the
moved row. A seam the server walks keeps its place (a re-export of the 11 plans moves 13 rows, not the 964 seam points), and so
does one whose seam the hop runs along or whose move the server refuses too.

Test: TestCutPointsTheServerRefusesAreNudgedOffTheCellLine (cutWith over a fake planner: refused seam moved 0.5 yd off both
lines with one note; a walked seam unchanged; a refused move unchanged) and TestOffSeamNudgesOnlyASeamTheHopCrosses.

Route audit 2026-11-12, batch B0 item 1.
RunLeaderReach reads the plan's own rows, so it named every pack or boss more than 50 yd from the row before it even when
--approach-hops cut that hop into approach rows: 3,412 warning lines in the route audit's sweep of 2026-11-12 (finding 8), and
they meant nothing for the plans exported with the flag.

cutLongHops now records each pack or boss whose cut left the party within the run leader's reach on the last approach row it
wrote, and Build prints no hop warning for it. A cut that failed, that was dropped by the leave-conflict rule, or whose last row
still stands beyond the reach keeps its warning.

Test: TestRunLeaderHopWarningOnlyForHopsLeftUncut (a cut hop loses the warning; the same plan without --approach-hops keeps it;
a cut ending 70 yd out keeps it).

Route audit 2026-11-12, batch B0 item 4.
export-a-plan: a cut point the server refuses on a mmaps cell line moves 0.5 yd off it, and the run leader hop warning names
only hops left uncut. probe-a-plan and ref-check-tools: the --sql reader stops at the first statement that is no INSERT and says
which line.

Route audit 2026-11-12, batch B0.
…alk to

The audit's 15 NOPATH rows were hold and gossip rows standing on the spot the previous row had reached. Skip a hold or
gossip only within 5 yd of that point; keep the skip for loot, use_item, wait_object and wait_clear.
… z and a normal path ending on the point

Accept a nudged seam point only when the floor lookup finds plain ground at the new XY (its z replaces the old z), the
server query succeeds, the path type is Normal, the verdict is ok, and ActualEnd lies within the 1 yd arrival tolerance.
Skip a non-INSERT statement (an UPDATE, a START TRANSACTION wrapper) to its terminating ; and read the rows after it,
naming each skipped statement and saying its hand edits are NOT applied.
pauseRun returns typed errRunNotRunning only when the server reply carries the already-gone answer (no-run/not-running code or message); transport, poll and interrupt errors never match. practiceReset checks errors.Is, no substring on the whole error.
pauseRun wraps errRunNotRunning only when the reply's code is the module's not-going code: no-run (no run slot, Commander.cpp:15381) or state (slot not Running, :15466-15468). practiceRunNotGoing takes the structured reply code; every substring match on refusal text is gone, and a different code carrying the phrase fails the reset.
plan tools: seam nudge, reach lint for non-moving rows, the --sql reader skips hand edits
p.runGone leaked across resets: once set, every later reset skipped its resume. The skip is now a local of practiceReset. state means the slot is not Running (Commander.cpp:15466), so after it the reset reads one run status: Paused resumes afterwards, Done or absent skips it; no-run still skips without reading. Codes only, no message matching. Docs say not running (paused, done or gone).
The state pause refusal's status read now returns a three-way state: Paused resumes after the reset, Done or no slot skips the resume, and Running, an unrecognised state, or a failed read refuses the reset exactly like any other pause failure instead of continuing unheld.
practice: a reset pause refused because the run is not running no longer ends the attempt
Run's pre-start checks (the team gate, a skipped leg's item, the graves and GM cross checks) returned
before rep.start and finish, so the report kept state idle with no error: dungeon_status showed running
true for a run that never ended (SFK-NEVER-STARTS). They now end the report refused with the reason in
error and a note, after emitting dungeon_refused; the run goroutine logs the driver's error instead of
dropping it. Docs name the new state and event.
The docs that list dungeon_status states (the RPC reference, the guide) and the
LIVE-CHECKS end-event kinds missed the new state and event from the refusal
change: dungeon_run can now end "refused" with the reason in report.error, and
dungeon_refused is emitted before it. A test runs the from_leg-out-of-range
refusal (the second early return, not the team gate) and checks the state, the
note and the single event.
dungeon_run: a refused start ends as refused instead of idle forever
Walk 97 fight 9-1: the agent's RPC died mid-Hakkar (exit 18) and the
--reuse-instance restart resumed the bound instance while Hakkar and his
arena adds were still engaged. The quiet dismissed them (beyond the 25 yd
landing reach, BEHIND) and the summon dropped the raid into the fight.

Before a reused or resumed placement summons anything, read the plan boss
arenas (`q units 60 <anchor> all`, the toolkit's `cb`): the boss and every
unit within 60 yd of a boss anchor that stands within 150 yd of the landing
must be out of combat. An engaged arena gets no placement: the walk polls
the reading for up to 90 s, and a fight that does not reset ends with exit
29, the existing fresh-instance request (the autopilot retries without
--reuse-instance, and that path unbinds the stale bind).

Tests: a resumed instance with the boss in combat places nothing, waits and
asks for a fresh instance (the old code places and exits 0); a fight that
resets during the wait places as before; a clear arena places as before.
The guard's final pre-summon read waited up to 90 s for a fight it saw to
reset, then summoned on the earlier quiet check: a patrol can walk into
the landing during that wait, so the last read no longer described the
landing that received the raid. An engaged final read now ends the
placement at once (exit 29, the fresh-instance path) and logs it; the
early read before the GM clear and the quiet keeps its reset wait.

Also read a boss fought on the spot: his hold row ("boss hold") carries
the arena and the boss as its first entry, so an engaged Vaelastrasz,
Nefarian or Zul'Farrak arena is no longer invisible to the guard.
raid-walk: never place a raid into a resumed instance whose boss fight is still live
…world buffs on at each boss row's pre-pull rest

A walk that tests preparation (walk 100: tier 1 gear and full consumables) has to carry the same auras a real raid
enters with. --prepull-aura role=<tank|melee|ranged|caster|healer|all>:<id,id,...|preset> is repeatable, one value per
role; it fires at the pre-pull gate the --gm-instance-data boss-row resend already uses (an out-of-combat rest or
gather of the step before a boss row, no later pull line of the page, no party bot in combat), once per boss row per
walk, under a run pause that is always resumed. A wipe recovery clears the fired rows, so the boss row the recovery
retries is buffed again (a death stripped the auras).

The auras go on with the dungeon_run buffs kernel: internal/dungeon now exposes AuraPort, AuraCfg, ApplyAuras and
AurasMissing, and both the dungeon Driver and raid-walk's port use them, so the first-aura check, the single retry and
the 'landed on the caster is a miss' rule have one implementation. A new gm_aura RPC selects the player by guid, waits
for the server to show that selection and sends '.aura <id>' only while it stands; it is gated like gm_kill (test
servers only) and refuses a non-player guid or spell id 0.

prepull-auras.json records the flag, the merged role lists, the boss rows fired and every bot's ids sent, seen and
missing; a bot missing more than 2 fails the walk (exit 18). The mt/ot batches are tank and every other bot takes its
class's role. 'world' is the new world-raid preset (the classic world buffs without Warchief's Blessing, which is
Horde-only); dungeons/prepull/zg-preraid.json holds the ZG walk's sample lists, every id checked in the core's
spell_template.
…isses

A refused, unacknowledged or unconfirmed run pause now sends no aura: the
fresh run status must show this plan paused on the rest step, party combat is
re-read right before the first .aura and before every later send, and a pull
mid-batch stops the batch and fails the walk. A boss row that becomes current
without a verified fire fails the walk, and a row is recorded as done only
after the verified application.

Every requested aura must verify by default. --prepull-aura-tolerance N is
the explicit opt-in for a per-bot miss bound; prepull-auras.json always
carries the missing list, and a tolerated miss is logged as such instead of
claiming every bot carries its list.
…mpts

The combat recheck re-reads every living party member, not only the members
--prepull-aura gives a list: a member the flag does not cover can pull too. A
failed or unreadable unit read now counts as combat, so the batch stops and the
walk fails instead of sending auras on a party it cannot show is out of combat.

prepull-auras.json no longer records a failed fire as a boss row. boss_rows
holds only the rows whose fire verified within the tolerance; a failed attempt
goes to attempts with its boss row and reason, and the file is written on every
fire so the evidence is always on disk. Tests cover an in-combat member without
a list, an unreadable unit and a failed attempt; each fails on the previous
code.
staticcheck S1009: len() of a nil slice is zero, so the nil test before the
three-missing check adds nothing.
raid-walk: --prepull-aura applies role buff lists once before each boss pull
@pjerra
pjerra merged commit 9ab18dc into main Nov 13, 2026
5 checks passed
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