Skip to content

Combined 3: hire floors + spot check (#385), Zul'Farrak boss steps (#391), walk-super without eval, boss rows own their boss, pull_size lint (#398) - #401

Merged
pjerra merged 60 commits into
devfrom
batch/ta-combined-3
Nov 2, 2026
Merged

pjerra merged 60 commits into
devfrom
batch/ta-combined-3

Conversation

@pjerra

@pjerra pjerra commented Nov 2, 2026

Copy link
Copy Markdown
Owner

Fixes #266
Fixes #243

Replaces #385, #391, #398. Combines the branches named below into one dev PR (one CI run).

Hire floors + spot check (#385)

  • Every dungeon plan has tank_min_hp, scaled to the hired tank's level. Under the floor: a preflight warning, and a refusal under strict preflight.
  • A hire from the other team's commander is refused when the run passes team.
  • A hire from a spot more than 5 yd below the portal's ground floor is refused (navmesh floors, not the trigger centre).
  • A guessed commander height with stacked floors judges the LOWEST floor (fails safe). No navmesh: the spot is not checked, and a log line says so.
  • A stop during the check ends the hire as stopped, not failed.

Zul'Farrak boss steps (#391, parts A and B of #243)

  • Zel'jeb wing and Farraki Arena legs: a units hold plus gossip 62498. Gahz'rilla: two approach stands and her interruptible casts as boss tactics. Sergeant Bly's units hold in the run leader's rows.
  • New hold fields: reset_to_gossip (flag 16384) and skip_if_dead_of_gossip (flag 32768). A gossip may set dead_is_done. A cut hop keeps off the plan's leave spawns.
  • Route pin for zulfarrak is 66 with Zul'Farrak boss steps: Zel'jeb, Farraki Arena, Gahz'rilla tactics, Bly (with the dead-NPC and arena-reset hold flags) #391 (re-measured below).
  • Deploy warning: do not deploy ZF exports with the farraki_arena or bly legs to a server without TortoiseBots PR #1477 (fix/units-hold-reset). Without it the holds hang.

walk-super without eval

From 3162c31: the cmd file is parsed by a strict word parser (plain and double-quoted words only). Anything else ($, backticks, ;, &, |, redirects, globs, ~, #, a second line) refuses the file with exit 2 and nothing runs. The walk runs as nice -n 10 "${ARGS[@]}". A missing or non-numeric exit-status file is exit 70, "status unknown", and stops like a crash.

Boss rows own their boss (from ab4fd16)

A boss row now owns boss.entry and boss.phase_entries in the route check, so a final approach that walks up to its own boss is no finding. The 16 hand-written route pins are re-measured on this merged tree (zulfarrak 66 -> 56, the others as in ab4fd16 with the new base). The merge also fixed a compile break: routeBossByExportLeg takes the built rows' LegMap, since planexport.LegNumber now takes a map (#391).

pull_size lint (#398, part A of the camp-pull spec)

  • internal/aggroest: port of TortoiseBots' pull estimate (pack, live mobs within aggro reach of the walk and stand, 20 yd call-for-help ring, 2 hops, 12 yd height band).
  • Plan field max_bodies per pull group, with plan-wide pull_ceiling fallback; exported in the pull row's item column.
  • dungeongen -check pull_size findings. World or estimate load errors fail the check, with or without -strict.
  • zul_gurub: 95 pull_size findings (reported, input for camp-pull points).

Known limits

Verification

  • vm-test ta on the merged tree (02e91d5): PASS (rc=0), 1411 files.

pjerra and others added 30 commits October 21, 2026 22:19
…by its units, agent_only unit waits; Zul'Farrak exports without a cut

A boss's hold may leave instance_data out: plan-export writes the run leader's hold done by its units (entry 0, param 0, flags 0, the boss entry first, so a boss a script turns hostile later counts while still friendly). wait_for.agent_only names why a unit wait needs no row: plan-export leaves it out with a warning quoting the reason instead of cutting the plan. Validate takes the units hold as the loader does. A boss.first under a hold is warned as no kill order.

zulfarrak.json: the bly leg's three unit waits are agent_only, Bly's gossip is option 0, the boss has a units hold on the leg's spot (1884 1212 9, r25, 300 s); Murta, Oro and Raven join kill_first; the gahzrilla leg's Gahz'rilla wait is agent_only (the leg stays optional: its gong needs a row kind the module does not have). The whole plan now exports without --skip-legs bly.
…l writes an optional leg; Zul'Farrak's End Door wait is for active_alt

A via_item use_object (the item used ON the object) exports as TortoiseBots' use_on row: entry = the object, x,y,z = its near point, item = requires_item, after the move to its stand; Validate applies the loader's use_on rule. Options.WithOptional (--with-optional) writes a named optional leg into the rows with a warning that its item is the operator's to give to the lead bot before .bot run start (a GM .additem); a name that is no optional leg is refused. zulfarrak.json: the weegli_door wait for the End Door is for active_alt (state 2): Weegli's blast opens it with the alternative state (zulfarrak.cpp:423), and the module run r8 paused twice at a state-1 wait on an open door.
…Gahz'rilla dies at once, an invisible corpse at the pit); README: the gahzrilla export is TortoiseBots' opt-in file
…n Zul'Farrak's Bly gossip

Bly's crew is killed by the pyramid event's troll waves in some runs; the run leader then paused at the gossip and held 300 s on a dead hold. The flag lets the module count a dead NPC as done.
…s the east pool ring from within 50 yd (no navmesh hops of its own)
The hire spot judged the portal ground from a guessed z (a GM teleport,
climb or landing leaves session.StatusInfo.ZUnknown until the server sends
a position), so a guess could pass the drop check. AgentPosition now
returns whether z is the server's own; the step waits up to 10 s for it
near the portal and refuses with "hire: commander height unknown near the
portal" when it never arrives. Far from the portal an unknown height is
not judged.
Sergeant Bly's gossip row counts a dead NPC as done, but the units hold after it needed a unit seen hostile: a Bly killed by the pyramid waves leaves the crew friendly for good (zulfarrak.cpp npc_sergeant_blyAI turns them only from his own postGossipStep 3), so nobody turns hostile and the hold waited 300 s.

hold.skip_if_dead_of_gossip on a boss's units hold: plan-export writes flag 16384 (kPlanFlagDeadGossipOk) and the plan check wants the boss's gossip step with dead_is_done. The run leader ends such a hold when no unit of it is hostile; at once when all lie dead.
…nFlagResetToGossip

The dead-gossip hold flag moves from 16384 to 32768 (plan-export constant,
plan check, README, zulfarrak.json note, tests). kPlanFlagDeadGossipOk = 32768
on the TortoiseBots side is not written yet.
…oor (#266)

The hire spot compares the navmesh floor under the commander with the
navmesh floor under the portal point instead of the guessed height
against the trigger centre, so a trigger a few yards above the ground no
longer refuses a commander standing on it, and the 10 s height wait is
gone. A kept binding that already holds the wanted party is not checked:
a resume hires nothing new there.

The plan's tank_min_hp is set at the band's middle level; the preflight
scales it to the hired tank's own level with the 2026-10 test runs'
per-level health curve, rounded down to 50.
… the script resets it

hold.reset_to_gossip (a boss's units hold only, never with skip_if_dead_of_gossip;
the leg's last event-start step must be the boss's gossip) exports as flag 16384,
kPlanFlagResetToGossip on the TortoiseBots side (#1455). skip_if_dead_of_gossip
stays 32768. Validation, plan-parse tests, the arena row pin and the export row
test cover both flags; README and the arena note say which row takes which.
The cut hop is the exporter's own walk. A hop whose cut points would come within
LeaveClearYd (15 yd) of a leg's leave spawn is left uncut, with a warning, so the
run leader hops it itself as it did before --approach-hops: Zul'Farrak's island
Shadowhunter 81610 otherwise stood 9 yd from the cut path to the velratha leg's
pull group 4. README says so.
omp and others added 23 commits November 2, 2026 22:49
LegNumber and PlanLeg treated optional and skip_export legs as absent even
when --with-optional or --with-legs exported them, so firstLeaveConflict
checked a cut hop against the wrong plan leg's leave list.

The export now builds one LegMap (plan leg index -> row leg number) from the
legs it writes, by name, with ExportedLegs, and passes it to LegNumber,
PlanLeg and firstLeaveConflict. Rows.LegMap matches a rows set's legs to the
plan by name for the callers that hold rows. The default export is unchanged.
A mob no row owns reads 0 from the owned map, which is also the
route's first row index: the first row could pick a stranger.
TestPullSizeTargetIgnoresUnownedMobAtRouteStart pins both sides.
Matches worldspawn.AggroRadius (0 for detection under a yard, like
NO_AGGRO). pull_size keeps its own explicit mapping: its 0 means the
export predates the column, read as 20 yd. TestNoDetectionRangeNeverWakes
mirrors the worldspawn row.
The Bly gossip option lets the export reach Bly and Ukorz, so the count covers
more of the run. Two Sandfury Guardian passes on Ukorz's leg (19.5/21.6 yd,
later rows' own packs) and Ukorz's own 0.1 yd final approach (no row: his boss
row names only Ruuzlu in first, and the loader refuses 7267 there); the
Courtyard via removed two. Fixes the comment that claimed the old baseline saw
the whole run.
pullMobs uses the route lint's filter (hostileMob, shared with
RouteIntoLaterPacks, not a copy): no NPC flags, no ignored or
friendly faction (35 is friendly to all, as in Check), no critters.
Rows gated to the other team are not pulled by this run: routePoint
carries the plan's team gate off the export flags and PullSizes skips
those rows (no pull number consumed). No pinned count moves: the
fixtures' counted rows are all hostile and ungated.
TestPullSizeIgnoresFriendlySpawnsAndOtherTeamRows pins the friendly
neighbour and the gate; TestPullSizeTargetIgnoresUnownedMobAtRouteStart
pins the owned presence check.
Over-ceiling rows are warnings for a person; only -pull-size-strict opts
into the ceiling. No-target rows and load errors still fail -strict.
checkFile takes the ignore factions and the pull-size-strict flag, and
the N warning(s) summary line is back. Tests: the committed uldaman plan
passes -strict on its 58 over-ceiling warnings (its no-target row still
fails it), and a synthetic over-ceiling row pins the strict matrix.
Pull 14 (the row over the G13/G16 terrace, export step 29) estimates 27
bodies; the neighbours 13, 16, 17 estimate the same set, 15 and 18 break
away at 31. RingYd 10 drops pull 14 to 22 (red, then restored).
…lk is floor-checked

The settle walk for a guessed height over two floors tried a 3 yd step east
first, and a step that left the 15 yd portal radius passed the hire as
allowed without the portal-ground floor check. The walk now picks only
targets inside the radius, the one nearest the portal first, and a settled
height is judged on its floor wherever the walk ended.
The route check gave a boss row ownership of its First entries only. A final approach that walked
up to its own boss (Zul'Farrak Ukorz 7267 at 0.1 yd) was reported as an unowned pass. A boss row now
owns boss.entry and boss.phase_entries too; the export carries neither.

Re-pinned the 16 hand-written plans with the old count in each comment: boss rows own their boss.
277 findings dropped, none added (168 lines changed only their owner text, to the boss row).
- The cmd line is split into argv by a strict word parser: plain words and
  double-quoted words only. $, backticks, backslashes, ; & | < > ( ), other
  quotes, globs, ~, # and a second line refuse the file (exit 2, nothing runs).
  The walk runs as nice -n 10 "${ARGS[@]}"; there is no bash -c re-parse.
- A missing or non-numeric exit-status file is exit 70, logged "status
  unknown", and stops like a crash (no restart).
- Tests: cases 16-18 (injected operators refused with nothing run; status
  unknown with a missing and a malformed file; walk-28 shaped argv matches the
  literal list and the old eval parse). make_cmd writes double-quoted words.
The pull lint dropped every npc_flags spawn and every faction-35 spawn,
so the -awakened and -scripted entries (Uldaman's Stone Keepers, scripted
gossip NPCs) never joined a pull. A pull row that owned only such a spawn
reported "no target" instead of its body count.

PullFilter now carries the check's awakened and scripted sets. A spawn of
those entries bypasses the npc_flags, faction 35, friendly and ignore
exclusions; the critter rule still applies. hostileMob is unchanged for
the route lint.

Test: four awakened Stone Keepers at one stand with ceiling 3 count 4
bodies, and the same for a scripted spawn with npc_flags.
…batch/ta-combined-3

# Conflicts:
#	tools/dungeongen/route_aggro_test.go
…tch/ta-combined-3

# Conflicts:
#	tools/dungeongen/route_aggro.go
… re-pin route counts after the boss-ownership merge
…tAtBly) went with the Zul'Farrak boss steps (staticcheck U1000)
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