Skip to content

Bugfix: 50+ fixes — P0 crashes, P1 behavior, P2/P3 logic - #2

Open
vorobjewsen30-max wants to merge 244 commits into
zoyluoblue:mainfrom
vorobjewsen30-max:main
Open

Bugfix: 50+ fixes — P0 crashes, P1 behavior, P2/P3 logic#2
vorobjewsen30-max wants to merge 244 commits into
zoyluoblue:mainfrom
vorobjewsen30-max:main

Conversation

@vorobjewsen30-max

Copy link
Copy Markdown

Summary

Comprehensive bugfix PR addressing 50+ bugs found through systematic code audit of all 164 classes (30K LOC).

P0 — Crash / Data Loss (7 fixes)

  • Direction up→down swapped: AIBotTaskSubcommand.javaup/u mapped to Direction.DOWN
  • Standability slab collision: Partial blocks (slabs, stairs) rejected as non-standable → pathfinding broken in builds
  • InventoryAction dropJunk data loss: Removed items then spawned NEW ItemStacks, losing NBT/enchantments
  • GoalPlanner visiting-set infinite recursion: Visiting key included desiredCount, allowing undetected cycles → StackOverflowError
  • AIPlayerEntity NPE swallow: NPE from super.tick() logged but silently ignored, now re-throws with full trace
  • MiningAction missing STOP: mineOnceInstant only sent START_DESTROY_BLOCK, never STOP — block never broke in survival
  • SmeltTask distant container mutation: fetchFuelFromBase mutated container inventory 100+ blocks away — server silently reverted

P1 — Wrong Behavior (12 fixes)

  • BrainCoordinator busy-flag race: Unsynchronized read → bot permanently stuck in busy state
  • BrainCoordinator ArrayDeque: Unsynchronized ArrayDeque.history mutations → corruption
  • DeepSeekApiClient NPE: choice.getAsJsonObject(\"message\") returns null on API errors
  • ToolRegistry NPE: null params in assign_task handler NPEs
  • WalkToController 2D arrival: Y coordinate ignored — bots 'arrived' when directly above/below target
  • MiningController state-change race: Reset then re-initialized in same tick
  • CraftTask duplicate table step: No dedup check when prepending crafting table
  • MiningChain lapis bestY=-1: Dragged diamond target Y up, preventing deep diamond mining
  • AIBotMemorySubcommand G1 violation: Silently killed active goals
  • PerceptionCollector radius hardcap: Config promised radius=16, actual behavior capped at 8
  • and more...

ZoyLuo and others added 30 commits May 31, 2026 01:47
GF-1 (P0): bidirectional orchestration gate + no step-skipping
- GoalExecutor.tickBot: foreign-task detection (player explicit command
  abandons plan and yields), hasPaused gate (wait for danger-watcher resume
  instead of skipping the paused step), removed unconditional assignNext
  fallthrough (step advance only via COMPLETED branch).
- BrainCoordinator.maybeWakeForFailureOrGoal + IdleCoordinator.tickBot:
  yield to GoalExecutor when an active plan exists (no assign races).
- ActivePlan tracks currentTask instance.

GF-2 (P1): planner counts + mining safety
- GoalPlanner.smeltItem: fuel ceil(missing/1.5) not 1:1 (~33% over),
  smelt input only fills the gap (reuse existing raw drops).
- MineTask.startMiningTarget: pre-mine lava-adjacency gate.

GF-3: verify + polish
- mine_iron_from_scratch: 4->6 logs, 3->6 stone, timeout 3600->12000.
- GoalExecutor.submit idempotent (ignore duplicate active goal).
- GoalPlanner: smelt fuel prefers any owned log; removed dead imports
  (InventoryAction, LinkedHashSet) and unused DEFAULT_MAX_DEPTH.

createTask mine/mine_ore branches confirmed as autoToolFill=OFF regression
fallback (not dead code); kept with clarifying comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reported failure: empty-handed 'mine iron' -> plan step 'MINE stone x3' failed
instantly with no_reachable_target_block_in_range. MineTask only finds EXPOSED
blocks within range, but a surface bot's stone is buried under grass/dirt, so it
could never reach it -> whole goal failed.

Fix: GoalExecutor's MINE step now uses OreSeekTask.digBlocks (new plain-block
mode) instead of MineTask. OreSeek's SCAN locates the nearest matching block via
full server data and digs a directed corridor/staircase down to buried stone,
reusing its hazard gate + pickup machinery. Plain-block mode disables vein-flood
so 'MINE stone x3' mines ~3 blocks individually, not a 64-block flood.

OreSeekTask already handled arbitrary blocks (isOre = pure membership,
expandOreFamilies passes non-ores through, expectedDropsFor(STONE)=COBBLESTONE,
requiredPickaxeTier(STONE)=WOOD); only needed the vein-flood opt-out.

Known remaining gap (separate fix): from-scratch in a non-oak biome still fails
at 'GATHER oak_log' because GoalPlanner hardcodes oak species.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… steps

Reported failure zoyluoblue#2: empty-handed 'mine iron' in a birch-only biome failed at the
very first plan step 'GATHER oak_log' -> no_resource_nearby. GoalPlanner is a pure
function (can't see the world) and hardcoded oak; the biome had only birch.

Root cause is two-fold:
- GatherQuotaTask only accepted the one exact log species requested.
- GoalPlanner emitted standalone 'CRAFT oak_planks' intermediate steps. Plank
  recipes are species-locked (oak_planks <- oak_log), so even after gathering birch
  those steps would fail.

Fix C:
- GatherQuotaTask: when the target is a log, accept/gather ANY log species
  (RecipeRegistry.LOGS family); progress counts the whole family. Added a
  Set<Block> overload of HarvestCore.nearestReachableBlock.
- GoalPlanner: do NOT emit standalone CRAFT steps for intermediate planks
  (depth>0). Downstream stick/crafting_table/tool recipes all accept the PLANKS
  family, and their CraftTask expands planks from whatever logs are actually in
  the inventory (CraftingHelper is inventory-aware). Top-level plank goals
  (depth==0) still emit a step. GATHER log steps are unchanged.

Net: empty-handed 'mine iron' now plans [GATHER log, CRAFT stick, CRAFT
crafting_table, CRAFT wooden_pickaxe, MINE stone, CRAFT stone_pickaxe, MINE_ORE
iron] and runs in any wood biome.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…der guidance

Reported failure zoyluoblue#3: empty-handed 'mine iron' -> bot spawned in a water cave with
no trees, goal correctly planned but step 1 GATHER log kept failing
(no_resource_nearby). The LLM then flailed with ~20 random move/mine calls, which
walked it into a stone-capped underwater pocket. NavSafetyNet's surface-jump
couldn't help (no air above), air hit -1, and it drowned. After death the fake
player sat at hp=0 forever while DangerWatcher spammed evade every 5s (zombie loop)
until manual despawn.

Root cause was survival robustness, not the mining chain (which now works):
- NavSafetyNet only jumped toward the surface; in a capped water pocket there is
  no surface, and findNearestStandable treats water as passable so 'snapping'
  returns underwater spots too.
- A dead ServerPlayerEntity fake player is never removed and never auto-respawns.
- A failed goal handed control back to the LLM, which wandered into the hazard.

Fixes:
- P0 NavSafetyNet: when air<=60 and the column above is capped (no breathable air
  within 5 blocks), emergency-teleport to the nearest breathable+standable spot
  (feet & head are actual air, not water). Mild case still jumps to surface.
- C DangerWatcher: on hp<=0/!isAlive, respawn the bot to the surface
  (Heightmap top) at full health, clear its task + goal plan, report in Chinese
  -- instead of looping evade forever.
- P1 GoalExecutor: a goal that fails for missing base resources now reports
  actionable Chinese guidance (ask the human / don't wander); systemPrompt rule
  12 forbids move-spam exploration that risks death.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n't compile)

78eb5d8 was committed without a clean build — AIPlayerManager used Heightmap
without importing it (BUILD FAILED), and systemPrompt rule 12 (no-wander on
resource failure) was never actually added. Both fixed now; clean compileJava +
compileClientJava pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gging

Reported failure zoyluoblue#4: empty-handed 'mine iron' on a treeless surface. The goal
plan was correct (gather log -> craft tools -> mine), but step 1 GATHER oak_log
failed (no trees within 16 blocks), goal_failed, and the LLM brain took over and
(a) used strip_mine to hand-mine dirt with no pickaxe, and (b) spammed say
begging the human for wood/a pickaxe/a ride. Both are exactly what the user
forbids.

Root cause was not the mining chain (tool gate + backtracking planner all work):
- GatherQuotaTask gave up after a 16-block search instead of walking farther.
- strip_mine had no tool gate, so the brain could bare-hand-dig with it.
- Last round's systemPrompt rule 12 told the bot to beg for help on failure.

Fixes:
- F1 GatherQuotaTask: when nothing is found at radius 16, auto-expand the search
  to 32 then 48 (throttled to protect TPS) and path to the nearest tree, instead
  of failing and handing control back to the brain. This makes the common
  'no tree right here but trees nearby' case just work, end to end.
- F2 StripMineTask: tool gate — if the bot has no pickaxe at all, fail fast
  (need_pickaxe: use mine_ore) so the brain can never bare-hand strip-mine.
- F3 systemPrompt rule 12 rewritten: fully autonomous, NEVER ask the human for
  help/resources/a ride, NEVER hand-mine; always use mine_ore/achieve_goal which
  auto-gather wood and auto-craft tools; on hard failure state it once and stop,
  do not flail or beg. GoalExecutor failure messages no longer beg either.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…failed)

5fb67ba claimed an F2 tool gate on StripMineTask but the onTick edit didn't
match (real onTick starts with the timeout check, not getServerWorld()==null),
so only the unused ToolTier import landed -- the gate itself was never inserted.
Now actually added: onTick fails fast with need_pickaxe when the bot has no
pickaxe at all, blocking the brain from bare-hand strip-mining. clean compile
EXIT 0; grep-verified on disk.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…infinite loop

Reported failure #5: empty-handed 'mine iron' got through gather->craft tools
(steps 1-5 all OK, bot held a wooden pickaxe) then hung forever on step 6
'MINE stone x3'. Log shows oreseek_found -> oreseek_collected gained=0 repeating
~100x: every stone block WAS broken (mine_start/mine_complete) and cobblestone
even reached the inventory (cobblestonex1), yet collected stayed 0/3 -> never
completed -> infinite loop until manual stop.

Root cause (in the veinMode=false directed-dig mode added by Fix A): the MINE_VEIN
settlement computed gained = count(drops) - invBeforeMining, where invBeforeMining
was RE-SET in approach() before each block. The just-broken cobblestone is still
airborne when settlement runs, so gained=0; then on the next block's approach
invBeforeMining was reset to the now-higher count, permanently 'absorbing' the
cobblestone that had landed. collected could never catch up.

Fix: capture invBaseline ONCE at onStart and settle with an absolute delta:
total = count(drops) - invBaseline; gained = max(0, total - collected);
collected = max(collected, total). Airborne drops just keep total flat for a tick
(it waits via veinPickupTicks), then total rises and collected follows. Works for
both directed-dig (stone) and vein (ore) modes. invBeforeMining is now unused.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…all dig)

Reported failure #6: empty-handed 'mine iron' now gets ALL the way through steps
1-9 (gather->craft tools->MINE stone 3/3 via the previous baseline fix->craft
stone pickaxe) and stalls on the final step MINE_ORE iron. Log: tool gate passes
(stone pickaxe), oreseek_found locates iron at dist 8, but raw_iron never enters
the inventory (0 occurrences), the bot never mines at the ore coordinates, and it
descends 18 times from Y64 down to Y12 -- repeatedly 'sees iron, can't reach it,
digs down' forever.

Root cause (OreSeek approach to a buried ore): A* returns GOAL_UNREACHABLE
because the ore is encased (no standable neighbor), so it falls back to
digCorridorStep. But stepToward returned from.down() whenever the target was
below -- the bot mined the block under its own feet and went into FREE FALL
(diag shows on_ground=false the whole time), so bot.getBlockPos() drifted
mid-fall, the direction recomputed every tick from a moving position, and the
corridor veered AWAY from the ore (mined z 47->44 while the ore was at z=48).
After ~12s it gave up and descended; one layer down it found another far ore and
repeated, tunneling to bedrock empty-handed.

Fixes (OreSeekTask):
- stepToward: staircase descent. When the target is below, step to the block
  AHEAD-AND-DOWN (horizontal + down) so the bot walks down a stair instead of
  mining straight down and free-falling. Pure-vertical only when horizontally
  aligned.
- digCorridorStep: fall guard. If the bot is not on the ground, do nothing this
  tick and wait until it lands, so direction is always computed from a stable
  footing and the corridor forms one controlled block at a time.
- APPROACH_TIMEOUT_TICKS 300->600: digging a diagonal corridor to a buried ore
  legitimately takes longer than 15s; give it 30s before abandoning the ore.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reported failure #6 (real root cause): empty-handed 'mine iron' got through gather
-> craft wooden pickaxe -> MINE stone 3/3 (the #5 baseline fix worked: gained=1
x3), then died. Cause was NOT the mining chain: at 14:00:14 the LLM brain, still
in its conversation continuation, narrated 'I'll go mine 3 stone', called
inventory, then assign_task mine stone -- which aborted the goal's currently
running MINE step (goal_replan reason='aborted' -> goal_failed). The brain then
took over, flailed with strip_mine/move x30, and fell to its death at Y12.

This is the GF-1 'two drivers, one wheel' race, on the path GF-1 missed: GF-1
gated the brain's automatic wakeup, but scheduleContinuation's poll keyed only on
TaskManager.getActive(). Between two goal steps getActive() is empty for ~1 tick;
the poll landed in that gap, thought the bot was idle, and re-invoked the brain
mid-goal -> brain assign_task -> abort.

Fix:
- scheduleContinuation: check GoalExecutor.hasActivePlan BEFORE getActive(). While
  a goal plan runs, keep polling (pure wait) and NEVER re-invoke the brain, even in
  the between-steps gap. When the goal ends, hasActivePlan flips false and the next
  poll hands the result back to the brain to report.
- handleMessage: a fresh user message during a goal = explicit redirect. Clear the
  goal + abort its task + release busy so the new command runs immediately. This
  also prevents 'busy' staying true for the whole multi-minute goal (which would
  otherwise block the user from interrupting a runaway goal).
- systemPrompt rule 9: after calling mine_ore/achieve_goal, STOP -- do not call any
  other tool or narrate steps; the goal runs autonomously and reports when done.
  Calling other tools meanwhile aborts it. (Covers the multi-tool-in-one-response
  edge that the continuation gate cannot.)

Also keeps bbf8364 (corridor free-fall / staircase approach) -- a real latent bug
in OreSeek's buried-ore approach that would bite once the brain stops interfering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes the long feedback loop: the buried-ore approach path (A* unreachable ->
digCorridorStep -> staircase descent -> MINE_ORE, the bbf8364 fix) had no
dev-side regression, so every check required the user to enter the game and hope
for the right terrain. New /aibot verify mine_buried_iron:
- gives a diamond pickaxe (removes tool/craft variables),
- walls the iron ore behind 3 solid stone blocks (unreachable by walking, must
  dig a corridor),
- submits the MineOre goal and asserts raw_iron lands in inventory within 2400
  ticks while staying alive.

This isolates the directed-corridor + fall-guard logic deterministically with no
brain in the loop, so future regressions on that path are caught at the dev
console (/aibot verify mine_buried_iron) instead of in live play. Wired into
ALL_FEATURES + startScenario; clean compile EXIT 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…) + OreSeek watchdog

Reported: 'AI 发呆,长时间不动' (stands still for a long time). Log (14:39 session)
shows the dominant cause is NOT a mining-logic bug but combat thrash:
- goal_abandoned reason='foreign_task_assigned' x12
- combat task assigned 31x, only 8 completed
A cave spider in a dark Y12 cave keeps attacking. Each time DangerWatcher fires
combat, the mining goal is permanently ABANDONED, the brain re-plans the whole
10-step chain from scratch, combat fires again, abandon again -- infinite churn;
meanwhile combat/APPROACH can't reach the walled-in spider so the bot just stands
there (path_idle) for up to 30s per bout.

Root cause = survival interruptions destroy the deterministic goal instead of
pausing it:
- DangerWatcher.shouldPauseForThreat returned false for hostile->combat, so the
  active mine_ore step was aborted (not paused) before combat ran.
- GoalExecutor.tickBot saw a foreign active task (combat) and abandoned the whole
  plan -- it checked foreign-task BEFORE the hasPaused branch, so even a paused
  step would have been abandoned.

Fixes:
- DangerWatcher.shouldPauseForThreat: any non-combat/non-evade active task now
  PAUSES for a threat (parks in the paused map) instead of being aborted, so it
  can resume after the fight/flee.
- GoalExecutor.tickBot: when a foreign task is active, check hasPaused FIRST; a
  paused step = survival pre-emption -> wait & resume, never abandon. Only abandon
  when the step is neither active nor paused (= genuine player redirect, which
  handleMessage already clears explicitly).
  Net: mob shows up -> mining pauses -> bot fights/flees -> mining resumes from
  the same step. No more abandon+re-plan churn.

Also (defense-in-depth, no mining task should ever hang forever):
- OreSeekTask no-progress watchdog: if 400 ticks pass in SCAN/APPROACH/MINE with
  no block broken and nothing collected, fail cleanly (oreseek_no_progress)
  instead of looping forever.
- OreSeekTask phantom-target guard: in MINE_ORE, only treat an air target as
  'mined' if we actually started mining it (miningStarted); otherwise ignore it,
  so SCAN can't relock the same already-gone block in a 0-progress loop.

Honest note: a cave spider the bot genuinely cannot path to can still cause
repeated <=30s combat bouts (goal now survives, but won't progress while the mob
is unreachable). Combat-unreachable fast-give-up is the next focused item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…smatch)

f19b847's commit message claimed shouldPauseForThreat was changed to always pause
non-combat tasks, but that Edit silently failed (real param is 'nextTask', I wrote
'replacement') so only the GoalExecutor half landed. Without this, combat still
ABORTED the mining step instead of pausing it -> GoalExecutor still saw a foreign
task -> still abandoned the goal. Now actually applied: any non-combat/non-evade
active task pauses for any threat (incl LOW_HP). clean compile EXIT 0, grep-verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… stuck-spin)

Reported #8: 'AI 发呆,长时间不动'. Log (15:20 session) shows real progress -- the
combat-pause fix worked (task_paused/resumed x4, goal_abandoned=0), and gather->
craft wooden_pickaxe ran clean. But the goal still died on step 6 MINE stone:
oreseek_found pos at Y61-62 while the bot stood at Y67 (path_idle the whole time),
gained=0 repeating, until task_stuck_aborted (200t) -> goal_failed. The bot was in
a cramped ~10-block spawn pocket; OreSeek kept locking the straight-line-nearest
stone that was vertically/terrain unreachable and spun between SCAN<->APPROACH
without ever moving or breaking it.

Root cause: using OreSeek (a locate-then-approach miner) for 'MINE stone x3' is the
wrong tool -- getting cobblestone doesn't need locating a specific block, and
OreSeek's reach/pathing is exactly what hangs in tight terrain.

Fix: new DigDownTask for the GoalExecutor MINE step. It NEVER locates, paths, or
walks -- it stands still and only mines blocks within arm's reach:
- prefer straight-down (with a 2-below void/lava safety guard so it won't drop into
  a cave or lava), else a horizontal neighbor at feet/head level;
- fixed-baseline absolute-delta collected counting (the #5 lesson);
- force-pickup each tick; pickup grace before completing;
- tool gate (fail need_better_tool if no adequate pickaxe);
- self watchdog: fail dig_down_no_progress after 300t with no block broken;
- isWaiting()=true so StuckWatcher (which fired at 200t) doesn't pre-empt it --
  the task's own 300t/2400t timers own the stuck protection.
Because reach is never the issue (only adjacent blocks), the SCAN<->APPROACH
stuck-spin is structurally impossible.

(DigDownTask tracks mining via block-state recheck + periodic re-issue rather than
ActionPack.isMiningIdle, keeping it fully self-contained.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…p (#9)

Reported #9: 'AI 发呆'. Log (16:18 session): gather->craft wooden_pickaxe ran
clean, then step 6 MINE stone failed two ways, repeatedly:
1. dig_down_no_reachable_block (instant): bot stood on grass/dirt; DigDownTask
   only looked for STONE in the 8 adjacent blocks, found none, gave up -- it never
   dug DOWN through the soil to reach the stone layer.
2. dig_down_no_progress after collected=1: 'mine_start pos=... face=up' re-issued
   ~2x/sec to the SAME block which never broke. Root cause: ActionPack.startMining
   re-news a MiningController each call (progress resets to 0), and my DigDownTask
   re-issued startMining every 20 ticks -> progress could never reach 1.0.
Both, plus the brain then hand-mining and eventually dying, are the same class of
bug we've hit at #5/#8/#9: every mining task hand-rolls its own mining loop and
gets it subtly wrong.

Depth fix (architect call -- stop whack-a-mole, fix the primitive):
- NEW action/BlockMiner: the one correct 'mine a single block to completion'
  primitive. Issues startMining only when mining is idle (NEVER re-issues mid-dig,
  so progress is never reset), computes the correct face from eye->block, times
  out at 200t, reports DONE/MINING/FAILED. All mining tasks can adopt it.
- Rewrote DigDownTask as a true straight-down shaft using BlockMiner: it mines
  whatever is directly BELOW (dirt/grass/stone alike) to punch through topsoil to
  the stone layer; counts target drops via fixed-baseline absolute delta; waits
  (not fails) while the bot is falling into the just-dug gap; hard-fails only on
  lava/water/bedrock; self watchdog 200t no-break.
- GoalExecutor replan dedup: if the one-shot replan reproduces the exact step that
  just hard-failed (no_progress/stuck/timeout/no_reachable), don't retry it -- fail
  with an actionable reason instead of re-running an identical doomed step.
- verify: new /aibot verify dig_down (wooden pickaxe, 2 dirt over stone column,
  assert 3 cobblestone, no stuck) -- reproduces THIS failure deterministically at
  the dev console so it's caught before shipping.

Scope held deliberately: OreSeek/MineTask/StripMine still use their own loops for
now (lower risk); BlockMiner is in place for them to adopt next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… OreSeek (#10)

Reported #10: 'max_turns reached'. Log (01:20 session) is actually big PROGRESS --
the BlockMiner/DigDownTask depth fix (#9) works: MINE stone now collects 1/3->3/3
cleanly (dig_down_no_progress=0), and the goal reached step 10 for the FIRST time
(9/10 steps pass: gather->wooden_pickaxe->MINE stone->stone_pickaxe).

The only remaining blocker is the LAST step MINE_ORE iron, still on the old
OreSeekTask, which task_stuck_aborted ~7x ('stuck:mine_ore', oreseek_found dist=4
but progress stays 0) -- the same A*-approach-to-buried-ore stall as #6/#8. With
the goal failing, the LLM brain kept retrying (mine_ore/strip_mine/mine_block/
move) until it hit maxTurnsPerRequest=24 -> 'max_turns_reached' (a symptom of the
underlying task failing, not a cause) -> dead.

This is exactly the tail I deliberately left at #9 ('OreSeek... BlockMiner ready
for them to adopt next'). Paying it down:
- NEW task/OreDigTask: reliable ore collection on the proven non-stuck pattern --
  scan nearest target ore via full server data, then dig a CONTROLLED straight
  tunnel toward it one block at a time via BlockMiner (never A*/path-stall), mine
  it + flood-mine the adjacent vein, fixed-baseline counting, no-progress watchdog,
  isWaiting()=true so StuckWatcher can't pre-empt (the #10 abort source). When a
  horizontal step is cleared it startWalkTo's into it to advance the tunnel front.
- GoalExecutor MINE_ORE step: OreSeekTask -> OreDigTask (import swapped too).
- OreSeekTask itself kept for now (old tools/commands still reference it) but is
  off the goal hot path.

Net: the whole empty-handed 'mine iron' chain is now end-to-end on the BlockMiner
primitive (MINE + MINE_ORE both), no A*-approach stalls.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mining 'mine iron' now works end-to-end (10 rounds of fixes). Lock it in with a
dev-side regression net so future changes can't silently re-break it -- no more
entering the game to gamble on terrain.

New adversarial scenarios reproducing the real failures #7/#8/#10 deterministically
(no LLM, fixed worldgen):
- ore_dig_buried (#10): OreDigTask, iron encased in stone -> must tunnel down to it,
  mine a 2-block vein; asserts 2 raw_iron. Isolates the BlockMiner ore path.
- mine_iron_pocket (#8/#10): empty-handed full chain inside a 5x5 stone-walled pocket
  (the cramped-terrain variable) with one tree + buried iron; goal path; asserts
  raw_iron. End-to-end smoke on the BlockMiner primitive.
- mine_with_mob (#7): submit mine-iron goal then spawn a zombie; asserts the goal
  SURVIVES to completion -- verifies DangerWatcher pauses (not abandons) and resumes.

Plus a suite runner: /aibot verify mining runs dig_down, ore_dig_buried,
mine_to_iron, mine_buried_iron, mine_iron_pocket, mine_with_mob,
mine_iron_from_scratch in one command (expandFeatures handles the 'mining' alias;
'mining' suggested in tab-complete). All 7 also included in 'all'.

Net: the whole mining line is now covered by /aibot verify mining at the dev
console -- the regression loop the past 10 rounds badly needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tech-debt cleanup after the mining line works end-to-end: every mining task was
hand-rolling its own start/poll loop (the root cause class behind #5/#8/#9/#10).
Converge them onto the one correct primitive.

- MineTask: MINING phase now drives BlockMiner (was: re-issue startMining every
  200t, which reset progress). begin() once, tick() to DONE/FAILED -> pickup.
  Added onAbort cancel.
- StripMineTask: mineBlock + mineVein use BlockMiner instead of the
  miningStarted+MiningAction.startMining hand-loop. Removed MiningAction import.
- All ore mining now routes to OreDigTask (the #10 reliable miner): ToolRegistry
  (mine_ore / assign_task mine_ore / mine ore-branch / createTask), AIBotTaskSubcommand,
  verify mine_to_iron. OreSeekTask.java DELETED (zero code refs left).
- verify: new mine_exposed (MineTask-on-BlockMiner, exposed iron ore) added to
  ALL_FEATURES + mining suite.

clean compile EXIT 0; grep-verified: OreSeekTask gone, MineTask/StripMine on
BlockMiner, mine_exposed wired. Mining is now one primitive end to end.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t/pickaxe)

With mining unified on BlockMiner, replicate the proven backtracking framework to
more minerals at near-zero cost.

- GoalPlanner.acquireBaseItem: replace the hardcoded raw_iron/copper/gold/coal
  if-chain with a single oreBlockFor(item) map that also covers redstone->redstone_ore,
  lapis->lapis_ore, diamond->diamond_ore, emerald->emerald_ore. ensureMineOre already
  auto-inserts the required pickaxe tier (ToolTier: gold/redstone/diamond/emerald need
  iron), so achieve_goal diamond auto-plans stone_pickaxe->iron->iron_pickaxe->diamond.
- AIBotConfig Goal.maxPlanDepth 12->16: diamond-from-scratch recursion (wood->stone->
  iron->iron_pickaxe->diamond) is ~12 deep and was grazing the old cap.
- Smelt chain already in place (iron/copper/gold ingot); achieve_goal iron_ingot now
  plans MINE_ORE iron -> SMELT.
- verify: achieve_iron_ingot (empty->ingot, smelt), achieve_iron_pickaxe (empty->pickaxe,
  3 iron + smelt, deepest tool chain), achieve_diamond (iron pickaxe given -> mine buried
  diamond). All added to ALL_FEATURES + mining suite.

clean compile EXIT 0; grep-verified. 'achieve_goal <any common mineral/ingot/pickaxe>'
is now one deterministic backtracking call.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extends the deterministic backtracking framework from minerals to agriculture, so
'种小麦/收点小麦' works one-shot like mining does.

- Goal.HarvestCrop(crop, seed, produce, count) + GoalStep.FARM kind.
- GoalPlanner.ensureHarvestCrop: if not enough produce, backtrack a hoe
  (HaveItem wooden_hoe — new hoe recipes added to RecipeRegistry: wood/stone/iron
  = 2 head + 2 sticks) then emit one FARM step. Seeds are left to FarmTask at
  runtime (it finds/replants), not assumed in the pure planner.
- GoalExecutor FARM -> FarmTask in a new count-bounded mode: FarmTask gains an
  (produceItem, targetHarvest) constructor that completes when that many produce
  items are collected (fixed baseline), with a 12000t quota timeout. Existing
  6-arg FarmTask callers unchanged.
- ToolRegistry harvest_crop tool (crop=wheat/carrot/potato) -> submit HarvestCrop;
  systemPrompt rule 9 extended with the 种/收 crop phrasing + same call-once-then-stop
  discipline.
- verify farm_wheat_from_scratch: wooden hoe + a row of mature (age 7) wheat,
  HarvestCrop goal, assert >=3 wheat. Added to ALL_FEATURES.

clean compile EXIT 0; grep-verified all 8 wiring points. P1+P2+P3 complete:
mining unified on one primitive, mineral goals generalized, farming chained.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
在设置按钮旁新增背包按钮:上半 AI 背包、下半玩家背包;左键整堆、Shift 单个、右键半堆,任何人可拿放。服务端 handleItemMove 直改 Inventory(遵守铁律 G3,不碰 ScreenHandler),移动后立即回推快照。含中英 i18n。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
P0: AIPlayerEntity(ServerPlayerEntity)服务端不跑 travel()、无被动重力,挖空脚下永不下落。DigDownTask 与 OreDigTask.digDownOneLayer 各自假设挖一格自然落一格,致 MINE/MINE_ORE 竖直下挖必卡死(diag 全程 y 恒定、200t no_progress),并连带 collected=0(卡土层到不了石层)。抽共享原语 ActionPack.descendInto(主动 teleport 下沉),两处共用。

P1: max_turns_reached 后只发消息不复位,遗留任务 FAILED 被 lastStatus 长期缓存(diag 13 分钟显示卡死)、站桩不停。新增 TaskManager.resetToIdle;BrainCoordinator 善后:仅无运行中任务才 clear goal/复位/stopAll(避免误杀在跑长任务),否则标记 awaitingTask 接续。

回归: /aibot verify dig_down、ore_dig_buried 覆盖 P0(修前 FAIL→修后 PASS)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
实测:bot spawn 在半埋位置窒息后,navsafe 用 snapPlayerToNearestStandable 找欧氏最近可站点,被埋时最近点在下方/侧下方,反复 snap 把 bot 一格格往坑里拽(994 列 64→63→62→61),最终困死坑底、寻路全废、夜间 shelter/evade 死循环刷屏。改为优先垂直向上找第一个可站点(地表方向)传送钻出,向上 24 格无解才回退全向最近点。Standability.isStandable 已保证落点脚位+头位空气、脚下有支撑=能站能呼吸。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A. 两阶段寻路(startPathTo):先纯步行(禁 DIG_THROUGH,搜索空间=空气格、收敛快、不被挖穿邻居撑爆到 SEARCH_LIMIT),无解再允许挖穿兜底(限额预算压 3D 体积爆搜)。AStar/NeighborEnumerator 加 allowDig 开关(向后兼容默认 true)。B. 可达性名副其实(nearestReachableBlock):旧逻辑只看目标相邻有空格却不验证 bot 走得到,导致选走不到的树/矿→GOTO 反复失败 stuck。改为对最近 N 个候选用纯步行 A* 真实验证可达。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DangerWatcher 加困死检测:逃避类任务(evade/shelter)在同一格反复触发却没移动(被围/困坑底)→ 累加到阈值即判被困,退避 30s 静默等救援、并按 60s 间隔向玩家求助(被困坐标+请传送),不再每 2 秒空派 shelter/evade 刷屏(实测夜间困坑底 16 分钟死循环根因:shouldAssignThreatTask 不拦 shelter + cooldown 仅 40t)。bot 真在逃(位置变)计数自然重置,不误伤正常逃跑。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
新增 HuntTask:找最近牛/猪/羊/鸡/兔→接近→击杀→捡生肉,凑够数量(复用 CombatCore 接近/攻击、HarvestCore 强拾取),补上 CombatCore 只打敌对怪、EatTask 没肉就放弃的缺口。饥饿链闭环:DangerWatcher 在没有任何食物+周围有猎物+不在威胁应对中时自动派 HuntTask 获取生肉,而非干等饿死。烤肉复用现有 SmeltTask(BEEF→COOKED_BEEF 走 vanilla 熔炉配方,无需新代码)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GoalPlanner.ensureMineOre 对需铁镐及以上的深层贵重矿(钻石/金/红石/绿宝石)插入 ensureArmor 前置:倒推一身铁甲(头5/胸8/腿7/脚4)+铁剑(已穿/库存都算——inventoryCounts 现计入装备槽,不重复做)。RecipeRegistry 加铁甲配方。BotTickCoordinator 低频自动穿甲(equipBestArmor),bot 做出甲即穿。复用已有穿甲能力。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
下矿到钻石层:OreDigTask 早有扫不到矿就向下挖一层换层再扫到 Y=-60,配合第0层修复的 descendInto(bot 无被动重力、挖空脚下需主动下沉),从地表一路下挖找钻石的链现已打通。备粮:GoalPlanner 对深层贵重矿(钻石/金/红石/绿宝石)在装备前置之后插入 ensureFood——食物不足则下 HUNT 步猎肉续航;HUNT 为 best-effort,周围没动物时 GoalExecutor 跳过(不阻断挖矿目标),续航由第2层饥饿链兜底。GoalStep 加 HUNT kind,GoalExecutor 映射 HuntTask。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
实测:挖钻石计划已含装备+备粮(第3/4层),但 MINE_ORE(下地下)↔GATHER oak_log(回地表)反复交错,bot 挖到 y≈59 后 GATHER 够不到地表树(SEARCH_UP=12)且竖井底回不去 → no_resource_nearby → goal_failed。

A 集中采集(治本):GoalPlanner 后处理把所有 GATHER 同类需求合并并提到计划最前,bot 先地表一次砍够全部木头再下矿/做甲/挖钻,消除地下地表交错(GATHER 无前置依赖,CRAFT 都在其后)。B 上浮兜底:GatherQuotaTask 扩到最大半径仍找不到可达资源且 bot 在地下(头顶不见天)→ teleport 到正上方最近露天可站点重试一轮。D 降噪:ActionDispatcher 区分 IllegalArgumentException(预期传参错,如 assign_task mine 缺 block)→ 简洁 warn + bad_arg 回传,不再打整页 stacktrace。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BotSnapshotS2C 加 equipment 字段(6 槽:0头/1胸/2腿/3脚/4主手/5副手),服务端 snapshot 从 getEquippedStack 填充;InventoryView 在 AI 背包上方加装备展示行(只显示 + hover tooltip,不参与转移);INVENTORY 面板高度上调容纳;中英 i18n。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ZoyLuo and others added 24 commits July 5, 2026 10:46
…e 分支

WALKING_TO_FURNACE 二次卡死(纯寻路+挖掘式都推不近远炉)时,原逻辑盲目回 FINDING
重选同一座够不到的远炉,churn 到熔炼步 smelt_timeout(实测 real_armor 一局 166 次 stall)。
其时 bot 若手握备用 furnace,应就地摆一座就近熔炼(复用 line211 smelt_furnace_unreachable_replace
同款思路,旧炉弃用认亏几块石头),而非死走远炉。

证据(A/B real_armor 6 种子×2):基线 6/12→patched 8/12(+2 在 ±2-3 噪声内,不单凭聚合定功);
机理:replace 路径触发 1 次(111#2,该局 PASS),零回归(24/25 churn 手里无备炉→落原 else refind
分支,行为与基线完全一致,findItem 空时不改道)。属 has-spare 半remedy。

已知未治(下一杠杆):24/25 churn 是"无备炉但有 cobblestone×71"——bot 只造 1 座炉摆出去后无备,
就地合成新炉需 SmeltTask 跨层调 craft(架构改动,待设计定夺)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…are 多数(refind 24→0)

has-spare 修(0b32157)只覆盖 churn 时手握备炉的 1/25;实测 24/25 是"cobblestone 满手却只造过
1 座炉、摆出去就没备"→原逻辑回 FINDING 死走同一座够不到的远炉,churn 到 smelt_timeout。

本修:走炉二次卡死、无备炉但 CraftingHelper.plan(FURNACE).success() 时,新增 Phase.CRAFTING_FURNACE
组合复用 CraftTask 子任务就地合成一座新炉(不重复扣料逻辑),合成完 PLACING_FURNACE 摆脚下就近熔炼。

机理实锤(A/B log mc_test_real_armor_113324):炉真够不到时 event=smelt_walk_stall_craft →
CRAFTING_FURNACE,cobblestone 95→87(精确扣 8)→ event=place(摆炉)→ 立即就近 SMELTING。
炉若只是 8 格内一时被挡(CraftTask.utilityAlreadyAvailable 判有炉)→短路 complete,COMPLETED 分支
判 findItem(FURNACE) 空则回 FINDING 让 nearestFurnace 就近接管——两态皆安全,绝不空手进 PLACING 硬失败。

A/B 同 6 种子×2(real_armor 36000t 整套铁甲+剑):基线 6/12 → has-spare 8/12 → 本修 10/12。
机理 smelt_walk_stall_refind 24→0(churn 死循环被完全拦截)、smelt_timeout 2→0、零回归
(craft_timeout/place_crafting_table_failed/no_place_for_furnace/out_of_fuel 全 0)。残余 2 失败
均为 4040404 水域种子 verify_timeout status=mine_ore(挖铁不够,非熔炼)。

过对抗审查(general-purpose agent):核心逻辑无真 BUG(死循环有界 smelt_timeout 兜底/子任务生命周期
正确/短路防护到位/耗料认亏合理),补两处低风险修——onAbort 对称清理 furnaceCraftSub、进 CRAFTING_FURNACE
前 stopAll 清残留 walkTo。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
根因:TaskManager.paused 是单槽 Map<UUID,Task>。挖矿目标步遭遇多重威胁时嵌套抢占
(mine_ore→pauseFor 存入池→combat 活跃→二次威胁 pauseFor(combat) **覆盖驱逐 mine_ore**→
shelter churn→resume 后池空)→GoalExecutor.tickBot 见"活跃 shelter≠目标步且 hasPaused=false"
误判显式替换→goal_abandoned 丢整个目标(实测 real_diamond 99999:13 怪围攻深洞 light=0,
目标丢后又派 light_area 是症状)。影响所有"挖矿遇多重威胁"场景。

修:pauseFor 时若 paused 池已有任务(必是最早暂存的目标步),后来的 current 必是被二次抢占的
生存反射任务(combat/shelter/evade/resupply)——它反应式、条件仍在会被 DangerWatcher 每 tick
重派、无需 resume,故直接 abort 出局、保留目标步在池,生存链打完 resume 回目标。单次抢占
(池空)走原 pause 路径,零影响。

A/B(real_diamond 同 10 种子,36000t,零死亡红线):基线 4/10 → 本修 6/10。机理:
pause_preempt_abort 触发 12 次(高频,嵌套抢占普遍,name=combat/evade/shelter/resupply)、
goal_abandoned 1→0 根治、**真死亡两边皆 0**(红线守住)。诚实:diamond 聚合提升部分是勘探
RNG(翻转的是 no_resource/mine_ore 种子),修的直接价值在 correctness(目标不该被静默丢)+
更长多怪场景(iron/armor);极端怪压下 bot 黏目标致 guard_low_hp(安全网拦截、无死亡)揭示
的"狠撤退"缺口是独立 follow-up。

过对抗审查:无阻断 BUG(resume 靠"池非空"而非"谁被 abort"、combat 无挨打空档、已测边打边挖
单次抢占走原路径零回归、idle 反例落地无害)+ 采纳加固(lastStatus 写保留的目标步)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
P0 fixes:
- Fix direction up→down in AIBotTaskSubcommand
- Fix Standability slab/partial-block collision check
- Fix InventoryAction dropJunk data loss (preserve NBT/enchant)
- Fix GoalPlanner visiting-set infinite recursion (StackOverflowError)
- Fix AIPlayerEntity NPE logging with re-throw
- Fix MiningAction mineOnceInstant missing STOP_DESTROY_BLOCK

P1 fixes:
- Fix BrainCoordinator busy-flag race & ArrayDeque sync
- Fix DeepSeekApiClient NPE on null API message
- Fix ToolRegistry/ActionDispatcher null params/toolCalls NPEs
- Fix WalkToController 2D arrival (ignored Y coordinate)
- Fix MiningController block-state race (early return after reset)
- Fix CraftTask duplicate crafting table step
- Fix MiningChain lapis bestY=-1 dragging diamond target up
- Fix AIBotMemorySubcommand G1 violation (check goal before assign)
- Fix SmeltTask distant container mutation (walk to container first)
- Fix CraftTask AIR block check
- Fix PerceptionCollector radius hardcap to 8

P2/P3 fixes:
- Fix CraftTask duplicate table step detection
- Fix CombatTask AIBotConfig default retreatHp
- Fix ContainerAction reserved tools filter
- Fix MiningAction single-instant break
- Fix FarmAction durability bypass
- Fix various edge cases and race conditions
- A*: reduce DEFAULT_MAX_NODES 10000→2000, increase timeout 50ms→200ms
- A*: default heuristicWeight 1.0→3.0 (weighted A* converges faster)
- A*: double cache sizes and TTLs
- SurvivalGuard: emergency surface at air<80 (overrides all tasks)
- DangerCheck: check lava 2-below (under thin stone)
- NeighborEnumerator: adjacentLava check below (flood protection)
- Standability: climbable danger check (lava below ladder)
- place_block: move from LOW_LEVEL to CORE tool (always available to LLM)
- move_to: anti-spam distance check — skip if already at destination
- EvadeTask: teleport 10 blocks up when no escape path found
- AIBotConfig: maxTurnsPerRequest 12→25 (bot was hitting limit)
- maxTurns configured in defaults only — user's config.json overrides
- Chat: bot responses broadcast to in-game chat (not just panel)
- PvPTask: hunt & kill a specific player by name (FIND→APPROACH→ATTACK)
- kill_player tool registered in ToolRegistry
- Task interruption: user chat message aborts current task (except combat)
- Perception already includes inventory — LLM sees it
- ChatCaptureListener: ALL chat goes to bot (no @ prefix needed)
- TaskManager: stopAll() after task COMPLETED/FAILED (fix twitching)
- BrainCoordinator: interrupt running task on user message
- sendPanelChat: broadcast bot responses to in-game chat
- BlockMiner/MiningController: null/ORIGIN guards + air re-check before breaking
- GatherQuotaTask: tree felling — scan up for more logs after breaking one
- SurvivalGuard: force swim up (stopAll + setVelocity + setSwimming)
- BrainCoordinator: force-interrupt busy LLM for new chat messages, anti-spam dedup
- PerceptionCollector: radius cap 8→16 (half chunk)
- GatherQuotaTask: explore hop stuck detection — switch to GOTO after 3 stuck hops
- AStarPathfinder: heuristicWeight default 3.0→1.0 (dig=false searches)
- ActionPack.startMining: abort old MiningController before new one
  (fixes Mismatch in destroy block pos desync)
@vorobjewsen30-max

Copy link
Copy Markdown
Author

Round 2: Additional fixes based on deep code audit

Critical fixes:

  • CombatTask retreat: Bot no longer cancels retreat walk immediately (was transitioning to HEAL on same tick, canceling the walk). Now stays in RETREAT until ≥6 blocks from target.
  • CombatTask isWaiting(): Added override so StuckWatcher doesn't false-positive abort during combat.
  • say tool: Changed from "Simplified Chinese" to "Russian" to match system prompt.
  • System prompt: Chinese example texts replaced with English, select_item tool mentioned, rules completed (were truncated).

PvP & Defense:

  • Player attack detection: Fixed LOW_HP short-circuit — player attacker is now checked before defaulting to generic LOW_HP threat.
  • EmergencyShelter HP: Reduced from 8 to 4 (only at lethal HP).
  • Night shelter path: Removed — bot fights instead of building walls at night.
  • Totem of Undying: equipBestOffhand tries totem first, then shield.
  • guard_under_attack: Added at HP ≤ 10 (50%) with hurtTime > 0 — interrupts tasks when being attacked.

Pathfinding & Actions:

  • Standability: Bottom slabs now provide self-support (player can stand on slab without ground below).
  • place_block: Respects select_item — if item was explicitly selected, places that item, not first BlockItem.
  • Pickup: Auto-pickup every tick in 6-block radius (hardcoded, bypasses config).
  • StuckWatcher: No longer false-triggers when bot is actively mining, pathing, or walking.

API & Config:

  • HTTP 408: Now retried (was missing from retry condition).
  • forage tool: Description fixed (claimed melon support but only targets sweet berries).
  • forage schema: Fixed broken objectSchema() chain.
  • All tools: Null-param safety verified — no remaining NPE risks.

Build

  • ./gradlew build — BUILD SUCCESSFUL
  • Deployed to server, pushed to fork

zoyluoblue pushed a commit that referenced this pull request Jul 30, 2026
… steps

Reported failure #2: empty-handed 'mine iron' in a birch-only biome failed at the
very first plan step 'GATHER oak_log' -> no_resource_nearby. GoalPlanner is a pure
function (can't see the world) and hardcoded oak; the biome had only birch.

Root cause is two-fold:
- GatherQuotaTask only accepted the one exact log species requested.
- GoalPlanner emitted standalone 'CRAFT oak_planks' intermediate steps. Plank
  recipes are species-locked (oak_planks <- oak_log), so even after gathering birch
  those steps would fail.

Fix C:
- GatherQuotaTask: when the target is a log, accept/gather ANY log species
  (RecipeRegistry.LOGS family); progress counts the whole family. Added a
  Set<Block> overload of HarvestCore.nearestReachableBlock.
- GoalPlanner: do NOT emit standalone CRAFT steps for intermediate planks
  (depth>0). Downstream stick/crafting_table/tool recipes all accept the PLANKS
  family, and their CraftTask expands planks from whatever logs are actually in
  the inventory (CraftingHelper is inventory-aware). Top-level plank goals
  (depth==0) still emit a step. GATHER log steps are unchanged.

Net: empty-handed 'mine iron' now plans [GATHER log, CRAFT stick, CRAFT
crafting_table, CRAFT wooden_pickaxe, MINE stone, CRAFT stone_pickaxe, MINE_ORE
iron] and runs in any wood biome.
zoyluoblue pushed a commit that referenced this pull request Jul 30, 2026
多地形测试 20260610(悬崖临水):bot锁水下铁矿潜水挖,SurvivalGuard guard_drowning 在 ore_dig 自身溺水
熔断(会【排除这块水下矿】)前就抢先中断mine_ore→浮上来(NavSafetyNet,实测surface 32次)又重锁同一水下矿
→反复guard_drowning→goal失败。修:guard_drowning 对 OreDigTask 返回null不抢断,交其自身熔断撤单+排除水下矿,
下次重锁干矿破循环。geo_lake(湖底矿溺水)+geo_deep/shaft/cave+achieve_armor 5/5不回归。
(注:geo_diamond_lava 与本修无关——回退本修后仍FAIL,是LavaEscapeTask在深层岩浆旁逃到地表后回不去的独立方差,另查)
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