Skip to content

Hold realmd logins at the hour start, hunter roster spec sets talents, fix the area-trigger test flake - #442

Merged
pjerra merged 10 commits into
devfrom
fix/ta-1108c
Nov 9, 2026
Merged

pjerra merged 10 commits into
devfrom
fix/ta-1108c

Conversation

@pjerra

@pjerra pjerra commented Nov 8, 2026

Copy link
Copy Markdown
Owner

Three changes for raid walks and trials. Draft until the next ZG walk runs on this build.

1. Hold realmd logins at the top of the hour

The core's realmd throttle compares last_login > NOW() - '300' as a number. In minutes :00-:02 of every hour it counts every account ever seen from the IP. Behind docker NAT every outside client shares one IP, so logins are refused (code 0x08).

  • Any realmd login whose start falls between hh:59:57 and hh:03:05 UTC waits until hh:03:05. It logs one line: login: waiting <s> s for the realmd hour-start window.
  • The hold sits on the two realmd login paths, the login command and reconnector.login, so raid-walk, raid-autopilot, plan-scout, lease and slot setup are all covered.
  • Parents that wait for a login child (raid-walk phaseLogin, plan-scout loginPhase) allow for the hold, looking 5 s ahead of the window.
  • The fleet supervisor's login budget stores the final login time: the hold plus a 0-20 s jitter, applied inside one booking. The fleet's --wait and reconnects both follow it.
  • TA_LOGIN_HOUR_HOLD=0 turns it off.
  • Known limit: the window assumes the DB's NOW() runs in a whole-hour time zone.

2. A hunter roster spec sets the talent build

  • For a hunter batch with spec (bm, mm, survival; sv is accepted), the walk sends .bot command <bot> talents <premade>. It then reads the build back from the bot's own chat or whisper, and only then sends .bot role.
  • A refusal (talent link is invalid, you may only command, has no AI yet), a missing read-back, or the wrong main tree logs hire: spec <x> not applied to <bot> (<reason>). It also lands in summary.json as spec_failed, with a final WARN: hunter spec not applied: N (...) line. The walk carries on, and the exit code stays 0.
  • Known limit: the read-back checks the main tree (BM/MM/Survival), not the exact premade point spread.
  • The allow-list accepts only .bot command <hire> talents [beastmastery|marksmanship|survival].

3. The TestAreaTriggerStepEndsAtOnce flake

Fixes #441.

  • Cause: the fake clock advanced virtual time while the agent's walk goroutine had not yet delivered its result, so under load the step read 61 s.
  • Fix: an optional Clock.Settle hook (nil for the real clock) that waits for the walk's result, and fails the test after 30 s.
  • Proof: under CPU load (GOMAXPROCS=2, 14 busy loops) the old code failed 4 times in 3000 runs; the fix had 0 failures in 8000 runs.

Tests

  • Full vm-test ta passes on a795bd5.
  • Targeted -race runs pass: the login hold, budget, fleet and reconnect tests ×20, the hunter spec tests ×3, and the flake test ×20.
  • Every new test was mutation-checked against its fix.

Reviews

  • 3 rounds of Codex adversarial review and an Opus cold review.
  • Rejected by design: an exact-premade check (see the known limit above).

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.
@pjerra
pjerra marked this pull request as ready for review November 9, 2026 07:05
@pjerra
pjerra merged commit 8c8368e into dev Nov 9, 2026
2 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