Skip to content

Commit 851e5fd

Browse files
Cap build-plan auto-fix terrain shifts
1 parent 482d032 commit 851e5fd

File tree

3 files changed

+31
-17
lines changed

3 files changed

+31
-17
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,7 @@ Agents should not treat the build planner as a black box. The most important rul
435435
- The planner now returns structured `issues` identifying floating cuboids or block targets, their `gapBelow`, and a `suggestedY` to ground them correctly.
436436
- The planner only adds support pillars for real unsupported columns and caps auto-support at 24 columns.
437437
- If more than 80% of the lowest build columns are already within 2 blocks of solid ground and `autoFix=true`, the planner can auto-lower the whole build instead of spamming pillars.
438+
- Automatic Y correction is intentionally conservative and capped to small terrain fixes. It will not bury a build deep into the ground just because one mixed-terrain column reports a large gap.
438439
- `resolvedOrigin` in the result tells you the exact world origin that was actually used.
439440
- `autoFixAvailable` tells you whether the planner believes a safe grounding fix exists.
440441

run-mcp-sidecar-node.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,9 +1020,11 @@ function buildBuildPlanGuide() {
10201020
"- It returns structured `issues` identifying which cuboids or block targets are floating, their `gapBelow`, and a `suggestedY` for grounding.",
10211021
"- Only columns with real air/fluid gaps below them are considered for auto-support pillars.",
10221022
"- If more than 80% of the lowest-layer columns are within 2 blocks of valid ground and `autoFix=true`, the planner can auto-lower the whole build instead of spamming pillars.",
1023+
"- Automatic Y correction is conservative. It only applies small grounding shifts and will not freefall a build deep into mixed terrain just because one column reports a large gap.",
10231024
"- Auto-support pillars use `minecraft:stone_bricks` and are capped at 24 columns.",
10241025
"- If there is no valid solid ground below some lowest columns at all, the planner rejects the build and tells the agent to lower the build or add a foundation.",
10251026
"- Repeated retries with the same floating `y=0` plan are the wrong response. Fix the base Y, add a foundation phase, or use preview issues plus `suggestedY` to revise the plan.",
1027+
"- In practice, agents should treat `autoFix` as a small-terrain correction helper, not as permission to bury the structure until all issues disappear.",
10261028
"",
10271029
"Example issue payload:",
10281030
"",

src/main/java/com/aaron/gemini/VoxelBuildPlanner.java

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ final class VoxelBuildPlanner {
3232
private static final int MAX_SITE_SCAN_UP = 6;
3333
private static final int MAX_SITE_SCAN_HEIGHT = 6;
3434
private static final int MAX_AUTO_FOUNDATION_COLUMNS = 24;
35+
private static final int MAX_AUTO_FIX_SHIFT = 3;
3536
private static final String DEFAULT_FOUNDATION_BLOCK = "minecraft:stone_bricks";
3637

3738
private VoxelBuildPlanner() {
@@ -1421,31 +1422,27 @@ private static BuildPlan shiftPlanForIssues(BuildPlan plan, List<SupportIssue> i
14211422
if (plan == null || issues == null || issues.isEmpty()) {
14221423
return null;
14231424
}
1424-
Map<String, Integer> deltasByName = new LinkedHashMap<>();
1425-
for (SupportIssue issue : issues) {
1426-
if (issue == null || issue.cuboid() == null || issue.cuboid().isBlank() || issue.gapBelow() <= 0) {
1427-
continue;
1428-
}
1429-
deltasByName.put(issue.cuboid(), -issue.gapBelow());
1430-
}
1431-
if (deltasByName.isEmpty()) {
1432-
return null;
1433-
}
1434-
ShiftResult result = shiftPlanForIssuesRecursive(plan, deltasByName, repairs);
1425+
ShiftResult result = shiftPlanForIssuesRecursive(plan, issues, repairs);
14351426
return result.changed() ? result.plan() : null;
14361427
}
14371428

1438-
private static ShiftResult shiftPlanForIssuesRecursive(BuildPlan plan, Map<String, Integer> deltasByName, List<String> repairs) {
1429+
private static ShiftResult shiftPlanForIssuesRecursive(BuildPlan plan, List<SupportIssue> issues, List<String> repairs) {
14391430
List<Integer> directDeltas = new ArrayList<>();
1431+
Map<String, SupportIssue> issuesByName = new LinkedHashMap<>();
1432+
for (SupportIssue issue : issues) {
1433+
if (issue != null && issue.cuboid() != null && !issue.cuboid().isBlank()) {
1434+
issuesByName.put(issue.cuboid(), issue);
1435+
}
1436+
}
14401437
for (Cuboid cuboid : plan.cuboids()) {
1441-
Integer delta = deltasByName.get(cuboid.name());
1442-
if (delta != null && delta != 0) {
1438+
Integer delta = safeAutoFixDelta(issuesByName.get(cuboid.name()), Math.min(cuboid.from().y(), cuboid.to().y()));
1439+
if (delta != null) {
14431440
directDeltas.add(delta);
14441441
}
14451442
}
14461443
for (BlockPlacement block : plan.blocks()) {
1447-
Integer delta = deltasByName.get(block.name());
1448-
if (delta != null && delta != 0) {
1444+
Integer delta = safeAutoFixDelta(issuesByName.get(block.name()), block.pos().y());
1445+
if (delta != null) {
14491446
directDeltas.add(delta);
14501447
}
14511448
}
@@ -1458,7 +1455,7 @@ private static ShiftResult shiftPlanForIssuesRecursive(BuildPlan plan, Map<Strin
14581455
boolean changed = false;
14591456
List<BuildStep> shiftedSteps = new ArrayList<>();
14601457
for (BuildStep step : plan.steps()) {
1461-
ShiftResult result = shiftPlanForIssuesRecursive(step.plan(), deltasByName, repairs);
1458+
ShiftResult result = shiftPlanForIssuesRecursive(step.plan(), issues, repairs);
14621459
changed |= result.changed();
14631460
shiftedSteps.add(new BuildStep(step.phase(), result.plan()));
14641461
}
@@ -1492,6 +1489,20 @@ private static int chooseShiftDelta(List<Integer> deltas) {
14921489
.orElse(0);
14931490
}
14941491

1492+
private static Integer safeAutoFixDelta(SupportIssue issue, int currentY) {
1493+
if (issue == null || issue.suggestedY() <= 0) {
1494+
return null;
1495+
}
1496+
int delta = issue.suggestedY() - currentY;
1497+
if (delta == 0) {
1498+
return null;
1499+
}
1500+
if (Math.abs(delta) > MAX_AUTO_FIX_SHIFT) {
1501+
return null;
1502+
}
1503+
return delta;
1504+
}
1505+
14951506
private static BuildPlan shiftWholePlan(BuildPlan plan, int deltaY) {
14961507
if (plan == null || deltaY == 0) {
14971508
return plan;

0 commit comments

Comments
 (0)