Baritone AI pathfinder

Baritone AI pathfinder

72.7k Downloads

Baritone for staircased mapart including carpets.

cbrentas opened this issue ยท 3 comments

commented

After doing my first staircased mapart using Baritone (for the most part at least) I want to share my suggestions on how this can be completely automated and less buggy.

If we can imagine staircase mapart divided into a series of lines going down the Z axis(X,Z are the horizontal) we get a lot of lines that have a lowest point on Y axis (height) and a highest point. If we for example say that a line has a lowest point of Y=5, a highest point of Y=200 and the player is at Y=50 (somewhere in between) it's not easy for Baritone to build below that level in order to continue the line. Also from what I experienced baritone is trying to build whatever is closer or in his reach. As a result you get a lot of unfinished lines.
My suggestions in order to make the process more automated(not necessarily faster) is the following.

  1. First of all complete the lines one by one before moving to the next one.
  2. Detect the lower point of a line and start building from there. On most cases the line's lowest point would not be in the edge of the map so baritone will have to backtrack in order to complete the other half(which is not so bad as I will explain on my 3rd point). That way you will not have missing parts and you will progress the schematic part by part.
  3. Carpets. When it comes to carpets baritone freaks out. From my experience it happens when baritone is in the area of the block the carpet is trying to be placed. I think I've read about this suggestion before and that it ruins more things than it's trying to fix but I will say it again in case I misread. As I said on my 2nd suggestion there will be a point where Baritone will need to backtrack. That will only happen when he is on the furthest block of the line. So my suggestion is to place carpets only when he is at least 1 block away. I believe this will not affect the movement of baritone but I bet it's really hard to program so sorry in advance.

I just want to say that after a lot of hours dealing with this stuff what I did was exactly this. Get baritone on the lowest level of each line and then execute the #litematica command. That's how I came to these conclusions.

commented

You didn't misread the point about carpets. Could probably isolate the part about moving out of adjacent blocks and placing canWalkThrough at eye height, but the major problem is trying to pillar-place things which aren't canWalkOn and changing that is what leads to the chain of problems you already found.

This reads like a doable ad-hoc strategy though assuming Z aligned staircases does not sound nice. Thinking about a simple implementation, what about this approach:

  • Scanning for placeable blocks, at any position (stopping at the first match and treating "outside schematic" as always correct)

    1. If the block below is wrong, don't place
    2. If it has no block below and it has a correct neighbor (or should all neighbors be correct?), place
    3. If it has a block below and is supposed to have a neighbor below which there correctly is no block (i.e. next to intended overhang), place
    4. If it has a block below and is not supposed to have neighbors, place
    5. If it has a block below and has a correct neighbor, place
    6. Don't place. (unsupported without neighbor or a case I forgot)
  • Scanning for breakable blocks, at any position

    1. If it's wrong, break
    2. If the block below is wrong, break (and hope this doesn't cause looping)
    3. Don't break

I tried not to use the staircase direction even though it could be inferred by counting and also tried to avoid nonlocal decisions (e.g. tracing down the line to see whether everything below is already built) so when building up it might start building from other lines (or wait for those if using the option in parenthesis in 2. for placing) and I did not find a way to force top down breaking (if you are in the middle of a horizontal strip, how do you know which way is up without looking further?).
Also only placing upwards if it's either next to overhang or next to already placed blocks can get it stuck so might have to drop that or do nonlocal analysis (i.e. "if it's next to overhand or a placed neighbor, place" and "if no chain of neighbors ends next to overhang or in a placed neighbor, place" or the simpler "if you can't do anything else, might as well place it").
For mapart this will probably work, but I guess even for simple other schematics which can be built "kinda like mapart" it will regularly fail with some form of loop because it would need something like "if you need to walk there, don't place". Which, despite it's simplicity, causes headaches galore.

commented

Thank you for you response. I had some trouble reading this because I am not so familiar with all the terms so please forgive me if am spitting nonsense and let me know.
So if I am correct you are suggesting a more advanced and "smarter" version of what it already does (I have not read the code I am just assuming based on my experience using it). I understand that my suggestion is straight forward that does specific things only under specific circumstances, unlike yours it makes decisions on its own and is closer to what someone could call AI through neural networks. There is nothing wrong with that but in my opinion it lacks the foundation. What you are explaining in your points about placing blocks (I am sure there are others but I will say what came on top of my mind).

  • At some circumstances when there are no placeable blocks it will have to abandon the "neighborhood". Baritone does not do good well with large structures so if there are not any eligible-for-placement blocks nearby it will just stop.

  • Let's say it finds a purpose and goes nearby and start building somewhere else. After a while or when there are no other blocks to place we will have repeated behavior of my first point and have for example 3 overhangs(overhangs if I understood correctly are the lowest points of a stair or line) that are at the same Y(height). If one overhang is on Z=1 and the other on Z=150 are they considered entangled and baritone should start building from one to another or is it better to calculate if it's less distance from the ground and start building a helping "tower" from outside the schematic.

  • Also I don't know if air placement is a thing and could make this whole thing easier.

Concerning the placing points:

If it's wrong, break

For mapart Baritone shouldn't care UNLESS it's the top most block. This will also help finding blocks easier to build helping bridges. The command mapart by the way doesn't work properly because it cares only about the top block as it intends to but ONLY does that, if you don't have the block in your inventory it gets stuck. I guess for normal schematic builds, wrong placements matter but maybe you can enable an option art for example and it will not care for placed blocks that are surrounded by others and not visible to the watcher(Already sounds so complicated though).

If the block below is wrong, break (and hope this doesn't cause looping)

This is what caused me the most looping and I had to restart baritone from another position. I may be biased because of my experience but this should be replaced by doing the action using helping blocks. I get that the fastest way it to break correct blocks but it might be more efficient using help blocks. This can become tricky as well though because baritone will need to create a bigger bridge to place the block needed.

Baritone is used for a lot of purposes and schematics is one of those that have been looked through a lot so it's understandable to get "confused" with actions that would work better in other scenarios. It is still remarkable that can do the things it does and more worth mentioning is the speed it evolves. My suggestion in general although that's already the goal and the whole logic behind it is making more "paths" to the network and decisions and build on each branch. I think I would be really intrigued on digging in and understanding the code my self and contributing directly when I have the time.

commented

Concerning terminology: I had to somehow differentiate between the schematic (intended blocks) and the world (actual blocks). I think "correct" and "wrong" are obvious and "supposed" is just a phrase I chose to explicitly refer to the schematic contents. One thing I should probably note is that often air also counts as a block.

Current builder strategy The current builder strategy does not quite work like this though it's somewhat similar, here's a simplified version for reference (the actual logic is a bunch of spaghetti, writing this from memory so mistakes might happen):
  1. Scan in 5 blocks radius for blocks you can place/break right now and do it (don't place at eye height or directly downwards, only break downwards if `breakFromAbove` is enabled)
  2. If nothing is in range, do a local scan and walk towards any found wrong blocks (walk onto liquids, into wrong blocks, next to or onto correct blocks and ignore if there is a missing block up to two blocks below)
  3. If local scanning doesn't find anything do a global scan and walk to any wrong blocks (same filtering)
  4. Additionally the pathfinder has block placing/breaking costs tweaked so placing incorrect blocks or breaking correct blocks is penalized and placing correct blocks is free.

Most notably there is only few checks for immediate decisions and even less when setting goals.
What I've listed above are alternative sets of rules for immediate decisions (do I place this right now) and I would adapt the scan filtering to use similar logic.

I understand that my suggestion is straight forward that does specific things only under specific circumstances, unlike yours it makes decisions on its own and is closer to what someone could call AI through neural networks.

This surprises me. Your suggestion is a set of high level advice which leaves room for a lot of interpretation (what even is a line?) so I tried compiling it down into something I can translate to code almost literally, there is no neural network or learning based decision making involved.

At some circumstances when there are no placeable blocks it will have to abandon the "neighborhood". Baritone does not do good well with large structures so if there are not any eligible-for-placement blocks nearby it will just stop.

Once nothing placeable is nearby it should look a little further, walking somewhere it can place again. The intendend behavior (which might take some tweaking to achieve) is that this happens whenever it reaches the top of a staircase and since all top blocks are filtered out during when deciding where to go (the new rules don't allow placing "midair") it will walk back down and start another line.

An overhang was intended to be an unsupported block next to a supported block, not the lowest point of a line. To be honest I haven't though too much about starting at the bottom and if there is multiple unfinished lines it will just walk towards the closest one. Would never placing throwaways be feasible? It would be a lot easier than placing them and making sure they don't cause loops.

For mapart Baritone shouldn't care UNLESS it's the top most block.

That's a separate concern and mapArtMode will still work. If you turn on mapArtMode anything but the topmost block is outside the schematic and always considered correct. Failing if it doesn't have the materials is somewhat intended though I do agree that it would be nice if it could go on somewhere else before failing.
If you want to hide interior you can maybe preprocess the schematic to fill it with some block it doesn't use otherwise and then put that on buildSkipBlocks. I don't think doing visibility checks inside the builder is an option right now.

The new rules should prevent placing above wrong locations, the comment in parenthesis was mostly a note that this is a pitfall to watch out for. Using throwaways without causing loops is much harder.

To be honest having a way to plan ahead would certainly be better than ad-hoc decision making, but it's also harder and I know of no such approach which is anywhere near usable. That's why I'm so opposed to anything requiring more complex logic so in the end this will hopefully end up being a small improvement rather than ending as yet another maybe-never-usable solution.
Also I got time now so I might as well go and try this out instead of writing more essays about it.

EDIT: I ran some experiments and it mostly worked, but nothing I'd consider worth a pr (#4303 is basically the usable results). If someone wants to test it themselves I probably still have the commits somewhere.