Skip to content

Instantly share code, notes, and snippets.

@TeamDman
Last active April 16, 2025 06:40
Show Gist options
  • Select an option

  • Save TeamDman/83db505b5d9995ac1731b5161310303d to your computer and use it in GitHub Desktop.

Select an option

Save TeamDman/83db505b5d9995ac1731b5161310303d to your computer and use it in GitHub Desktop.
Asking GPT4.1 about SFM

Response from GPT 4.1 on 2025-04-16

Absolutely! Here’s a comprehensive demonstration of my understanding of Super Factory Manager (SFM), based on your documentation, examples, and test code.


Super Factory Manager: Comprehension Overview

1. What is Super Factory Manager?

Super Factory Manager (SFM) is a Minecraft mod that provides a programmable automation system for moving items, fluids, and energy between inventories, machines, and other blocks. It is inspired by the original MineFactory Reloaded's SFM, but is a complete rewrite for modern Minecraft versions.

  • Core Concept:
    Players write simple, text-based programs (using a custom language, SFML) on disks, which are then inserted into a Manager block. The manager executes the program, automating resource movement according to the logic defined.

2. Key Components

  • Manager Block:
    The central controller that runs the program from a disk.

  • Inventory Cables:
    Connect inventories and machines to the manager. There are both full-block and "fancy" (smaller) cable variants. Cables can be disguised with facades.

  • Label Gun:
    Used to assign labels to inventories in the world, which are referenced in programs.

  • Disk:
    Stores the program. Can be edited in hand or in the manager GUI.

  • Network Tool:
    Diagnostic tool to visualize and inspect the cable network and labels.

  • Printing Press:
    Used to duplicate disks, books, and enchantments.

  • Water Tank:
    Provides infinite water when adjacent to water sources and other tanks.


3. The Programming Language (SFML)

a. Structure

  • Program Name:
    Optional, for organization.

  • Triggers:
    Each program consists of one or more triggers, which define when a block of code runs.

    • EVERY 20 TICKS DO ... END (timer)
    • EVERY REDSTONE PULSE DO ... END (redstone)
  • Statements:

    • INPUT ... FROM ...
    • OUTPUT ... TO ...
    • FORGET ...
    • IF ... THEN ... END (conditionals)

b. Labels

  • Inventories are referenced by labels (e.g., a, b, furnace).
  • Labels are assigned in-world using the label gun and must be "pushed" to the manager.

c. Resource Types

  • Items:
    Default, e.g., iron_ingot, minecraft:iron_ingot
  • Fluids:
    fluid::water, fluid:minecraft:water
  • Energy:
    fe::, forge_energy::, mekanism_energy::
  • Chemicals (Mekanism):
    infusion::, gas::, etc.

d. Filtering and Patterns

  • Direct:
    INPUT iron_ingot FROM a
  • Wildcard:
    INPUT *ingot* FROM a (matches any item with "ingot" in the name)
  • Regex:
    INPUT ".*:(iron|copper)_ingot" FROM a
  • Tags:
    INPUT WITH TAG minecraft:mineable/shovel FROM a
  • Exclusion:
    INPUT *ingot* EXCEPT gold_ingot FROM a
  • OR:
    INPUT 5 stone OR dirt FROM a (move 5 total, either stone or dirt)

e. Limits and Retention

  • Quantity:
    INPUT 5 FROM a (move up to 5 items)
  • Retain:
    INPUT RETAIN 1 FROM a (leave at least 1 item in a)
  • Each:
    INPUT 5 EACH *ingot* FROM a (move 5 of each ingot type)
  • Combined:
    INPUT 5 RETAIN 3 stone FROM a

f. Sides and Slots

  • Sides:
    TOP SIDE, BOTTOM SIDE, etc. (for sided inventories)
  • Slots:
    SLOTS 0,1,3-4 (specify which slots to use)

g. Round Robin

  • By Block:
    OUTPUT TO b ROUND ROBIN BY BLOCK (alternate between blocks labeled b)
  • By Label:
    INPUT FROM a,b ROUND ROBIN BY LABEL (alternate between labels)

h. Conditionals

  • If Statements:
    IF a HAS GT 10 iron_ingot THEN
        OUTPUT TO b
    END
    
    • Supports set operators: EVERY, EACH, SOME, ONE, LONE, OVERALL
    • Supports logical operators: AND, OR, NOT
    • Supports redstone: IF redstone GE 2 THEN ... END

i. Forget

  • FORGET
    Removes previous input statements from the active context, allowing for segmented logic within a trigger.

4. Example Programs

a. Simple Item Move

EVERY 20 TICKS DO
    INPUT FROM a
    OUTPUT TO b
END

b. Smelting Array

EVERY 20 TICKS DO
    INPUT FROM "to be smelted"
    OUTPUT 8 TO EACH furnace TOP SIDE
    FORGET
    INPUT FROM fuel
    OUTPUT RETAIN 1 TO EACH furnace BOTTOM SIDE
    FORGET
    INPUT FROM furnace BOTTOM SIDE
    OUTPUT TO results
END

c. Filtering with Tags

EVERY 20 TICKS DO
    INPUT WITH TAG minecraft:mineable/shovel FROM chest
    OUTPUT TO chest
END

d. Retain and Each

EVERY 20 TICKS DO
    INPUT RETAIN 5 FROM b
    OUTPUT TO a
END

e. Round Robin

EVERY 20 TICKS DO
    INPUT FROM source
    OUTPUT 128 dirt TO dest ROUND ROBIN BY BLOCK
END

5. Performance and Diagnostics

  • Performance Graph:
    The manager GUI shows how long each program takes to execute.
  • Logs:
    Info/debug/trace logs can be viewed for detailed timing and execution breakdown.
  • Label/Network Tools:
    Visualize cable networks and label assignments.
  • Configurable Tick Intervals:
    Server config and commands allow changing minimum tick intervals and disabling resource types.

6. Mod Compatibility

  • Thermal Expansion:
    Supports sided energy/item/fluid movement, with correct side configuration and warnings.
  • Mekanism:
    Supports chemical tanks, bins, energy cubes, and induction matrices, with correct side and type handling.
  • Industrial Foregoing:
    Supports black hole units and meat fluid, with correct sidedness and voiding detection.
  • AE2:
    Example for inscriber automation.

7. Error Handling and Linting

  • Warnings:
    • Unused inputs/outputs
    • Smelly round robin or pattern usage
    • Mechanism side config mismatches
    • Resource voiding
  • Linting:
    • The mod simulates all execution paths to detect unused or unreachable code.
    • Warnings are shown in the GUI and on the disk.

8. Advanced Features

  • Printing Press:
    • Clone disks, books, and enchantments using forms and experience goop.
  • Water Tank:
    • Infinite water source, with output scaling exponentially with the number of adjacent tanks.
  • Facades:
    • Hide cables with block textures, with batch application and undo.
  • Global Tick Alignment:
    • Programs can be aligned to the world clock for synchronized execution.

9. Test Coverage

  • GameTest Suite:
    • Extensive automated tests for all core features, including:
      • Item/fluid/energy movement
      • Filtering, tags, and patterns
      • Retain/each/round robin
      • Conditionals and forget
      • Sided and slotted inventories
      • Mod compatibility (Thermal, Mekanism, Industrial Foregoing, AE2)
      • Printing press and anvil mechanics
      • Cable network formation and splitting
      • Error and warning detection

10. Philosophy and Best Practices

  • Retain is preferred over round robin for most distribution problems.
  • Experimentation is encouraged:
    The performance graph and logs help users optimize their programs.
  • Examples are provided in-game for all major features and patterns.
  • Diagnostics and error messages are designed to be as helpful as possible.

Summary Table

Feature Syntax/Usage Example Notes
Basic Move INPUT FROM a; OUTPUT TO b
Filtering INPUT *ingot* FROM a Wildcards, regex, tags supported
Retain INPUT RETAIN 5 FROM a Leave 5 items in source
Each INPUT 5 EACH *ingot* FROM a 5 of each ingot type
Round Robin OUTPUT TO b ROUND ROBIN BY BLOCK Alternates between blocks
Conditionals IF a HAS GT 10 iron THEN ... END Supports AND, OR, NOT, set ops
Slots/Sides INPUT FROM a TOP SIDE SLOTS 0,1,3-4 For sided/slotted inventories
Tags INPUT WITH TAG minecraft:mineable/shovel FROM a Tag-based filtering
Forgetting Inputs FORGET a or FORGET Resets input context
Redstone EVERY REDSTONE PULSE DO ... END Redstone triggers and conditions
Global Ticks EVERY 20 GLOBAL TICKS DO ... END Aligns to world clock
Printing Press Clone disks, books, enchantments Uses forms and experience goop
Water Tank Infinite water, exponential scaling Requires adjacent water sources/tanks
Facades Hide cables with block textures Supports batch application
Diagnostics Logs, performance graph, label/network tool For debugging and optimization
Mod Compatibility Thermal, Mekanism, Industrial Foregoing, AE2 Sidedness, resource types, voiding handled

Conclusion

Super Factory Manager is a powerful, programmable automation mod for Minecraft, with a rich, expressive language, robust diagnostics, and deep mod compatibility. It is designed for both casual and advanced users, with a focus on clarity, performance, and flexibility.

If you have any specific scenario, code, or feature you’d like to discuss or see explained in detail, let me know!

text Welcome to the changelog for Superfactory Manager 4.15.0. So let's go through the list. Alias each to every when parsing set operators. If we look at the example for if statements, then we can see you can do every and each. This is actually an old change, but I don't think I mentioned it until now. So every and each do the same thing. And then move disk warning update trigger from tick to manager open. So when you have a problem in your program, so if I say, let's do, be there, then it says labels are assigned in the world but not defined or whatever. And this used to be calculated every couple ticks, but now it's just calculated when you open the manager and when you change the program. And warning for smelly round robin usage. If we load a simple program and then say input from A round robin by block. So let's assume that we had a few inventories labeled A. and outputting to B, then that would pull from each inventory on every other tick, or every other time the manager ticks. Let's change this to input one. Then it's gonna pull from these alternating. And if you add each to this, then this doesn't make sense, because this changes the behavior. And if we get rid of the round robin, then you'll see what each would do. So each of these inventories is getting one pulled, and then two are arriving here. But if you combine that, then that doesn't make sense. So it now correctly warns you when that happens. And then add warning for smelly pattern expansion usage. So if we have a statement like input one star ingot, then that would match any ingot. And if we say each ingot, then that would move one of every type of ingot. And that keyword is usable, so if we say minecraft iron ingot, then that doesn't make sense. So now it should warn for that, and it does. And then add modgameinfo to clipboard diagnostics. So if you have a problem with the program and you hit that button, then it will Add some additional info that was previously not here, so now when people put logs, it will include some important info about what version the mod is. So we go back to the changed log. Add each side alias. So if you have a power cube from Thermal Expansion and a mechanism cube, then we can pull energy from the top here. You can say output to each side, so that is new. What's going on here? Label A is connected to the world, not detected as a valid inventory. That's strange. So it is receiving power. Hold labels. I don't know why that's not getting rid of this label. There we go. So in the program, we're saying input from the top side and then output forge energy to each side of the B inventory. If we look at this, then each side technically includes the null direction, which some inventories have different behavior for. So that's why it's inputting even though none of these are actually set to input. But if we add all of these to input, then it'll actually start accepting a lot more. So that's the each side alias. And then let's see here. Spoiler for the change as well. Look at that program. Forge energy now has an alias with fe, rf, energy, and power. Improve error logging when resource voiding detected. This rarely happens, but when it does happen, then it will give more info about what items are being voided. Fix bug where programs work without inventory cables. Yes. So if we had a setup like A and B, then this shouldn't work if we have a simple program inputting from A to B. Because this is not a cable. But if we make it a cable, then it should work. So that was caused by a missing check. And that should be fixed now. Control-Able again now applies to Kitty Corner. So if you have a bunch of inventories on some cables, I want to label all of these as B. Holding CTRL and right-clicking will apply it to all of them. Now, that also includes inventories at the corners. Add config for minimum tick time. That is a requested feature. In the program, usually it's max, or you need at least 20 ticks between each statement, so if I reduce that, it's going to give us a warning. And there's now a config option for that. So if we go... Oops. So there's now a trigger minimum interval in text that can be modified. And additionally, there is a special case where if you're just moving energy, it will allow you to specify an even lower amount. So if we say BA, and we want to move energy, then this, because it's only moving energy, we can specify a one tick interval. So with the thermal expansion stuff, there's a rate limit on how much they can accept or move per tick. So if you're only moving energy, then this lets you do that. So if we say output items to be, then this will complain because only for forge energy by default will it let you do lower. And then... for this to tick faster if only moving energy, and then fix anvils causing problems when quitting the world. So for the crafting of stuff, the mod adds anvil falling recipes, and that was causing crashes when people were saving their worlds, and hopefully that's fixed now. And that's the summary of the changelog, so you can view the changes in the examples. There's that changelog that goes over everything we just covered.

text Hello there! Let's take a look at some of the performance information that Superfactory Manager gives you. I have an example here of a program that's taking quite a long time for it to execute. And if you look at what the program does, I have this barrel here with some supplies stocked. And when I take supplies out, it refills them. And if I put supplies back, it puts them back into storage. So this program is taking quite a while. And inside the storage, I have many, many barrels that contain all of the stuff that it's restocking it with. So if we look at how the program works, we see that we're moving stuff in and out of the storage. So here we're inputting and keeping a specific amount inside the chest, and we're outputting everything but that from the chest into the storage. So if it's one of these items, we're keeping 64, and if it's not one of those items, we're taking all of it back from the chest and putting it into storage. And then after we do that, we're taking from the storage and we're outputting, stopping after 64 of each of these items. So this program is taking quite a while, and if I wanted to see how I could diagnose what parts of this program are taking the longest, then the easiest way to do that is to just comment out some of the programs. So holding Ctrl and Slash will toggle the line comments for what you have selected. So if I go back here, now we'll see that we've taken a dip, so it's now taking 2 milliseconds instead of 8. So we can from this conclude that this bottom part of the program is taking the most time, which means that if we undo that and then comment out the top part, it should also take less time but not as fast as it was. So now we're at 2 milliseconds and then 5 milliseconds after we enabled the bottom one and turned off the top one, whereas with both of them enabled it's about 8 milliseconds. So now we're back to 8 milliseconds. And the other thing that the mod gives you is logs. So if we look at the logs and hit info, it will tell us how long each statement in the program took. So this first input statement that's moving things from the chest into the storage took very little time because the input statements don't actually do anything. It's when it gets to an output statement that it will look at the previous input statements for the work to be done. So this is effectively just adding a very small entry to a list. And then this statement is another input statement, so it's also very quick. And then the output to storage took 2.4 milliseconds. And then here we see the entire trigger took 2.57 milliseconds. And then when we get to the next trigger, there's another input statement, which of course is very fast. And then the output statement here, which took longer than the output statement above. So this gives a very detailed breakdown on how long each part of the program is taking. And this logs interface can give you quite a lot of information. So if something's not working in your program, then this can be used. The info log level is enough for you to get timing information. Some of the more detailed log levels give you a lot more information. So if we go to the debug, it will tell you what it's seeing inside the slots. And of course, this is a lot of information, which you're better off viewing inside VS Code. So for a single execution of this program, it generated 60,000 lines of logging information. So when it's tracking the storages, if we look at the top here, we'll get the diagnostic overview. So here's our program. And then here's our labels. We have 1009 total labels with most of them being storage and then one chest. So you can imagine that crawling through 1000 storage inventories takes quite a long time. So that could be part of the source of our issues. So inside here, The main thing to know is that the timing information for the triggers can be easily gotten by just hitting info. And the other part of that is people frequently ask, is it better to have multiple triggers versus using the forget keyword? So if we go back here and this could instead be forget, and the program would still work the same. And it's taking the same time as well. And then the only difference is if we hit view logs here, it's going to treat everything as one trigger. So the program trigger now is 8 milliseconds instead of, what was it, 3 plus 4 or whatever. So that could be one reason to choose having different triggers instead of using the forget keyword, is it could make it easier to diagnose which parts of the program are taking longest, at least until I improve the experience, because there's no reason that this couldn't be improved in the mod, but for now this is just how it is. So that's a quick overview on how to see what's going on, but you'll also notice is that this only logs one execution of the program. So when I go here and I click info it's only doing this for the the next time the manager ticks. And you can imagine that this becomes a problem when you have a setup where you have code that is executing infrequently, so 20 ticks, next to code that's executing every tick to move power. So if I go to this program and I hit view logs only get information about the tick for the moving energy so that part can be a complication so the easiest way to resolve that is to just come into your program and comment out the energy part because usually it's energy that's on a different interval than the rest of your program so if we were to come and look at the logs now we get a lot more information about the rest of the program instead of it only reporting on the energy side of stuff. So that's of course something that I can improve as the mod author, but for now, this is just how it is. So I hope that's been a helpful quick overview on how to deal with the performance stuff. And really, the easiest thing to do is just try, if you have two things that you think one might be better than the other, just try it and see what the graph and the manager tells you is faster. So that's all for now. Have a good one.

text Hello there, welcome to another video on Super Factory Manager. As of this video, 4.17.0 is the latest version of the mod. A lot has changed since the last video I've made, so I'm going to try and go over some of the basics for people who haven't used the mod before, and then I'm going to go over some of the more deeper stuff to try and help people understand how to get the most out of the mod. So last time I made a video, I said it would be short, and then it was about 40 minutes, and now I have a checklist of about 44 items that I thought would be interesting to talk about. So even at one minute an item, this is probably going to be another long video, but hopefully I can chunk it up to be a little more approachable so people can jump around to just the parts they want. I think most importantly, let's start with the absolute basics. So to use this mod, there's a few items provided by it. So if we do at SFM in JEI, then the most important ones to get started are the manager, the label gun, an inventory cable, and a disc. So there's recipes for all of these. They might change depending on the mod pack you're playing, but they should be fairly straightforward. So let's say you have two chests and you want to move everything from the left chest into the right chest. Then we can do this by connecting the chests using cables and a manager. The manager itself acts as a cable. And we're going to put a disk in the manager. And the mod comes with a bunch of programs. So this is a programming mod. There's a text editor and you just type your program. But it comes with examples. And this is how I hope people can use this to explore and see what's available, so we're going to start with a simple program. So a program has a name, which is what's going to be displayed on the disk when you hover over it, just to help you organize. And then it has a list of triggers. So in this case, this program has one trigger. Every 20 ticks, it's going to execute the following code. Input from A, output to B. And lines starting with two dashes are comments, they do not affect the execution of the code. So in this case, we're going to be inputting from A and outputting to B, but there's no item buffer. So when you say input, this is just adding it to a list of things to be checked when you do an output statement. So let's save this code by hitting Done or Shift-Enter, and we'll see that there's some warnings on the disk now that we're defining labels in the code, but we haven't applied them in the world. So to address this, we can use the label gun, shift-click on the manager, and now it has the labels in the code copied into it. So we can shift-scroll or use the GUI to say what label we have active, and then we can right-click blocks in the world to label blocks. And you can have more than one label on a given block. But for now, let's just say left chest is A, right chest is B, and then right click to the manager without shifting to push the labels. And now, there's no warnings. And if we were to put items inside here, then we'll see that they get moved into the other inventory. So there's a 20 tick delay, so every one second it's doing this. If we look in here, all the items have safely arrived, which is what we'd expect from such a simple program. So we saw earlier that sometimes when you make common mistakes, like forgetting to put labels, then the manager will warn you when you've made mistakes. So let's look at the checklist here. Placing cables in manager. You can edit disks in hand, which is what I'm doing now. And this is kind of useful if you want to have like a to-do list. And then using examples to learn syntax. So there's a bunch of examples. Let's just try and look at some of the others. So filtering, if you wanted to input specific items so let's say you only want to move stone or iron ingots then you can specify that after the word input but before the word from so you can say five stone three iron ingots you can specify the quantities and a filter and there's also wild cards So if you wanted to get all the ingots, then you can use regular expressions to do that, so dot star matches anything. And if you omit the quotes, so this is quoted here, if you omit the quotes, it's very basic, it just turns all the asterisks into dot star patterns. But it's pretty easy, like most of the time you just want wildcard at the start and the end. And this is optimized so that it's doing string.startsWith and .endsWith instead of using regular expressions for such a simple case like this. So don't worry too much about it, but... In theory, it's more performant to not use the patterns, and that's kind of a common pattern, is that people are asking, what's the most efficient way to do this program? Is this the most efficient way to do it? And to that, I'd say experiment around, because the mod comes with a little performance graph that shows you how long your programs are taking. And this is meant to be of the main indicator on if you're doing something crazy and if you should be looking for alternatives. But if you wanted to A-B test this, like set up two different programs and see which one's better, then this is a great tool to help with that. Item filtering is wildcard, and for the wildcards, the mod uses the item IDs. So if you hit F3 and H, that will show advanced tooltips. So as I'm hovering here, we see minecraft colon grass underscore block, minecraft colon chest in the dark grey there. So those are the IDs that Superfactory Manager uses to move items. So if I were to edit this program here, Say input chest from a then this is gonna match from any mod Items with the exact name chest, so I don't know if I have anything else in here, so if I say chest So this is sophisticated storage colon chest so actually this should also be moved if I grab some of those put chests those chests and dirt and So it's not moving the grass, but it's moving these two different chests from these two different mods, so Minecraft supposedly being a mod. But if we had something else, so this is an iron chest. This isn't exactly equal to chest, so this isn't going to be moved. But if I were to put star chest, then it got moved. So if you wanted to see everything that matches this, then you can put your cursor on the pattern and hit ctrl space, and that will expand to see all the items that match the given pattern. So this is useful if you wanted to just experiment around and see what you're trying to match. And by default, this is doing all the mods. And if you only wanted to move Minecraft chests, then you could say minecraft colon chest, and that would only move the chests from Minecraft. So now these ones are staying. And technically, these resource identifiers, the full form is sfm item colon minecraft colon chest. And this is because the mod can also move fluids, but by default, it's doing items. So if you omit this first part, that works. And if you omit minecraft, that works because minecraft, it matches all the mods without the pattern. So if we had this. And also there's no ctrl-z, so if you make a mistake then hopefully you've saved recently and you can hit cancel and it should give you a warning before abandoning changes. And there's also, you can toggle the line numbers, which has been contentious since they were introduced. So continuing with this theme, if we saw that you can do sfm item minecraft chest, and you can also Usually you just omit sfm because no other mods have added support. Like technically other mods can add their own resource types to this, but for now we're at the stage where it's just going to be me adding support for other mods. And you can also omit segments, so instead of saying item colon minecraft colon chest, you can just say colon colon to skip the word minecraft and then assume kind of a star at the end here. And this also works, so now this would remove all the items. But if items work, then surely fluids should work too, which of course they do. So input fluid, output fluid, and we have to specify it twice because by default it's only items. So now we need to replace these with tanks or something. So if I get a cauldron, and a tank, and a water bucket, then this is not working. Not because the program is wrong, well actually the program is wrong, but because of specific mod interactions. So by default, this is trying to input to the null direction, which mechanism blocks do not like. So we have to say top side, because we want to input directly into the top side of the tank, and now it's going to work. And that's a pattern that's kind of common. Some mods don't like being accessed from the null direction, which is kind of an implementation detail, but There's some tooling to try and help identify this. So if I control space on the B label, we can see what's the contents of the inventory. And for mechanism blocks, it gives a little warning here to try and help remind you. But there's also a inspector. So if I open an inventory and hit control I, then it will show the slots and some blocks don't play very nice with this, because these are our best guess at the slot numbers. Like if I look at a chest, then these are the slot numbers that would be used to help you try and write programs, because you can say input from fluid from A, let's do... I can say input from chest slots 0 to 5, and that's what this is kind of useful for to try and help guess that, but these do not correlate to the slots that are used for automation, just because the GUIs and the blocks have different implementations. But this can be helpful, but some mods like Mechanism are implemented in a way that the slot numbers don't increment properly when trying to preview this. But if we hit the Inspector, then it does the same thing as hitting Ctrl Space on the label from earlier. So we have this tank and we're moving fluid, and if you have something like a dripstone farm to try and get a bunch of lava, then this would of course work as well. And we'd have to label all of these, so holding control you can label the continuous blocks as well. So if I were to get rid of this cauldron and just place a bunch more, this makes it a lot easier to set up very large things and have it all work. So now all the lava is getting put into this tank and that's pretty nice. So let's look at this checklist again. Wildcards, quantity, so if we only wanted to move a certain amount at a time, so let's get rid of that, and say I want to move from B to A, and if I push this here, now because I've broken the cable, it's gonna say that we have a bunch of blocks that are labeled but not connected. So if I shift-click this exclamation point, it will remove those labels. If I pull, now they're gone. And let's try and set up a little program here. So we wanna move a little bit of lava from B into A. So input 10 fluid from the bottom side, again adding a side because this is a mechanism block, and outputting fluid to A, top side. So now it's adding 10 millibuckets of lava from the left to the right. So you can get very particular with the amounts that are being used. So let's do a little reset here. and look at the checklist. So we talked about wildcards, quantity, and then forget statements. So if you have some chests, let's do A, B. You can also just type a label, C. We'll put a disk, not the checklist. So there's a reset button with a confirmation dialog. We'll push the labels into it. So now we have A, B, and C, and we're getting some warnings that we're not using them. We'll purge the unconnected labels. So it also purges the unused labels. So let's push that. So let's say we want to input from A, output 1 to B, and 1 to C. Then this is going to work because all output statements see all the previous input statements. So if I run this, then now we can purge the labels and it should keep just A, B, and C. So got rid of stuff over there. And if we put some items into A, it's going to take two at a time because each of these inventories is getting one at a time. And if you were to do something else, like say input from B output to Let's do input 3 from B. We'll change it to outputting 10 to B. So from A to B, we're moving 10. And then from B to C, we're going to move 1. But this output statement still sees this input statement. So to avoid that, we can use forget. So this keyword will forget all the previous input statements so that you can have multiple different segments inside one trigger. So now, let's take a look at what's going on here. So it's moving 10 at a time into B, and then I believe three at a time, or just one at a time is moving from B to C. So because we said input three and output one, the lower of the two is the one. So if we were to change this to just not filtered, then it would be moving three at a time. So that's why forget is useful. And alternatively, you could just copy and paste the code and have the two different parts be in separate triggers. So the first part and then the second part. And this is exactly the same thing as using the forget basically. There's not really a performance difference either because people always ask that. That's how forget works. So that's kind of the basics. And we've talked about using the performance graph. And let's just keep going down the list. So except if you wanted to input So let's change those up here. So let's input ingots from A and then output to B. So this should move all ingots and leave stuff like grass. Of course it's working. But what if you wanted to move all ingots except gold? Then we can add that. So input ingot except gold ingot from A. And that should do exactly what it sounds like it's going to do. The gold will stay, but all the other ingots will be moved. So that's how except works. And this comes after the list of resources. So we can input ingots, stone, and that would move ingots and stone except for gold ingots. redstone triggers. So the mod has very primitive redstone support at this time. If we wanted to, instead of every 20 ticks, we could say every redstone pulse do, and that would make the manager run every time the manager gets a redstone pulse. There's not support for, like, exporting signals or reading signals from otherwhere. Right now it's just only on the manager. When you press the button, it does the thing. So if we put stone in there, hit the button, now the stone is gone. So retain is an interesting keyword which solves a lot of problems that some people have. So if we had, let's get a furnace and some coal. So let's say we had, let's purge everything. Let's say we had We have Ingredients, Furnace, and Outputs. So we're going to set up a very basic smelting program. So every 28 ticks do input from Ingredients, Output to Furnace, End. So this is going to move everything from the ingredients just into the furnace, which sounds good, but in practice is going to do some weird behavior like putting coal into the top slot. At least it should. Ingredients, furnace. What's going on here? Furnace is used in code but not assigned in the world. Let's push the labels. So now it's just complaining about output and we can see that the grass has moved into the top. So that was... so here we go now let's just get rid of this so now we see the coal is going into the top and the bottom slot which is not what we want and there might be so let's try output to furnace bottom side and let's output just coal so i think the bottom side is uh such that it will only see the slot for the fuel which is now it's working but if you had more than one furnace then this will be fairly greedy so it's going to put all the coal into the first furnace and the other two might get starved. So if we have coal in here then it's going to go all into the left furnace before it gets to the next one. So we can fix that by using the retain keyword. So if we say output retain one coal to the furnace bottom side then it's going to stop moving items once the furnace has one coal. But there's still a little something wrong so we'll see if what we observed. So if we have coal in our ingredients chest, there's one coal in the first furnace, but the other two don't have any. So this tracker is being shared between all the inventories labeled as furnace. And we can fix this by saying, output retain one coal to each furnace. So this each keyword at this location is going to copy the tracker for each of the blocks so that they all get their own count. And now we'll see that each furnace is getting one coal. So people keep asking for like a round robin distribution or like even distribution. So if I have one stack of coal and six inventories, I want it to be split evenly between them. But really, the core of your desire is that you just want each furnace to always have one piece of fuel in the slot. And this is how you do that. So now let's say we have a bunch of raw stuff that we want to smelt. look at the items. There's like raw iron and let's do raw gold. We want to smelt these ingredients. So we're inputting from ingredients and we're outputting the fuel, but now we can say output raw to furnace top side. And that's going to do the same thing where the first furnace is going to get it all. So we would want to change this to say output retain one raw to each furnace top side. So that will keep one item to be smelted in the top at all times. But because of the tick rate of the manager is one second, there's going to be a small period of inefficiency here. So we can just change this to output two. And now there will always be a second item for it to keep smelting immediately once the other one's processed, while keeping the rest of the ingredients just in here waiting for slots to be moved into instead of filling up all the furnaces. And Right now, we're smelting the stuff, but we're not taking the finished results and putting it into our output chest. So let's change from output, because that's a keyword, so let's just say results. So let's push this, and we'll say forget, so that we don't move the ingredients. So we'll say input from furnace bottom side, output to results. The input versus output behavior, we can input fuel to the bottom side, and that goes to the fuel slot, or sorry, output fuel to the furnace bottom side, but when we input from the furnace bottom side, like hoppers, that will pull from the results slot in the furnace. So now we see that the items are gone, and they've arrived in the chest. So that's how you set up a very basic smelting situation, and this scales very well, of course, if we were to add more furnaces. We just need to add more furnaces, more cables, and apply the label. And now all of our furnaces are smelting even more. So this lets you scale up very quickly. That's how retain works, and you can use it on the inputs as well. So if you wanted to leave a certain amount of items inside a chest, Instead of on an output statement, you can say input retain 25 from ingredients. So that will leave 25 items in the ingredients at all times. So if we have. Six there, then once one of the furnaces takes a coal, it's going to stop and leave some there. So if we take a coal and then take another coal, it's not going to take another because we've told in the program to leave 25 items in the ingredient chest. So for you to add. an iron or the raw materials, then it sees 25 coal and it can take the rest of this. But that's how retain works on an input statement. So that and look at our checklist. So we've talked about fluids and the anatomy of a resource identifier. Example that shows the supported reset identifiers. Yes, so if we look inside the examples, then there is one called Resource Types, and at the bottom of this, it contains a auto-generated list of all the resource types that the mod has support for. So, if you're ever wondering what is supported, then opening this, depending on what modpack you're playing, this will contain all the ones that are available. So you'll notice Forge Energy is here, and there's also one called Mechanism Energy. So Forge Energy uses integers for the movement logic, and Mechanism uses a floating long type, which is much larger than the maximum integer value. And Superfactory Manager just uses normal longs for movement. So if you wanted to move the max long energy per tick to an energy machine, or a mechanism block, then you can use Mechanism Energy to get a little bump there. And we've seen directions and slots. There's also a reordered syntax. So instead of saying input from ingredients, you can say from ingredients input. Some people like to think in the other direction and that's something that this mod supports. kind of the intermediate stuff. So what is under advanced? So code inspections, we've kind of seen that. There's a little bit more than what. So we've seen kind of doing it on a pattern, control space, it shows everything that matches that pattern, and doing it on a label opens the inspection for that label. So in this case we have several blocks labeled as a furnace, and the items that we can see, so in slot one we see that there's one coal, and in slot zero from the north side we also see the coal, yadda yadda. And this does it for each of the blocks for each of the directions. We can do it on an input statement, so if we control space on input, hmm, strange. Then we see the items that are available in that. So input from furnace bottom side. That should only see the results, but I'm kind of wondering why it's seeing the coal. It's a little strange. And if you control space on an output statement, then it sees what the predicted inputs are and the output. So, I'm wondering why it can see... Oh, you know what? It's because when it's doing the prediction generation, it's accessing it from... the null direction, maybe? I don't know. It's not 100% accurate, but it can be mildly useful. uh, slot inspector, we've seen ctrl-i, each resource, each label. So we've seen each label to kind of reset the counter, but each resource gets interesting. So if we had, uh, a program like this, so... I'm trying to think of a good way to show this. Let's just set up a different situation. Let's do a manager and two chests, and we have some ingots. So let's say I want one of each type of ingot to go from the left chest to the right chest. So let's put a disk in here. So we want to input from left, output, retain one each star ingot to right. So this is using the word each but in a different spot. So previously it was after the word to to reset the tracker for each of the blocks labeled. But here we're saying, output retain one each ingot to write. So this is going to create a different tracker for each resource that matches this pattern. So this is a little inefficient depending on how many things match the pattern, and I think the mod will actually prevent it from working if you don't use a restrictive pattern. So in this case, It would expand to more than 512 trackers, so if you are playing in a modded pack and you have several thousand items in the game, then that's probably not what you intended to do, so it will fail if you don't be a little specific. So in this case we want to output one of each type of ingot at most to the right. So if we run our program here and clean up the labels, now we just say right and left and push. So now we have one of each type being put into the right. So you can imagine that you can do this with seed types and phytos as well. So that's actually something else. I think I have this. I don't use these very much but I know everyone uses this mod for them. So let's see if we can set up a very basic setup for these. And we'll need a water source. which Superfactory Manager also provides. So this water tank can be used as an infinite water source if you have two water sources next to it. I think you can waterlog these. So now this is providing water, if we add some cables. Now we just need power, so let's get a cube. So we want to edit our program, so we're going to Every tick input forge energy from cube output to Fido. And because it's a mechanism block, we want to specify a direction. And I don't think it matters for thermal expansion, but this should be the power for the machines. So we. Strange. What is going on? Invalid program. Minimum trigger interval is 20 ticks. Yes, that's right. So for item and other resource movement, it's 20 tick minimum. But for Forge Energy, it's a single tick is the minimum interval. And now we see the graph has sped up here. And now that it's compiling, we can grab the extra labels. So we want these to be FIDOs and the cubes. So now these should be receiving power. And the next step of this is to get the water into them, so we can say forget, actually no, because we need a different block. So now we have water. Hmm. Oh yes, of course, fluid. So that actually is a good example of this warning. So it says statement output to FIDO at line 7 column 4 uses resource type item which has no matching input statement. And this is a very common problem where people are saying I'm inputting the fluid but it's not moving and that's because we've forgotten to specify fluid in the second location as well. So now there's actually a warning that shows us that. So it says line 7 column 4 and we can toggle the line numbers with this button. So line seven, we're outputting items instead of the fluids that we're supposed to. So now we have no warnings and the Fido's are receiving water. So then the next step is some seeds. I don't know what seeds I have in my instance right now, but let's just get a bunch of wheat and pumpkin and melon. Why not? So we want to move all the seeds into the Fido's. Inputs from seeds. output to Fido, and let's say output 2 to each Fido. And we want to just leave 2 at most, so we'll say output retain 2 to each Fido. So that should work once we put the seeds. So now we see that there's seeds. I think these also need like fertilizer or something. This Fido grow stuff. So we want each of the machines to have this. So now each of these is getting two seeds and two phytogrow, in theory. But this one is not connected by cables. Label it and push. So now it has water, energy, this stuff, and the seeds, and it's moving very slowly. So I think there's an augment that can speed this up. And we want the processing speed one. So that's going very quick. And we need a results chest. So let's forget input from Fido, input to results. And this is not going to do what we expect it to because I think this is going to pull out the seeds and the fertilizer as well. Results we need to label. So let's pull, label and push. So now we're getting everything, which is not what we want. So let's stop that. And if we open the inspector with Ctrl-I, we can see that slots 2 to 5 are the output slots. So because we're accessing from the null direction by default, it is bypassing the side config. So we don't have to go into each of these and say, output to the bottom, and then try and pull from the bottom in the code. We can just limit the slots that it's going to pull from to use slots 2 to 5. That should fix the problem where it was pulling the other thing. And these augment slots, when we have the inspector, it shows the slot number, but that's just because of the way this slot overlay is working. You cannot access these with automation, unfortunately. So now it's working. We have ran out of fertilizer. but the results are being properly put into our output chest. So let's get rid of the seeds. Or actually, we want the seeds to be recycled. So let's say, input from Fido, output star seed to seeds. So this should move the seeds from the results into the seed chest and then everything else that remains will go into the result chest. Get rid of these. You should see these numbers go up now. So it looks like we're just on melons right now. We've ran out of ores. So let's say you have some modpack with a bunch of seeds for different ores and stuff, and you want to make sure that you are properly farming every type before you start choking it out with just one. So if we have melon seeds, let's also label these, so CTRL, right-click, paste. Now all of these are also active, but they've all received melon seeds, when perhaps you want more variety in this, and I think these can also work on saplings. So that's another good thing to test. So let's say I want to grow one of each type of these, and then the rest of the phytos can do whatever. But I want to make sure that I'm always growing one of every type. And to do that, we can change our programming level. So here is where we're outputting the stuff. So we can change this to not be each Fido, but we want to pertain to each seed to Fido. So this will make sure that For each type of seed, across all phytos, there's at least two active. So let's see what that does. Too much stuff in my inventory here. If I take the seeds out of this, now we have wheat, pumpkin, and sweet potato. Oh, and it stopped because some of these others have melon. And let's also edit this to add to each sapling. So across all phytos, we want in total two of every type of seed and sapling. So we take the melon seeds out of all of these. And now we have spruce, melon, oak, birch. And if we add more of these, then I think they'll stay empty because we've added everything. So these ones at the end, Let's see, we have one there and stuff here. So now, we are farming one of every type, but we have some extra capacity that we're not leveraging. So we can just add a separate output statement. Say, output retain to star seed, and let's do So on a separate statement, we will use the rest of the phytos to just farm whatever else we have. So now the rest of these are active, keeping two ingredients so that when the first one finishes, the next one can immediately start. And we've set it up so that there's always one of each type of thing being farmed, and the results are going into this chest here. So again, here's the code. Just to go over it again, every tick, we're moving energy from the mechanism cube into the phyto. And then we're also moving water from the factory manager water tank. And then we're moving from the seed chest, keep two of each seed variant and each sapling variant in total across all the phytos. And then for the leftover ones that haven't received anything, we're going to keep two of any type of seed and two of any type of sapling inside each of those. And then We're also keeping to phytogrow so that each of them has fertilizer. And then in a separate block, inputting from the result slots and outputting to the seed chest for all the seeds, and we also want to match sapling, and then outputting to the results. So all of these saplings now in our code would be moving back over here as they finish growing. We didn't augment the rest of these, so this part, I think, if you right-click... Yeah. So you can install the augments with a right-click. So now these should all be running at max speed. Watch as the results pour in. So that was each resource, and then forgetLabel, ifHas, ifRedstone. So let's leave that running and take a look at the forget thing. So if we have a more complex scenario, so let's say we have two chests. Let's get a disk. Start with the basic program. So input from A, output to B. And if we say input 10 from A, output 1 to B. This gets a little complicated. So we're inputting 10 from A and C, so 10 total across both of these, outputting 1 to B, and then forgetting the input from C and then outputting to D. We're going to need some more inventories here. So what's our program doing? Input 10 from A and C, output 1 to B. So across A and C we're moving 10, and when one of these runs out, it will only output to D from the A inventory because we're forgetting the C. label I don't really know when this would be applicable but when you say just forget with nothing it'll forget all the input statements but you can forget specific labels and that is also a thing it's kind of hard to explain so I think I'm just gonna stop trying and then if so if statements are a thing so if we have a chest and we only want to move from the left chest when there's a certain amount of items somewhere else it's actually an example for this So input from source, and then if the source has greater than 10 iron ingots, then output to the destination. So let's just leave it at that. And we need to label it, so we have source and dust. So now when there's no iron ingots, nothing is going to move. But once we have some iron ingots, then everything is going to be moved. So I recommend you to check out the examples. In this syntax, gt means greater than. That's the same thing as doing the greater than symbol. So it supports both. You can do greater than, greater than or equals, or ge for greater or equals, less than, lt, less than or equals, eq for equals, or just an equals sign. So if we do equals 10 iron ingots, then it'll only move when there's exactly 10. So if we cut this down, 9, 10, bam, everything moves. And I think this works with redstone somehow, so if... I need to actually see the example because I forgot how that looks. Redstone signals? So this checks the redstone signal on the manager block. You can do if redstone greater or equal to 2 or whatever. So that checks the signal strength on the manager. So you can have that in a timer where it's checking that as well. So if has and if redstone. So there's logs. If we look inside the manager. So let's just change this input from source to dest. And let's make this a redstone trigger. So when we press this button, everything should go from the left into the right. And... If something's going wrong or we're just trying to see what the heck is happening, we can hit View Logs and this brings up a log screen that is unique to each manager. So let's say we want to see just info. Now the screen will turn blue to tell you that the logs are active. And when we run the program, it will reset the log level after the program has been run once. So if you're on every 20 ticks or whatever, then it'll reset very quickly. But for now, we can scroll up and look at the logs. The scrolling is a little jank. I haven't been able to fix that yet. And we can see a little bit of what's going on inside the manager. So the program. The input statement took a very short amount of time, because input statements just add themselves to a list and don't really do anything. But then the output statement is where a lot of the actual logic happens, and it took an entire millisecond. So when you have the logs active, things take a little longer, but it will help you see which statements are taking more than the others kind of thing. And then some of the info level logs, it discovered two input slots. So of course there's 27 slots in a chest, but two had items. And a bunch of output slots, some of them were full so that's why it's less than 27. And it moved a bunch of items. So this is mostly for diagnostics, you're not really intended to kind of read it in here, you're probably just going to copy it to VS Code or something. But if you have something really wrong with your program, then we can change it to debug and see what the heck is going on. So we run it, then when we view logs we get a lot more information. what is the manager observing and what's the manager trying to do and in the worst case scenario something's really not wrong we can use the trace level and that will get an incredible amount of detail that most people won't care about so there's a lot of caching involved so previous versions of the mod has had issues where it just doesn't work because Like, if you leave the chunk and come back, then a cache wasn't being invalidated or whatever. So that's why there's this rebuild cable network, and there's also a slash command. If something's not working, then either your code is wrong, and either there's an error or there's not, or it could just be a cache problem. So this button, click it as much as you want, but just once would be fine. And if it's still not working, then it's probably your code and not the mod. Or it could be a bug. Who knows? There was plenty in the past, and there'll probably still be a lot waiting to be discovered. So that was the logs, and there is a VS Code extension manipulating redstone signals. So people want to change an output signal, and that isn't really something that can be done yet, except if you consider just using a comparator. So if we have comparator and redstone. So if I want to make a high redstone signal, Then we can just have a bunch of junk in here. Let's say every redstone pulse outputs 25. So boom, now we have redstone control using SFM. Kind of. So we've looked at smelting, phytos. There's an example for inscribers. So actually, I'm in the dev environment right now, so if I just do test run v2. So for inscribers, the mod actually comes with an example for this, which hopefully is working, but this. So we have our ingredients and we have the individual. printed ones. We have the silicon, or three silicon, and then one for each type of calculation, logic, and engineering. And then the final results, we have three of those. So if we pull the labels from this, we see we have silicon, the last step, and then the intermediates. And all of this is being put into the results. Gather supplies to chest adjacent to a crafting table. So I have a test for that as well. if you have a huge kind of storage network and then you have a tinkers crafting table that shows the inventory adjacent to it, I don't have tinkers in this instance right now, but let's say you just wanted to keep some crafting materials on hand so that when you use all your iron ingots it replenishes and if you have extra then it takes them back and puts them into storage then this is something else that you can do as well and it's a little laggier than some of the other programs so this scary nine milliseconds you could probably replace this with like a pressure plate and a redstone trigger instead of a timer but this is just for for testing so the program we're inputting from the chest so to take the extras we're inputting everything except for keeping 64 of the stuff that we want to keep in the chest. And then every 20 ticks from the storage, we're outputting into our side chest to make sure that we have 64. So it's taking the extras and putting them into storage and then replenishing whatever's been taken from here. So this is something else that you can do. You could probably build a clock with this, so every 20 ticks, like, move an item into a slot, and then if the slot has more than 8 items, or like, checking the number of items in the slot to update the item count in a chest to update comparators. Let's see here. Going for 50 minutes already, holy cow. Turning a chest into an autocrafter and a furnace helper. That could be kind of fun. Probably faster than me trying to build a redstone clock. So if we had a furnace, then let's say we have, do I have an autocrafter? I do. So we could say these first nine slots are going to correlate to the slots in this crafter, and then this slot would be the input slot to a furnace. So this would be slot three. So input from slot 3 and put it into the furnace and of course we need to label everything so we have furnace and chest. So this gets put into there and we could have the results move as well. So input from furnace bottom side and we also need fuel. So slot 5 is going to be our fuel slot. So if we put fuel here, it should move into the furnace. And now it's smelting our items, and that should be moved back into here. There's no way of ironing it, just by putting items inside here. And let's see, how does this work? I think you have to encode the recipe, I'm not entirely sure. So if I wanted to make planes. Actually we need logs. Yeah, so this one won't work, but I think in like the new vanilla versions there's a crafter. So you could have a program that for each of those slots would move them into the corresponding slot in the crafting block and then put the results in there as well. So you can kind of create a mini interface where the different slots correspond to the different machines. And of course in Modded you have so many different machines you could have furnaces and this kind of hub where you would use a larger chest and be able to create a way to access all your machines from one central location. Which is something that I thought would be cool but haven't really gone all the way with in terms of creating a demonstration. And there's the G4 file, so let me see if I can switch So this is the file that determines the actual language. People are asking like, what's all the keywords available in the mod? This file is what you want. So somewhere on GitHub. this is available, and the syntax is .g4 is for Antler, A-N-T-L-R, which is the kind of parser that the mod uses. And we can see that program here is the entry point, so a program has an optional name, so the question mark is optional, trigger, which could be zero or more, so there's an asterisk, and then the end of the file. So a name is the name keyword followed by a string, and then a trigger is every interval, do, block, end. So an interval could be every tick, or every five ticks, or every five seconds, or it could be every redstone pulse. So these are kind of the code labels. This is a timer trigger, this is a pulse trigger. So to me this is fairly understandable, but of course to less programming people this might be a little alien but if you want the the truth on what exactly this mod supports then this is the grammar file so if you can match these rules then it should work so i think uh antler preview so if i say every So IntelliJ has a little preview for this and it can show the tree that this gets parsed to or whatever. I don't really expect people to use this, but the truth on what the program is, is this file. So if we look at an input statement, so every 20 ticks do input from A, so a block. is any number of statements and a statement could be an input statement and then an input statement says input and then some matchers excluding stuff optionally and then from optionally each any number of labels So if we look at an input matcher, it's a movement, and a movement is a limit, comma, any more limits, and then trailing commas are okay. So a resource limit is a limit followed by your resource ID, and a limit is a quantity and a retention, so a quantity could be like input to each resource ID. So I, let's see here. If we open up this preview thing, it might actually help. We say input to each ingot from A. So the input matchers here is the movement, so we have the limit to each star ingot from A. Then we see all the sides. I'll leave this as an exercise to the viewer. So that's another very long video on Superfactory Manager. I think I've gone over most of the important stuff, but I encourage you, if anything, to take away from this to look at the examples provided, and they should be a pretty good summary on what the capabilities are. So if you forget how something works, it's like, oh, how do I do an if statement? Then the if statements examples should help jog your memory after watching this monster of a video. So thanks for watching, and thanks to everyone who appreciates the mod. I encourage you to, if you encounter any issues, then just tell me on GitHub or Discord or whatever, and I'll do my best to fix them. Enjoy! Hello there! I just watched the entire video, and I realized that there were some things that I didn't quite explain in as much detail as they probably deserved. And if we look at all the items provided by the mod, there's a few things that I did not go over yet, so let's look at those. So there's a network tool, which is mostly a diagnostic thing, but it will show you all the labels. So usually just the ones that you pulled with the label gun, it'll do the in-world preview. But with this, it'll show everything. And when you right-click on a block, it will give you some more inspection stuff. It's mostly for diagnostics, but it can help you see what blocks are labeled and which ones you might've missed, or looking behind your walls and seeing what's going on there. So that's the network tool. And then there's the printing press. The printing press. So this block lets you do a few different things. So if we look at the usages in JEI, we can clone things with it. So we can copy maps, books, enchantments, and programs. So if we wanted to duplicate an enchantment, then let's look at what that would take. So if I have projectile protection three, and I want to copy this, then in order to do that, Let's see here, we need the plate for that, so to get that we need an anvil and an iron block, I believe. So if we were to place this in the world and then drop an anvil on it, it will convert the iron block into this printing form. And we can use this form to duplicate the book. So to do that we need some experience goop as one of the ingredients, and to get that we need nine experience shards, and you can get shards by crushing a book on obsidian instead of an iron block. So we get some obsidian and some junk enchantments. That would get us the shards and after a little bit we can get the goop. So this we use the printing form, the goop, and we need a book. And then to actually activate the recipe we need a piston. So piston facing down once activated. We'll consume the ingredient and create another book. So the form is reusable, but then the goop is consumed. So you can turn nine kind of junk enchantments and copy one of your better ones. And of course this is also useful for using, like you can clone programs if you wanted to automatically create a factory that uses the programs to create even more programs, then that is now possible. in addition to some other fun stuff. So that's the printing press. And the water tank, looking at the tooltip, it says requires two adjacent water sources, more effective when also adjacent to other water tanks. So I'll show how that works. So by default, when it is active, it will provide an inventory that has an infinite one bucket source of water. So if I get a really large mechanism tank and a program, So let's say every pulse we want to move from that water into the tank. So let's do... Every redstone pulse inputs fluid from water output to tank top side. So this, we need to apply the labels. My understanding is correct, that should move one bucket of water. Water tank empty. Very strange. I'm surprised that's not giving a warning because that should be input fluid to tank. There we go. So now we have one bucket of water. If we were to expand this to have a second one. then they get a bonus from being next to each other. So I think this would give four. So let's reset our tank and look at what's inside here. Oh, I already clicked it with the tank and it drained it. But now they're providing two buckets each and there's two of them. So now we got four. So if we had another one, it would be even more. So empty. Now we have nine buckets of water. Nine. I forget what the math is, but you get more the more you have. And these are technically infinite sources, so if you want to achieve it, then you can do multiple input statements, I believe. I'm going to save from water. So that can, they can be drained from multiple times at once. So if we completely empty this, how much would we get from this? 54 buckets of water. So that's how you use the multiplication bonus on the water tank. Some other blocks as well, you can kind of cheese by doing multiple operations in a single tick. So if we had a Fido and a power cube, then usually there is a limit on how much these machines can accept. So if we have a disk, So for every pulse, let's input from the cube into x. And this will move a certain amount of RF. So they only accept 200 RF per operation. But if you just double this up, then we can move a little bit more. Now we're at 1,200. That's a... When mods implement a per tick restriction, then sometimes they do it like per side. So if we remove the cheese, but 2x each side, then this will, instead of using the null direction, it'll use all the normal directions. So up, down, left, right, or north, south, east, west, whatever. So this will also move as if you were having a cable on each face of the block, so if we press this, then now we have 1,400, so 200 times all the directions? I thought there was... Yeah, so if we double it up this way, then that would be even faster than what we had previously. So that's a little bit of cheese, but usually you're fine just doing every... without needing to do this, but... That was one thing. And then there's also round robin, which I forgot to explain. So if we have a chest and a disc. So let's say we have three different chests. And let's say A and B. So if we say input from A, output to B, then that's just going to fill up the first B before moving on to the next. But we could say output to B, round robin by block. Let's just say input 1. So now, this will alternate between all the inventories labeled B every time it ticks. So it got one grass, and now the other two are going to tick, and then it gets another grass. So it's rotating between them. They're all labeled with the same thing. We're round robin by block. And there's also, we can do B, C, D. So if we had multiple labels, then it would see all the positions that all of these have labeled. So if we have, let's pull from here. Let's hit that, pull, there we go. If we hit B, C, D, then doing round-robin by block will see what distinct positions are available and then rotate through those. So even though these two chests have multiple labels, round-robin by block should be going 1, 1, 1 instead of prioritizing these two, I think. So they're still even, so it looks like that's more or less correct. And so there's round robin by block, but there's also round robin by label. So now it'll rotate between B, C, and D. So let's clean this up a little, get rid of the... make this a little clearer. So now it'll rotate between all the things labeled B, C, and D. So that's the round robin stuff. And the other thing I mentioned earlier, but didn't explain very well was the mechanism energy type. So if I test run this, so mechanism has an induction matrix, which is a giant energy storage device. And to maximize the throughput here. we can move mechanism energy instead of forge energy. So if we do fe from source. So using forge energy, this can only move max int per operation just because of the energy handler interface or capability only using integers. So if we look at this, then it's going up by about two, what is it, two billion is the max int? pretty good but not the fastest so if we change it to mechanism energy then that should move faster yeah see now we're going up by a much larger amount per operation and of course this is restricted by these providers or whatever so this multi-block has The blocks inside can either upgrade the capacity or the throughput, and I think these are all- I have it like max throughput with one layer of induction, so everything on the bottom layer is capacity, and then the rest of this is throughput. And even then, it's not enough to completely fill it in one operation, because the creative cube has huge volume, but we get pretty good. This is usually larger than I think what most people would set up, but that's how you use the mechanism energy type, and this probably works with the other mechanism blocks as well. So I think that's all the stuff I forgot. And of course the mod has, in the examples, you can see some of the known issues. So right now there's... I think I actually fixed this, so I should probably get rid of that. But having a bunch of labels on the label gun can corrupt a world, so maybe try and avoid that. I don't know what the exact details on it, but it's like chunk ban, I think is what people call it, where you just have way too much data. And the position when you try and click to set your cursor position in long scripts can get desynced. The scrolling is sometimes messed up. And yeah, so there's also a changelog available in the game that you can see. So 4.17 is the latest, and then I'm in the development environment, so that's just a placeholder. So goodbye for now.

text Hello, and welcome to the changelog video for Super Factory Manager 4.20.0. To start things off, there's a new command, slash sfm changelog. Start looking there. There's a lot of stuff included in this release, so I'll try and be brief. Let's start by just going through the basics of the mod. You'll need a factory manager, some cables, a disk, and some inventories. Let's place our manager, and we'll connect it to chests using our cables. and immediately the new edition there's a fancy inventory cable so it's smaller than a full block which a lot of people have wanted and we'll need a disk so we can insert that and in the examples we'll start with a simple program so every 20 ticks input from a output to b end so we'll hit done and apply that and now it says label a and b are used in the code but not assigned in the world new edition in this update did you remember to push your labels using the label gun so that is a common thing that people have forgotten to do, so I've added that hint. So that means we need to take our label gun, pull the labels, label the inventories, and then you'll notice that if I don't push them, it's giving us the hint, did you remember to push? So now when we push, all the warnings are gone, and if we put something in this chest, it will be transported over here from A to B. And along with these cables, part of the update is facades, so we can apply using having the network tool in the offhand. then a block in the right hand we can apply blocks to these cables and you can hide the overlay because by default the network tool shows you the position of cables so if they were hidden behind your walls and stuff you still see them so using alt a new addition you can toggle on and off that overlay and of course the facades also work with the fancy cables so the the smaller than full block ones and they work with transparency And there's a few other things to note. So if you hover on the network tool, it will tell you how to use it to place the facade. So place in the offhand. And then there's also control-click, alt-click, and control-alt-click to control the behavior of the application of the facades. So if you have a floor made out of the cables and you want to do some kind of fancy pattern with it, then you would want to be able to apply all of these at once. So let's say I want that to be the starting floor and then I want some of the glass to be that way, but let's say I want to change the glass to be something else. Let's make it wool. I can apply it to just that glass using alt-click, but if there's also some wool over here, then let's say I want to change that again and make it red. Then I can change just a few of it. So are you sure you want to update the appearance? And in this case, it's giving us the warning that we're going to update a lot of blocks at once. So that was Alt-Click changed it. If we hover over the network tool again, Control-Click is contiguously. Alt-Click matches the blocks without having them to be touching and Control-Alt-Click does everything. So if we do Control-Click, it'll do just these ones or those ones. But if we do Alt-click, it will change all of them. And you get a warning if you're going to change a lot of stuff at once. And then if you do Ctrl-Alt-click, it updates everything. And this also applies, you can change the shape of the tables to be full block or not. And if you have the big ones and the fancy ones, and you try and change just some of them, then it hopefully It's trying to not let you mess up by mistake, that's the gist of it. So mess around with that. So let's keep looking at the changelog. There's a bunch of performance improvements, we'll try and skip over the nitty details. Edit in hand reminder, so if you have a disk you can edit in your hand Push label warning, optimize each limits So if you're using this syntax where output retain one each seed This is matching for all of the items that match seeds So in this case I don't have many seeds but if you have something like mystical agriculture Then you have a lot of seeds in the modpack and this is much optimized now And in fact you can do it like this, so input one each from A. So this will take one of any item, and previously this would have lagged because this would have expanded in the code to match for each item. So if we put like this, then it's taking one of each type of item at once. So this is new and not laggy. So that's part of the change. In fact, what I should be doing Let's just go through this. So we've looked at this. Support for ore. So if you want to move 5 stone ore dirt, that is now possible. Which is different than moving 5 stone and 5 dirt. Because if you just had a comma here, then that is different. But if you had 5 stone ore dirt, then it would only total move 5 stone ore dirt. Yeah. Some correctness stuff was fixed. What else? Add notice to the inspector, so the slot numbers, if I hit Ctrl-I, then it shows the slots corresponding to the inventories, and I added the warning that says they don't always correspond to the automation slots. So if you have something like a metallurgic infuser from Mechanism, some mods, the way they're implemented, the numbers don't increment, and that's because the slots in the GUI don't always correspond to the slots that are exposed through automation. So if I put some iron in here, and some coal. Then the network tool that I have in my offhand, if you put it in your main hand, it will actually, when you right-click on a block, it'll tell you what is being exported through the automation stuff as well. And you can access this also using the Ctrl-I Inspector and then hitting Export. This also gives you some hints on what is being visible from the block. And of course, mechanism blocks require a direction for inserting and extracting resources. Some correctness bugs, we had a warning. Some syntax highlighting fixes. So people with, fix without and with logic implementation. So if you want to input without a certain tag, then that was previously not working and now it should be working. So if you want to input with tag ingots or without ingots, or you want something with ingots and chests, or like if you want to combine them or whatever, then that should work now. Let's see if we can do an example of that. Let's take you out. So we have input from A, and we'll do input with tag ingots. If we get some ingots, then is it singular or plural? Label A is assigned. What's going on here? So input with tag ingots from A. If you have COFH core, then that will tell you what the tags are, but I don't in this instance right now. And we said input without tag. And that's moving everything except the ingots. We said input with tag ingots or with tag, or tag, mineable slash axe, I think. So if we get a log, those will get moved, but not the stuff that are not compatible with axes. So all the ingots and all the stuff that's mineable with an axe are being moved, so that's the block tags are also supported here. And now the logical stuff is also working. We added fancy inventory cables, so thank you for the contributors who made that possible. As well as the facade stuff, a lot of The big stuff were contributed by others, so thank you to those, Burkino and the others. Optimize the label gun rendering, so some optimization. Optimization caching. Oh yes, the label gun. So if we look at the label gun. Right now we have A and B labeled, and let's say there's a bunch of other stuff that way as well. If you hit Alt, it will cycle through modes. So showing blocks with the active labels, right now I have B or A, it'll show just those ones. And then some people, if you have a million, or not that many, but a lot of blocks labeled with the same thing, then it might be desirable to only show the one that you're looking at as well if you're dealing with a lot of stuff. And of course, if you have multiple labels on the same block, then it will also work that way. So if we do A, it's showing because this has A, it's going to show all the labels for that block as well when you're in the modes. So that was optimized. So if you have many blocks labeled, then that's also going to be faster when you're holding it to see it. You can search in the label gun now. So if we have first, second, And now it acts as a search, and if you hit done, it'll actually just use the content of the text box as the next label, so there's a search in here now as well. The configs can now let you disable resource types, so there's also a new button here, view server config. So if you're a pack maker and you want to add a super factory manager but you don't want to let it move redstone, flux, or what is it, forge energy, same thing, or items or fluids or whatever, then you can disable that through here, so it'll give you like SFM item. Let's see if that works. So now if we try and move stuff, That might require a server restart, I forget. Oh, you know what? Viewing the server config here, this is just read-only. Like, this doesn't let you change anything. However, there is a sfmconfigedit command. So if you're a level four permission or operator or whatever, then you can edit the config here without having to mess around with the text files. So if we do it this way, then now, Change everything. No. Hmm. Well, I didn't test it that way, but I did test it for... Let's undo this. Maybe it's just item? The part I did test is if you change the timer trigger interval, so by default it's 20, but if you want to set it to 1, then now you can. If you're in single player and you just want to change it, or if you're on your own server, then you can change it so that every tick it's going to move some items. Or if you want to make it slower, and now they'll update. All the managers in the world will notice when the config is changed and they will work accordingly. So if we reset it back to 20, then it should work again without any intervention. Hmm, that's not good. Invalid program, what's up there? Minimum trigger interval is 20 ticks. Oh, yeah, because we added one. So it did work. It just, I had set it to one, not 20. I'm a little surprised that this one didn't work. Because I was pretty sure that that was the way it works. I'll have to look into that. It might be a restart thing. Continuing down the list, what else has changed? A warning when mechanism blocks are used with the wrong sides specified. So let's see what that looks like. So if I have a redstone. So let's get a chemical tank. So if we're inputting from the creative one and outputting to just a normal one. So we have A and B. So let's input. chemical from A to chemical to B. What is going on here? That should be warning. There we go. I had an older version of the mod. It gives a warning. Mechanism blocks are read-only when no input side is specified. And if we look at the code, it's just saying input chemical from A. But the warning is saying we have to add a side. So if we do from A top. side to be top side, then we have a different warning. Mechanism block side config at the given position doesn't agree with the statement. So if we look at the block, the side config top is an input, not an output. So if we fix that, then the warning goes away. And now this is being refilled with the chemical. If we increase the delay, it becomes a little more obvious. because it's taking the infinite amount and just filling this thing right up. But if we reset the sides here on both of them, we'll get this warning back. So the side config doesn't agree. But if we shift click, it'll attempt to fix the warnings and we'll actually go into the mechanism blocks and update the side configuration to agree with what the program wants to do. So here at the top is the input and here at the top is the output. And now it's working again. So those are the mechanism warnings for accessing without a side modifier and then accessing when the sides disagree. So that's fixed. There's a label gun pick block modifier key, so if I have the label gun open, I have a bunch of stuff labeled. If I hold Alt by default, it will let me pick a label as if you middle-moused it, kind of like the way you can pick a block in the game. But using Alt-click, you can quickly select labels from blocks. So if I have a bunch of stuff here... then I can quickly switch between them by just looking at the one that has the label that I want and using the pick feature Some fixes Confirmation dialog stuff, so copying and pasting and stuff, there's new confirmation dialogs The water tanks were optimized a bit and they have been buffed, so now they give more water per water. So if we set up some of these, they need water adjacent to them to be active, and if we look at them with the network tool, We can see it gives one bucket of water when only one is active, but if two are active, then we get two buckets. And if three are active, instead of three buckets, we now get four buckets. And if four are active, each of them is now giving eight buckets. So you can get quickly up to the max integer size if you span like 20 of these. And there's a corresponding command, so if they bug out, sometimes the cables bug and you have to reset stuff, so there's BustCableNetworkCache has always been around for a while, but now there's also BustWaterNetworkCache in case I messed up the logic to get this to work. NetworkToolOutput got some cleanup. The water tank added some reminder text to say that it only gets the bonus if the other tanks are also active. There's an issue tracker inside the mods toml so that when SuperFactoryManager causes a crash it will include the GitHub issues link in the crash report. Optimized some of the data structure so if you have 9000 blocks labeled it should not crash you for having too much NBT data. If you have a very large amount of cables, then when you break a cable, it's going to just tell the mod to calculate stuff again later instead of trying to do some math to figure out what cables to keep. Because if you have a lot of cables, so if you have a situation like this, and you break this middle block, then these other blocks are now four different networks. So when you break the block, it will It should unregister the other ones. I'm kind of surprised it isn't, but whatever. The network tool now works on dedicated servers. Rendering fixes. You should be able to move cables with pistons now, so let's see if we can test that. I don't know if it'll work with the facaded ones because they are block entities. Yeah, so that doesn't work. But if we have a normal cable, then that should be fine. So let's get our program back for the chemicals. So it's filling, and if we break the connection, it will not fill again. But then once we restore the cable, it fills again. So that was kind of working before, and now I've made sure to make it an intended mechanic. Managers will now initialize randomly on load, so if you have 10 different managers each saying every 20 ticks, they will start their schedule offset from each other, just by nature of it being random, so that should help if you have a very large amount of stuff being done by the mod. And the other thing is if you want to try and take full control over the scheduling of it, then there's a new syntax for aligning to the global clock to have managers do things at the same time. So it's a little hard to explain. So let's see if we can look at the demos for that. So there's, hmm, that's not good. Alright, so I can only guess that that's from when I messed around with disallowing the resource types, so... Maybe that's just bugged right now, but very few people are going to be using this for now, so I'm not too worried about that. I can fix it later, which means I don't have to upload all the jars again. But this, like changing the minimum interval, this was kind of the main intent that this was added, so that if you're playing in your single-player world, just make it dead simple to adjust this number. So setting it something higher will make all the programs update, and then setting it something lower You can, if you wish, change the defaults so that you can do every one tick and have it just work. Of course, if we look at the warning, did you remember to push your labels? So now every tick, it's checking and it's without having to restart anything. So only operators can run this command, but there we go. So let's back to where we were after that scare. On the to-do list is fixing the GUI scaling for this. Somewhere in here. Change log. Where were we? Global ticks. Let's see. Delete. The scroll bar is fixed, but deleting the text selection offset is not fixed, so be aware. Adding support for specifying global alignment. So if we look at that, then there's an example for timer stuff. There should be. Simple program, filtering change log, blah blah blah. Tag matching, thank you. Did I... Is it off the screen? Yeah, it didn't. Okay, at least it's included. I need to fix the scaling thing though. So the manager starts when placed, it's randomly initialized, and then you can do global ticks or global plus one. So if you want two things to happen after each other, then you could do every one ticks. Then you can also do every 20 plus 1 ticks do. So this will happen the tick after this one, which is distinct from every 21 ticks, because if it was every 21, then eventually these would synchronize and tick on the same time. But if you want it to always be the tick after, then that should work. Every 20 plus 1 ticks. Then A, B, and we can make U, C. Let's also change this to 1. So it moves 1 over and then the tick afterwards it moves it back. And if you want that to be synchronized to the global clock, then you can add G or global or whatever. But that synchronizes it to the world time, so if you have two managers running that exact program, then you can synchronize them easily using that construct. So the example has more details. So there's an example, confirmation stuff, fixing errors, performance improvements, config command, logging for if the mod voids something, which should only happen if another mod is lying, but moving on. Config values. There's a crafting recipe to clear the label guns. Hitting clear on the label gun should now reset the active label, so if I B selected and then I hit clear, then now there's nothing selected. Cleanup support Inspections There's hotkeys now for the next and previous for the label gun so if I You can shift scroll but if you're on like a laptop or whatever then scrolling might not be the easiest so there's now a keybind So label gun next and previous whoops, I went like that. And now without having the shift scroll I can do next and previous using the hotkeys. Facades, the facades have direction support so if I have a fancy one and let's get a log Then when applying this, it checks the side that you click for how it to be applied. So if you have... I think it works... I'm not entirely sure how the stairs behaves. I forget. It's interesting, but it depends on the side that you click, and it just grabs the texture or whatever. So if you have a block that has different faces, so let's say a furnace, and you want all your cables to look like a furnace, than you can. So the full blocks, it just picks the facing direction from that, but for these, the same face is used for all the sides. And of course you can change between the shapes, so the same way you apply facades, if you have the cable in the hand, then you can clear the facade or change the shape of it using the same way that you can apply the textures. Performance improvements, scroll bar. There's a fix for the industrial foregoing or meat fluid. That was previously not working and now it is. What is this? There was a crash when hitting the little question mark button to fix problems. So that's fixed now. There was a crash related to the disk item and stuff that grabbed the tooltip. So I think that was happening on servers where if you were using the creative search, then that would cause a crash. So let's see if I can verify that that's working. So if we go back to our server. I will hopefully not crash when I open the creative thing. Yeah, so that's working, good. And then... We're almost at the end. There is a printing press bug which should be fixed now, and I think that's about it. So in 37 minutes we managed to get through all the changes with a few problems along the way. All right, well, enjoy. Let me know if you have any problems. There's the GitHub issue tracker. So if you have any problems, then you can go there. There's also a Discord button here now. So if you want to say hi, then this makes it quite easy to hop in. So, see ya.

===

Summary

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\main\resources\assets\sfm\template_programs\redstone_item_move.sfml

name "Redstone item movement"

EVERY 20 TICKS DO
    -- redstone is a keyword
    -- I fixed it so that it can be used without quotes now :D
    input redstone from a
    output minecraft:redstone to b
END

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\main\resources\assets\sfm\template_programs\redstone_signals.sfml

name "Redstone signals"

every redstone pulse do
    input from a
    output to b
end

every 20 ticks do
    -- this checks the redstone signal on the manager block
    if redstone ge 2 then
        input from a
        output to b
    end
end

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\main\resources\assets\sfm\template_programs\a_simple_program.sfml

NAME "A simple program"

EVERY 20 TICKS DO
    -- on their own, input statements do nothing
    -- there is no item buffer
    INPUT FROM a

    -- all the magic happens here
    OUTPUT TO b
END


D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\vscodeextension\super-factory-manager-language\tests\VisualTest.sfml

-- Test here anything related to colors related stuff
-- Should be tested at least with 3 different themes, the default one for vscode
-- and another 2 that you like and have multiple colors

name "A program" -- color on name and "a program"
 
every 1 ticks do -- every, do using the same color, 1 using another, ticks using the same as fe::, item::, gas::, fluid::
   input fe:: from power_source1 -- label should have no colors by default, nor the number on power_source1
   output fe:: to machine
end
 
every 20 ticks do
   input from furnace
   output to furnace
end

every 20 ticks do
   if chest has gt 5 then -- gt, lt, eq, .... should have a color
        --Something
   end
   if chest has >= then -- >=, =, >, .... should have a color
    
   end
end

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\main\resources\assets\sfm\template_programs\slots_and_sides.sfml

name "Slots and sides"

every 20 ticks do
    input from a top, west side slots 0,1,3-4,7-9,21
    output to a bottom side slots 2
end

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\main\resources\assets\sfm\template_programs\furnace_manager.sfml

name "Furnace Manager"

every 20 ticks do
    input from "to be smelted"
    output 8 to each furnace top side

    FORGET

    input from fuel
    output retain 1 to each furnace bottom side

    FORGET

    input from furnace bottom side
    output to results
end

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\main\resources\assets\sfm\template_programs\timer_triggers.sfml

NAME "Timer triggers"

-- The manager's internal clock starts at zero when placed
-- It is also randomly initialized after being unloaded
EVERY 20 TICKS DO
END

-- Sometimes you may want more control
-- Instead of the manager's internal clock, you can use the world clock to choose when to tick
EVERY 20 GLOBAL TICKS DO END

-- You can also add an offset
EVERY 20 PLUS 1 TICKS DO END        -- manager clock
EVERY 20 GLOBAL PLUS 1 TICKS DO END -- global clock

-- There is also a shorthand notation
-- Don't mistake this for math lol
EVERY 20+1 TICKS DO END  -- manager clock
EVERY 20G TICKS DO END   -- global clock
EVERY 20G+1 TICKS DO END -- global clock

-- You can mix and match
EVERY 20 G PLUS 1 TICKS DO END
EVERY 20 GLOBAL + 1 TICKS DO END

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\main\resources\assets\sfm\template_programs\resource_types.sfml

NAME "Fluids and other resource types"

EVERY 20 TICKS DO
    -- the default resource type is sfm:item
    INPUT stone FROM a
    -- is the same as
    INPUT sfm:item:*:stone FROM a

    -- so if you want to move all fluids, you gotta do
    INPUT fluid:: from a
    OUTPUT fluid:: to b
    -- this expands to INPUT sfm:fluid:*:* FROM a

    -- these are equivalent
    INPUT fluid::water FROM a
    INPUT sfm:fluid:*:water FROM a

    INPUT iron_ingot FROM a
    INPUT sfm:item:*:iron_ingot FROM a

    -- older versions used to default to "minecraft" for items
    INPUT iron_ingot FROM a
    INPUT sfm:item:minecraft:iron_ingot FROM a
    -- this is no longer the case, since there isn't usually name conflicts
    -- if there are, you can just manually specify the mod id if you care
    INPUT totally_not_minecraft:iron_ingot FROM a
END

-- the following resource types are supported
-- (this example is generated so it's always up to date)
EVERY 20 TICKS DO
    $REPLACE_RESOURCE_TYPES_HERE$
END

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\main\resources\assets\sfm\template_programs\ae2_inscribers.sfml

NAME "AE2 Inscribers"

-- labels:
-- logic, engineering, calculation, silicon, last => inscribers
-- materials, results => chests
EVERY 20 TICKS DO
    INPUT FROM materials
    OUTPUT gold_ingot TO logic SLOTS 2
    OUTPUT diamond TO engineering SLOTS 2
    OUTPUT certus_quartz_crystal TO calculation SLOTS 2
    OUTPUT silicon TO silicon SLOTS 2
    OUTPUT redstone TO last SLOTS 2
    OUTPUT printed_silicon TO last SLOTS 1
    OUTPUT printed_calculation_processor, printed_engineering_processor, printed_logic_processor TO last SLOTS 0
FORGET
    INPUT FROM logic, engineering, calculation, silicon west side
    output to materials
FORGET
    INPUT FROM last west SIDE
    OUTPUT TO results
END
EVERY 200 TICKS DO
    IF SOME last HAS LT 3 THEN
-- There's a partially filled inscriber.
-- We want to shuffle ingredients to make sure there isn't a full craft
-- that is improperly distributed.
-- This trigger interval should be longer than the time it takes
-- to process a single craft so that SFM doesn't sabotage the crafting
-- process by pulling out ingredients from inscribers that have enough.
        INPUT FROM last
        OUTPUT TO materials
    END
END

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\vscodeextension\super-factory-manager-language\tests\SFMLFile.sfml

--This file is just to make sure the icon on .sfml file is place correctly

name "hola"

every 20 ticks do
    input 20 from interface
    output * to furnace top side
    forget
    input fe:: from interface2
    output item:minecraft:cobble* to chest2
    output * to fluid
    item::
    item:minecraft:
    item:industrialforegoing:laser* this doesnt
    ::cobble*
    item
    if boolean_expression then
        if boolean_expression 
        then
        end
    test> = > < <= >= --First one shouldnt have color, the rest yes
    end
    except Except exCEPT
    IF boolean_expression tHen
    end
end

every redstone pulse do

end

EVery Redstone pulse DO

end
evEry 20 ticks do
hollaaaaa
end

every 1 ticks do
   input fe:: from power_source
   output fe:: to machine
   
end

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\main\resources\assets\sfm\template_programs\if_statements.sfml

name "IF statements"
-- three inventories, all labelled "a"

--       a              a                 a
--  | 5 stone |    | 5 stone |    | 10 stone |

-- every a has = 5 stone    - FALSE (not all of them do!)
-- each a has >= 5 stone    - TRUE (alias for "every")
-- some a has eq 5 stone    - TRUE
-- one a has = 5 stone       - FALSE (more than one does!)
-- lone a has = 5 stone      - FALSE (more than zero or one does!)
-- overall a has eq 5 stone - FALSE (there are 20 stone in total, not 5)
-- a has eq 5 stone           - FALSE (default behaviour is 'overall')

every 20 ticks do
    INPUT FROM source

    if some source has gt 10 minecraft:iron_ingot then
        OUTPUT TO dest1
    end

    if
        not source has > 20 stone
        and source has ge 20 cobblestone
    then
        output 20 to dest2
    end
end

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\vscodeextension\super-factory-manager-language\tests\Warning.sfml

--This example comes from the github itself, currently it does 5/7 cases (blame the last forget)


EVERY 20 TICKS DO
    INPUT fluid:mek: FROM tank1
    OUTPUT TO tank2
    -- missing fluid output

    FORGET

    INPUT FROM thing1
    OUTPUT fluid::* TO thing2

    FORGET

    INPUT FROM tank1
    OUTPUT fluid:: TO tank2
    -- missing fluid input

    FORGET

    INPUT FROM chest
    INPUT fluid:: FROM tank1
    OUTPUT TO chest
    -- missing fluid output

    FORGET

    IF chest HAS > 0 stone THEN
        INPUT FROM chest
        INPUT fluid:: FROM tank
    END
    OUTPUT TO chest
    -- missing fluid input
END
EVERY 20 TICKS DO
    OUTPUT TO chest
    -- missing input

    FORGET

    INPUT FROM chest
    -- missing output
END
EVERY 20 TICKS DO
    INPUT 5 FROM a,b,c
    OUTPUT 1 to z1
    FORGET b,c
    OUTPUT TO z2
    -- no output uses input from b,c
END

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\vscodeextension\super-factory-manager-language\tests\snippets.sfm

-- Here you can test snippets (keep these 3 lines and delete the rest before commiting)
-- Currently there are 7 snippets (change number and text below if added more):
-- every, if, ifelse, ifelseif, input, output, basic and energy

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\main\resources\assets\sfm\template_programs\tag_matching.sfml

NAME "Tag Matching"

EVERY 20 TICKS DO
    -- these are equivalent
    INPUT WITH #forge:gems FROM chest
    INPUT WITH TAG forge:gems FROM chest
    INPUT WITH TAG #forge:gems FROM chest

    -- you can omit the namespace to match all namespaces
    -- these are equivalent
    INPUT WITH #ingots FROM chest
    INPUT WITH TAG *:ingots FROM chest

    -- some tags have multiple path elements
    INPUT WITH TAG refinedstorage:disks/items/* FROM chest
    INPUT WITH #tools/*/*_matter FROM chest

    -- block tags work for items too
    INPUT *a* WITH TAG minecraft:mineable/shovel FROM chest

    -- matching all remaining segments is possible
    INPUT WITH #the_bumblezone:essence/** FROM chest
    -- should match:
    -- #the_bumblezone:essence/life/grow_plants
    -- #the_bumblezone:essence/calming_arena/drowned_bonus_held_item"
    -- #the_bumblezone:essence/knowing/block_entity_forced_highlighting"

    -- combine with other stuff too
    INPUT
        3 RETAIN 5 WITH TAG #forge:ingots,
        4 dirt,
        WITH #mineable/axe,
        minecraft: WITH #ingots,
        stone,
    EXCEPT sand, gold_ingot
    FROM chest

    OUTPUT TO other_chest
END

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\main\resources\assets\sfm\template_programs\known_issues.sfml

NAME "Known issues"

-- Official SFM Discord:
-- https://discord.gg/5mbUY3mu6m

-- Official SFM issue tracker:
-- https://github.com/TeamDman/SuperFactoryManager/issues

-- JEI support is missing from some versions

-- Sometimes managers stop working for 'no reason'
---- "Rebuild cable network" gui button to try fix single
---- "/sfm bust_cable_network_cache" to try fix all
---- If it happens once, it will probably come back :(
---- I have no idea why this happens
---- If you can reproduce this, pls tell me how

-- Having like 9000 labels in a label gun corrupts world
---- because the NBT is too big
---- the network tool may cause similar situations

-- mouse drag selection desyncs with mouse position in long scripts
-- pressing tab jumps to the bottom of long scripts

-- outputting to composters (1.20.3+) without specifying a side
 -- will skip the check for if the item is compostable
 -- turning the composter into a trash can

-- "Press Ctrl+E to edit" and other tooltips show in edit screen

-- scroll bar in the logs gui becomes inaccurate as logs grow

-- CoFH inventories have integer overflow when moving items in large quantities
EVERY 20 TICKS DO
    INPUT seeds FROM mekanism_creative_bin
    OUTPUT TO phyto SLOTS 0
    -- sfm warning that resource loss has occurred because cofh returns erroneous values
END

-- Sorry for any inconveniences :(

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\vscodeextension\super-factory-manager-language\tests\SFMFile.sfm

--This file is just to make sure the icon on .sfml file is place correctly

name "phytos!!!!!"

every 20 ticks do
    if redstone eq 15 then
        input fluid:: from water
        output fluid:: to phyto
        forget
        input * except phytogro from drawer top side
        output retain 20 to each phyto slots 0
        forget
        input phytogro from drawer top side
        output * retain 4 phytogro to each phyto slots 1
        forget
        input * from phyto slots 2-5
        output * to drawer top side
    else
        input * from phyto
        output * to drawer top side
    end

end

every 2 ticks do 
    input fe:: from powah top side
    output fe:: to phyto top side
end

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\main\resources\assets\sfm\template_programs\round_robin.sfml

NAME "Round Robin"

EVERY 20 TICKS DO
    -- you probably don't need round robin
    -- instead, try retain!

    INPUT FROM source
    OUTPUT RETAIN 1000 fluid::water TO thingy
    -- don't put more than 1 bucket of water in each block labelled "thingy"


    FORGET

    INPUT RETAIN 5 stone FROM first
    OUTPUT TO second
    -- don't take the last 5 stone
END

-- alternatively, there is some round robin support
-- it will rotate the each time the statement ticks, so it isn't the fastest

EVERY 20 TICKS DO
    INPUT FROM source
    OUTPUT 128 dirt TO dest ROUND ROBIN BY BLOCK
    -- instead of outputting to all blocks labelled "dest"
    -- when this statement executes it will pick only one block
    -- the chosen block rotates each time this statement is executed

    FORGET

    INPUT FROM "storage a", "storage b" ROUND ROBIN BY LABEL
    OUTPUT 8 bone_meal TO EACH dispenser
    -- instead of outputting to all the labels
    -- one label will be chosen each time the statement executes
    -- basically alternating between
    -- INPUT FROM "storage a"
    -- and
    -- INPUT FROM "storage b"
END

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\main\resources\assets\sfm\template_programs\thank_you.sfml

NAME "Thank you!"

---- Ko-fi - https://ko-fi.com/teamdman

-- micrwvae
-- juh9870
-- Flynn
-- rick2049deckard
-- Satherov
-- TheVoos
-- Qther

---- GitHub Sponsors - https://github.com/sponsors/TeamDman

-- GodTierUser

---- Patreon - https://www.patreon.com/c/TeamDman

-- ...There's nothing here. Be the first!

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\main\resources\assets\sfm\template_programs\filtering.sfml

name "Filtering"

-- basic filtering with limits
EVERY 20 TICKS DO
    INPUT
        5 stone,
        3 iron_ingot,
        cobblestone, -- trailing comma is fine
    FROM a
    OUTPUT TO b
END

-- use an asterisk to fuzzy match
every 20 ticks do
    -- quoted patterns use full regex
    OUTPUT ".*ingot.*" to b

    -- without quotes, "*" gets converted to ".*"
    -- this is shorter, I prefer it
    INPUT *ingot* from a
end

-- you can exclude items too
EVERY 20 TICKS DO
    INPUT *ingot* EXCEPT iron_ingot, gold_ingot FROM a
    INPUT EXCEPT cobblestone FROM z
    OUTPUT 5 EXCEPT copper_ingot TO b
END

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\main\resources\assets\sfm\template_programs\changelog.sfml

NAME "Changelog"
-- Official SFM Discord:
-- https://discord.gg/5mbUY3mu6m
-- Official SFM Donation Page:
-- https://ko-fi.com/teamdman

---- 4.21.0 ----
-- Add intellisense, disabled by default - backslash to accept suggestion
-- Bottom left button in program edit screen now opens editor config
-- Fix missing focus highlight from tab-navigation of buttons in manager screen
-- Fix syntax highlighting for "SECOND" keyword
-- Fix escaped quotes in strings containing the escaping backslash
-- Add reminder when SFM crashes the game that there's a config to turn off managers
-- Add hotkey reminder to ctrl+i overlay
-- Fix disconnect issue when clearing facades from cables

---- 4.20.0 ----
-- Fix disks having no name when program has no name for 1.21+
-- VSCode extension updates, thanks @Titop54 for the help <3
-- Fix max program length ungraceful crash, thanks @Crazy
---- Set MAX_PROGRAM_LENGTH to ~32,300 because of vanilla constraints
-- Renamed resource types example to include "fluid" in the title
-- Add edit-in-hand reminder text to disk when no program present
-- Add reminder to push labels when warnings present
-- Optimize EACH limits
EVERY 20 TICKS DO
    INPUT FROM chest

    OUTPUT RETAIN 1 EACH *seed* TO phyto
    -- this no longer lags the more items match lol
END
-- Add support for OR
EVERY 20 TICKS DO
    INPUT
        5 stone OR dirt, -- move 5 total (stone|dirt)
        cobblestone -- move all cobble
    FROM chest
END
-- Fix correctness bug
---- With multiple 'b' inventories, each one would keep 5 instead of 5 across them total
EVERY 20 TICKS DO
    input retain 5 from b
    output to a
END
-- Update round robin example to advocate for using "retain" instead
-- Add notice to inspector that slot numbers may not correspond with automation slots
-- Fix syntax highlighting for "without", thanks @AkaiPandaTV
-- Fix WITHOUT and WITH logic implementation
EVERY 20 TICKS DO
    INPUT minecraft: FROM left
    OUTPUT WITH TAG ingots OR tag chests EXCEPT iron_ingot TO right
    OUTPUT WITHOUT TAG ingots OR TAG nuggets TO "top"
END
-- Add fancy inventory cable, cable with non-full-block model, thanks @kevinrpb
-- Optimized label gun and network tool rendering, thanks @Burkino <3
-- Add label gun render-only-selected gui button added, thanks @Burkino
-- Add label gun gui searching, thanks @Burkino
-- Add cable facades, thanks @Burkino
---- Network tool in off-hand will replace cable texture with block in main hand on right click
-- Add resource type disallowing via config for pack makers, thanks @Burkino
-- Add warning when using Mekanism blocks in IO statements with no side specified
-- Add label gun pick-block modifier key for right clicking
-- Fix label gun with no active label failing to shift-right-click to clear labels on blocks
-- Remove confirmation dialog when reset is pressed with blank program and no labels
-- Add confirmation screen when pasting
-- Optimized water tank logic, thanks @WanderinWolf for the bug report
---- Capacity scaling has been buffed from n to 2^n
---- Added `/sfm bust_water_network_cache` in case I screwed up the cache logic lol
-- Update network tool output; hide capabilities that aren't present, added newline
-- Update water tank reminder text to mention that other tanks must be active for bonus
-- Add issue tracker url to mods.toml, shown when SFM is suspected of causing a crash
-- Add CompressedBlockPosSet data structure to fix packet length issues with network tool and label gun
-- Invalidate network cache instead of recomputing when removing cables from large networks
-- Fix network tool not working on dedicated servers, thanks @RacerDelux
-- Fix only rendering one of label gun and network tool when both held at once
-- Fix network tool not refreshing render data properly when in off-hand
-- Fix cables being moved by pistons not updating the cable network
-- Remove unused keyword tokens: WORLD, PROGRAM, MOVE
-- Add random initialization of manager internal clock on load from world
-- Add support for specifying global alignment and offset for timer intervals
EVERY 20 GLOBAL TICKS DO END
EVERY 20 GLOBAL PLUS 5 TICKS DO END
EVERY 20G PLUS 5 TICKS DO END
EVERY 20G + 5 TICKS DO END
EVERY 20G+5 TICKS DO END
-- Add timer_triggers.sfml example
-- Fix manager asking for confirmation when pasting over empty disks
-- Fix missing truncation on ctrl+space actions for label and output tokens
-- Performance improvements by switching to imperative loops, thanks @Qther
-- Add `/sfm config show` (anyone) and `/sfm config edit` (op) commands
-- Add "show config" button to manager gui
-- Improve resource loss logging
-- Add config variable to disable config loss logging in console
-- Add "(SFM)" to mod name in mods.toml so it shows up in searches for "SFM"
-- Add crafting recipe to clear label guns
-- Fix label gun clear not resetting active label
-- Add label cleanup support for labels on blocks that are not detected as valid inventories
-- Fix inspection results missing line endings when multiple resource types present
-- Add exports for all side to network tool, listing selected side first
-- Add hotkeys for label gun next/prev for people with no scroll wheel, unbound by default
-- Add facades to fancy cables, HUGE thanks to CoFH for their RetexturedBakedQuad class <3
-- Add direction support to fancy cable facades
-- Add `/sfm changelog` command
-- Add swapping between fancy and normal cables with network tool
-- Performance improvements by reducing memory allocations iterating block neighbours
-- Fix inaccurate scrollbar
-- Fix industrial foregoing ore meat fluid not moving because SFM mishandling fluid tags
---- Thanks @luxxiz for the report!
-- Fix modifying disk label data in ServerboundManagerFixPacket failing due to immutability (#212)
-- Fix DiskItem calling client method on server when producing tooltip contents (#210)
-- Fix pasting large program causing packet too large error (#211)
-- Fix printing press form crash bug (#209)
-- Add label gun view mode to only show a subset of labels

---- 4.19.1 ----
-- Fix 1.19.4 build failures due to Gradle hating me

---- 4.19.0 ----
-- Fix uppercase resource identifiers not being auto-lowercased internally
-- Add WITH TAG support, check examples for more info
EVERY 20 TICKS DO
    INPUT minecraft:* WITH TAG minecraft:mineable/shovel FROM chest
    OUTPUT TO chest
END
-- Add WIP hotkey for copying item NBT to clipboard, doesn't work on JEI hovered items
-- Fix tooltips for manager screen showing over the program edit screen... again
-- Add `/sfm export_info` client command to dump item and recipe info
-- Update SFM 1.21.1 build to fix broken Mekanism compatibility
---- gas::, infusion::, etc are now aliases for chemical::

---- 4.18.0 ----
-- Add 1.21.0 build with Mekanism support
-- Set up a Ko-fi for donations
---- Thanks to @micrwvae for being first!
-- Restore Mekanism support for Minecraft 1.20.4
-- Add syntax highlighting for SECONDS keyword
-- Improve performance iterating over EXCLUDE resources

---- 4.17.0 ----
-- fix cache not being invalidated when chunk unloaded
---- this was caused by other mods not invalidating capabilities
---- this caused managers to stop working (rejoining a server, etc)
-- fix label guns sharing internal objects with each other and disks
---- this caused disks in managers to get updated without pushing
---- sometimes manager disk didn't have labels you thought you pushed
-- fix reset button not purging labels
-- add confirmation screen to reset button
-- remove confirmation screen when overwriting empty program
-- add client config for showing line numbers
---- add toggle button in program edit screen
-- reduce log spam from syntax errors
---- build process now properly stops when parsing fails

-- fix partial forgets resetting trackers
EVERY 20 TICKS DO
    INPUT 10 FROM a,b
    OUTPUT 1 to z
    FORGET b
    OUTPUT to z -- this used to move 10, should be/is now 9
END

-- add warnings
EVERY 20 TICKS DO
    INPUT fluid:: FROM a -- fluid:: never gets used
    OUTPUT TO b          -- item:: never get inputted
END


---- 4.16.0 ----
-- fix crashes using ctrl+space
---- happened when using on `Gas::` or `fe::` or `bruh::`
---- Thanks @crystalivel for the report <3
-- add manager screen button to rebuild network
---- this will do until I can squash the bug
---- I'm sorry for the headaches this has caused
-- add known issues example program
-- fix INPUT statement limits not being respected
---- occurred when using 2+ outputs with one input
---- https://imgur.com/a/sfm-unintended-behaviour-gBkNyae
---- Thanks @Lewi for the report <3
-- fix recipes to use item tags for signs and rods
---- Thanks @pddshk for the suggestion (#127) <3
---- Thanks Blase for the reminder <3
-- add mekanism_energy:mekanism:energy resource type
---- This can move MAX_LONG per operation where supported
-- make "EVERY TICK DO" valid syntax
---- this still only allows forge_energy by default
-- optimize more common regex (*seed, seed*, *seed*)
-- add line numbers to program edit screen
---- Thanks @Lewi and @Reko for the suggestion <3
-- remove admin requirement for /sfm bust_cable_network_cache
---- logs whoever uses the command
-- add logging for individual managers
-- program edit screen no longer pauses the game in single player
-- more confirmation dialogs when overwriting programs
---- Thanks @DeeJayh for the report, sorry for ur loss <3
-- make FORGET keyword blue
-- make NOT,AND,OR keywords gold
-- allow `redstone` as a label
-- fix missing dash in changelog
-- support `IF chest HAS EQ 0 THEN`
---- previously only `IF chest HAS EQ 0 item:: THEN`
-- add ctrl+space action for IfStatement and BoolExpr
-- add git tag action to release process documentation
-- add GitHub release to release process documentation
-- update AE2 example to accommodate case where ingredients are spread between inscribers
---- Thanks @Kem for the idea <3

---- 4.15.1 ----
-- Fix crash bug where variable-size inventories (composters) crashed
-- Fix modloader=forge missing for the mc=1.20.1 CurseForge uploads

---- 4.15.0 ----
-- alias "each" to "every" when parsing set operators
---- (e.g., "IF EACH chest HAS GT 1 iron_ingot THEN")
---- this is an old change but I forgot to mention till now
-- move disk warning update trigger from tick to manager open
-- add warnings for smelly round robin usage
-- add warning for smelly pattern expansion usage
-- add mod+game info to clipboard diagnostics
-- add EACH SIDE alias
---- OUTPUT forge_energy:: TO "flux cell" EACH SIDE
-- improve error logging when resource voiding detected
-- fix bug where programs worked without inventory cables
-- ctrl+labelgun now applies to kitty corner
-- add config for minimum tick time
-- allow programs to tick faster if only moving energy
-- alias forge_energy:: <= fe::, rf::, energy::, power::
-- fix anvils causing problems when quitting world

---- 4.14.3 ----
-- add support for 1.20.2
-- add support for 1.20.3
-- add support for 1.20.4

---- 4.14.2 ----
-- fix capability caching to avoid resource voiding and programs not working

---- 4.14.1 ----
-- fix label gun not working on servers

---- 4.14.0 ----
-- switch to milliseconds instead of nanoseconds
-- add FORGET keyword
---- FORGET a,b,c -- remove these from inputs
---- FORGET -- remove all inputs
-- add ROUND ROBIN keyword, works in inputs and outputs
---- INPUT FROM a,b ROUND ROBIN BY BLOCK
---- OUTPUT TO a,b,c ROUND ROBIN BY LABEL
-- fix REDSTONE keyword not being allowed as a resource id
---- INPUT redstone FROM a -- works now :D
-- add /sfm op command for diagnosing cable network problems
-- performance improvements :D
---- breaking many cables at once now faster
---- fastutils in cable network makes programs faster too
---- shifted pattern compilation to build time instead of first inference
-- improved GitHub README
-- test suite improvements
-- fix inv overlay (Ctrl+i) failing with big payloads
-- double inspection payload max size before truncation to 20480
-- add ae2 inscriber example program
-- add network tool item

---- 4.13.0 ----
-- add ctrl+slash hotkey to toggle comments
-- add warning when exiting program without saving
-- add 1.20.1 mekanism support
-- add emotes to discord server
-- add role to discord server to be pinged about updates

---- 4.12.0 ----
-- add changelog to templates
-- add ctrl+space hotkey for code inspection
-- add `EACH` modifier on quantities and retentions
-- add resource exclusion patterns
-- add missing syntax highlighting
-- cleaner templates
-- improved export inspector output (Ctrl+I)

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\main\resources\assets\sfm\template_programs\limits.sfml

name "Limits"

every 20 ticks do

    -- This is hard to explain since there's so many variations
    -- This is more a collection of samples than a coherent program
    -- Guess what you think each statement does, try them out on your own!

    INPUT FROM a
    INPUT 1 FROM a
    INPUT stone FROM a
    INPUT 3 stone FROM a
    INPUT 5 RETAIN 3 stone FROM a
    INPUT 5 *ingot FROM a
    INPUT 5 EACH *ingot FROM a
    INPUT 5 RETAIN 1 EACH *ingot FROM a

    -- quantity and retention can both be expanded with the EACH keyword
    -- this only makes sense if your resource id is a pattern
    INPUT 5 EACH RETAIN 3 EACH *ingot* FROM a

    OUTPUT TO b

    INPUT 1 "redstone" FROM EACH a
    OUTPUT 1 TO EACH b

    OUTPUT 5 TO b
    OUTPUT 5 stone TO b
    OUTPUT RETAIN 1 coal TO furnace BOTTOM SIDE
    OUTPUT 8 EACH *ingot* to b
    OUTPUT RETAIN 8 EACH *ingot* to b
    OUTPUT 8 EACH retain 8 EACH *ingot* to b

end

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\main\resources\assets\sfm\template_programs\forget.sfml

NAME "Forget"

EVERY 20 TICKS DO
    INPUT FROM a
    OUTPUT 1 to b

    INPUT FROM c
    OUTPUT TO d -- the "a" input is still active!!!
END

EVERY 20 TICKS DO
    INPUT FROM a
    OUTPUT 1 to b

    FORGET a
    -- or just
    FORGET

    INPUT FROM c
    OUTPUT TO d -- only the "c" input is active :D
END

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\examples\random\forgeenergy.sfm

name "energy"
EVERY 20 TICKS DO
    INPUT 1000 sfm:forge_energy:forge:energy from a BOTTOM SIDE
    OUTPUT sfm:forge_energy:forge:energy to b TOP SIDE
END

---

every 20 ticks do
    input from a top side
    output to b top side
end

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\examples\random\alltheores.sfm

name "ATO"

every 20 ticks do
    input from coal top side
    input from alum top side
    output coal to furn bottom side
    output 2 alltheores:raw_aluminum to each furn top side
end

every 20 ticks do
    -- when not using quotes, "*" gets replaced by ".*" for convenience
    -- then the identifiers are treated as regular expressions
    input *:*ingot* from furn
    output to out
end

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\examples\random\patterns.sfm

name "patterns"

every 20 ticks do
    -- since we are using quotes, we must use ".*" instead of just "*" if we want to match all mods
    input ".*:(iron|copper)_ingot" from a
    output to b
end

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\examples\random\mekanism.sfm

every 20 ticks do
    input gas:mekanism:* from a bottom side
    output gas:mekanism:* to b bottom side
end
---
every 20 ticks do
    input infusion:mekanism:* from a bottom side
    output infusion:mekanism:* to b bottom side
end
---
every 20 ticks do
    input pigment:mekanism:* from a bottom side
    output pigment:mekanism:* to b bottom side
end
---
every 20 ticks do
    input slurry:alltheores:* from a bottom side
    output slurry:*:* to b bottom side
end

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\examples\03-conditions.sfm

name "condition"

every 20 ticks do
    INPUT FROM a
    -- since more than one inventory can have the same label, it might be useful
    -- to be able to be specific for conditionals.

    -- if every a has gt 10 iron_ingot then -- all inventories must match
    -- if some a has gt 10 iron_ingot then -- at least one inventory must match
    -- if one a has gt 10 iron_ingot then -- exactly one inventory must match
    -- if lone a has gt 10 iron_ingot then -- exactly zero or one inventory must match
    -- if overall a has gt 10 iron_ingot then -- counting items from all inventories instead of individually, must match
    if a has gt 10 minecraft:iron_ingot then
        -- defaults to "overall" if set qualifier not specified
        -- "minecraft:" namespace prefix is default and can be omitted
        OUTPUT TO b
    end

    if not d has gt 20 stone and a has ge 20 stone then
        output 20 to d
    end

    -- this will resolve to "true" and will always run
    if (false and true) or (false or true) or not true then
        -- "not", "and", and "or" operators all have same precidence
        -- use parentheses to ensure things work as you expect
        output to c
    end
end

-- if this program were running while "a" had 9*iron_ingot:
-- "b" would receive 0*iron_ingot
-- "d" would receive 9*iron_ingot

-- if this program were running while "a" had 30*iron_ingot:
-- "b" would receive 30*iron_ingot
-- "d" would receive 0*iron_ingot

-- if this program were running while "a" had 64*stone:
-- "d" would receive 20*stone
-- "c" would receive 44*stone

-- if this program were running while "a" had 16*stone:
-- "d" would receive 0*stone
-- "c" would receive 16*stone

-- if this program were running while "a" had (2*64)*stone:
-- "d" would receive 20*stone
-- "c" would receive (64+44)*stone


--------------------

every 20 ticks do
    if a has ge 32 iron_ingot then
        input 1 cobblestone from a
    else if a has ge 16 iron_ingot then
        input 1 stone from a
    else
        input 1 red_sandstone from a
    end

    output to b
end

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\examples\02-retention.sfm

name "retention"

every 20 ticks do
    -- move all items except 1 from "a" to "b"
    input retain 1 from a
    output to b
end


every 20 ticks do
    -- move 25 items except 1 from "a" to "b"
    input 25 retain 1 from a
    output to b
end


every 20 ticks do
    -- move 25 red_sand items except 1 from "a" to "b"
    input 25 retain 1 red_sand from a
    output to b
end


every 20 ticks do
    -- move 20 red sand and all coal
    -- but leave 1 red sand
    input
        20 retain 1 red_sand,
        retain 1 coal
    from a

    output to b
end


every 20 ticks do
    input from a
    output retain 1 to b
    -- only output until "b" has 1 item
end

every 20 ticks do
    input from a
    output 5 retain 20 red_sand to b
    -- accept 5 red sand at once, with "a" max of 20 in "b"
end


every 20 ticks do
    input
        15 retain 5 iron_ingot,
        12 retain 3 stone
    from "a" top side

    output
        2 iron_ingot,
        64 retain 10 stone
    to b

    -- moves all but 5 iron ingots to "b", 15 at a time
    -- moves up to 10 stone to "b", ensuring "a" keeps 3 stone
    -- "b" can accept up to 64 stone but "a" can only provide 12 at a time
end

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\examples\01-moveitems.sfm

name "move items" -- name declaration is optional

-- use two dashes for a comment
-- keywords are case insensitive

-- more than one trigger can be used in a program
every 20 ticks do
    -- each trigger block can contain multiple statements
    INPUT FROM a
    OUTPUT TO b
    -- "a" and "b" are inventory labels
    -- use a label gun to identify them in world
end



-- The previous trigger will move all items, but for instructional
-- purposes, more triggers will be included here
every 20 ticks do
    INPUT 1 from a -- only extract 1 item at a time
    OUTPUT to b
end



every 20 ticks do
    input from a
    output 1 to b   -- only insert 1 item at a time
                    -- if multiple inventories are labelled "b",
                    -- only one will receive an item
end


every 20 ticks do
    input from a
    output 1 to each b -- every "b" inventory will receive 1 item
end



every 20 ticks do
    input 1 from each a -- each "a" inventory will only have 1 item removed,
                        -- instead of 1 item total being moved
    output to b
end

every 20 ticks do
    input iron_ingot from a -- only move iron ingots, "minecraft:" namespace is assumed
    output to b
end

every 20 ticks do
    input thaumcraft:iron_nugget from a -- only move thaumcraft iron nuggets
    output to b
end

every 20 ticks do
    input "redstone" from a -- redstone is a keyword for future use, so must be surrounded in quotes
    output to b
end

every 20 ticks do
    input "minecraft:redstone" from a -- redstone is a keyword, so must be surrounded in quotes
    output to b
end

every 20 ticks do
    if a has gt 0 "redstone" then -- again, redstone must be in quotes
        input from a
        output to b
    end
end


D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\examples\07-redstone.sfm

every redstone pulse do
    input from a
    output to b
end

every 20 ticks do
    if redstone ge 2 then
        input from a
        output to b
    end
end

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\examples\04-smelting.sfm

name "smelting"

every 20 ticks do
    input from chest
    
    output
        minecraft:oak_log -- items can be specified
    to furnace top side

    output
        coal,               -- namespace defaults to "minecraft:"
        1 retain 1 oak_log, -- limits are per-item
        charcoal
    to furnace bottom side 
end

every 20 ticks do
    input from furnace bottom side
    output to chest
end

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\examples\WIP\alias.sfm

-- not yet implemented.

name "alias"

every 20 ticks do
    INPUT FROM a BOTTOM SIDE SLOTS 0 AS ax
    IF ax has gt 10 iron THEN
        
    END
end

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\examples\08-fluids.sfm

EVERY 20 TICKS DO
    INPUT fluid:minecraft:water from a BOTTOM SIDE
    OUTPUT fluid:*:* to b TOP SIDE
END

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\examples\06-slots.sfm

name "slots"

every 20 ticks do
    -- multiple slots can be specified, comma separated, also supporting ranges
    input from a top side slots 0,1,3-4, 5
    output to a slots 2
end

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\examples\05-each.sfm

name "each"

-- using every inventory labelled "c", move 5 items total
-- "b" will receive 5 items, or whatever is available if less than 5.
every 20 ticks do
    input 5 from c
    output to b
end

-- for every inventory labelled "a", 5 items will be moved
-- "b" will receive |a| * 5 items
every 20 ticks do
    input 5 from each a -- note the "each" keyword
    output to b
end

===

Summary

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\compat\ae2\SFMAppliedEnergisticsCompatGameTests.java

package ca.teamdman.sfm.gametest.compat.ae2;

import appeng.blockentity.misc.InscriberBlockEntity;
import appeng.core.definitions.AEBlocks;
import appeng.core.definitions.AEItems;
import ca.teamdman.sfm.SFM;
import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity;
import ca.teamdman.sfm.common.program.LabelPositionHolder;
import ca.teamdman.sfm.common.registry.SFMBlocks;
import ca.teamdman.sfm.common.registry.SFMItems;
import ca.teamdman.sfm.gametest.SFMGameTestBase;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.StandingSignBlock;
import net.minecraft.world.level.block.entity.SignBlockEntity;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.gametest.GameTestHolder;

import java.util.stream.Stream;

@SuppressWarnings({"OptionalGetWithoutIsPresent", "DataFlowIssue"})
@GameTestHolder(SFM.MOD_ID)
public class SFMAppliedEnergisticsCompatGameTests extends SFMGameTestBase {

    @GameTest(template = "7x3x3", timeoutTicks = 20 * 20)
    public static void ae2_inscribers(GameTestHelper helper) {
        var managerPos = new BlockPos(0, 2, 1);

        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        for (int i = 0; i < 6; i++) {
            helper.setBlock(new BlockPos(i + 1, 2, 1), SFMBlocks.CABLE_BLOCK.get());
        }

        var siliconPos1 = new BlockPos(4, 3, 1);
        var siliconPos2 = new BlockPos(5, 3, 1);
        var siliconPos3 = new BlockPos(6, 3, 1);
        var logicPos = new BlockPos(1, 2, 0);
        var engineeringPos = new BlockPos(2, 2, 0);
        var calculationPos = new BlockPos(3, 2, 0);
        var lastPos1 = new BlockPos(1, 3, 1);
        var lastPos2 = new BlockPos(2, 3, 1);
        var lastPos3 = new BlockPos(3, 3, 1);
        helper.setBlock(siliconPos1, AEBlocks.INSCRIBER.block());
        helper.setBlock(siliconPos2, AEBlocks.INSCRIBER.block());
        helper.setBlock(siliconPos3, AEBlocks.INSCRIBER.block());
        helper.setBlock(logicPos, AEBlocks.INSCRIBER.block());
        helper.setBlock(engineeringPos, AEBlocks.INSCRIBER.block());
        helper.setBlock(calculationPos, AEBlocks.INSCRIBER.block());
        helper.setBlock(lastPos1, AEBlocks.INSCRIBER.block());
        helper.setBlock(lastPos2, AEBlocks.INSCRIBER.block());
        helper.setBlock(lastPos3, AEBlocks.INSCRIBER.block());
        var silicon1 = ((InscriberBlockEntity) helper.getBlockEntity(siliconPos1));
        var silicon2 = ((InscriberBlockEntity) helper.getBlockEntity(siliconPos2));
        var silicon3 = ((InscriberBlockEntity) helper.getBlockEntity(siliconPos3));
        var logic = ((InscriberBlockEntity) helper.getBlockEntity(logicPos));
        var engineering = ((InscriberBlockEntity) helper.getBlockEntity(engineeringPos));
        var calculation = ((InscriberBlockEntity) helper.getBlockEntity(calculationPos));
        var last1 = ((InscriberBlockEntity) helper.getBlockEntity(lastPos1));
        var last2 = ((InscriberBlockEntity) helper.getBlockEntity(lastPos2));
        var last3 = ((InscriberBlockEntity) helper.getBlockEntity(lastPos3));
        silicon1
                .getCapability(ForgeCapabilities.ITEM_HANDLER)
                .resolve()
                .get()
                .insertItem(0, new ItemStack(AEItems.SILICON_PRESS), false);
        silicon2
                .getCapability(ForgeCapabilities.ITEM_HANDLER)
                .resolve()
                .get()
                .insertItem(0, new ItemStack(AEItems.SILICON_PRESS), false);
        silicon3
                .getCapability(ForgeCapabilities.ITEM_HANDLER)
                .resolve()
                .get()
                .insertItem(0, new ItemStack(AEItems.SILICON_PRESS), false);
        engineering
                .getCapability(ForgeCapabilities.ITEM_HANDLER)
                .resolve()
                .get()
                .insertItem(0, new ItemStack(AEItems.ENGINEERING_PROCESSOR_PRESS), false);
        calculation
                .getCapability(ForgeCapabilities.ITEM_HANDLER)
                .resolve()
                .get()
                .insertItem(0, new ItemStack(AEItems.CALCULATION_PROCESSOR_PRESS), false);
        logic
                .getCapability(ForgeCapabilities.ITEM_HANDLER)
                .resolve()
                .get()
                .insertItem(0, new ItemStack(AEItems.LOGIC_PROCESSOR_PRESS), false);

        Stream
                .of(silicon1, silicon2, silicon3, logic, engineering, calculation, last1, last2, last3)
                .map(InscriberBlockEntity::getUpgrades)
                .forEach(upgradeInventory -> {
                    for (int slot = 0; slot < upgradeInventory.size(); slot++) {
                        upgradeInventory.insertItem(slot, new ItemStack(AEItems.SPEED_CARD), false);
                    }
                });

        var powerPos1 = new BlockPos(0, 3, 1);
        helper.setBlock(powerPos1, AEBlocks.CREATIVE_ENERGY_CELL.block());
        var powerPos2 = new BlockPos(4, 2, 0);
        helper.setBlock(powerPos2, AEBlocks.CREATIVE_ENERGY_CELL.block());

        var materialsPos = new BlockPos(6, 2, 0);
        var resultsPos = new BlockPos(5, 2, 0);
        helper.setBlock(materialsPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(resultsPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        //noinspection DataFlowIssue,OptionalGetWithoutIsPresent
        var materials = helper
                .getBlockEntity(materialsPos)
                .getCapability(ForgeCapabilities.ITEM_HANDLER)
                .resolve()
                .get();
        //noinspection DataFlowIssue,OptionalGetWithoutIsPresent
        var results = helper.getBlockEntity(resultsPos).getCapability(ForgeCapabilities.ITEM_HANDLER).resolve().get();
        materials.insertItem(0, new ItemStack(Items.REDSTONE, 64), false);
        materials.insertItem(1, new ItemStack(Items.REDSTONE, 64), false);
        materials.insertItem(2, new ItemStack(Items.REDSTONE, 64), false);
        materials.insertItem(3, new ItemStack(Items.DIAMOND, 64), false);
        materials.insertItem(4, new ItemStack(Items.GOLD_INGOT, 64), false);
        materials.insertItem(5, new ItemStack(AEItems.CERTUS_QUARTZ_CRYSTAL, 64), false);
        materials.insertItem(6, new ItemStack(AEItems.SILICON, 64), false);
        materials.insertItem(7, new ItemStack(AEItems.SILICON, 64), false);
        materials.insertItem(8, new ItemStack(AEItems.SILICON, 64), false);

        // put signs on them lol
        helper.setBlock(
                materialsPos.offset(0, 1, 0),
                Blocks.OAK_SIGN.defaultBlockState().setValue(StandingSignBlock.ROTATION, 8)
        );
        ((SignBlockEntity) helper.getBlockEntity(materialsPos.offset(0, 1, 0))).setMessage(
                0,
                Component.literal("input")
        );
        helper.setBlock(
                resultsPos.offset(0, 1, 0),
                Blocks.OAK_SIGN.defaultBlockState().setValue(StandingSignBlock.ROTATION, 8)
        );
        ((SignBlockEntity) helper.getBlockEntity(resultsPos.offset(0, 1, 0))).setMessage(
                0,
                Component.literal("output")
        );

        var manager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        LabelPositionHolder.empty()
                .add("silicon", helper.absolutePos(siliconPos1))
                .add("silicon", helper.absolutePos(siliconPos2))
                .add("silicon", helper.absolutePos(siliconPos3))
                .add("logic", helper.absolutePos(logicPos))
                .add("engineering", helper.absolutePos(engineeringPos))
                .add("calculation", helper.absolutePos(calculationPos))
                .add("last", helper.absolutePos(lastPos1))
                .add("last", helper.absolutePos(lastPos2))
                .add("last", helper.absolutePos(lastPos3))
                .add("materials", helper.absolutePos(materialsPos))
                .add("results", helper.absolutePos(resultsPos))
                .save(manager.getDisk());

        manager.setProgram("""
                                   EVERY 20 TICKS DO
                                       INPUT FROM materials
                                       OUTPUT gold_ingot TO logic SLOTS 2
                                       OUTPUT diamond TO engineering SLOTS 2
                                       OUTPUT certus_quartz_crystal TO calculation SLOTS 2
                                       OUTPUT silicon TO silicon SLOTS 2
                                       OUTPUT redstone TO last SLOTS 2
                                       OUTPUT printed_silicon TO last SLOTS 1
                                       OUTPUT printed_calculation_processor, printed_engineering_processor, printed_logic_processor TO last SLOTS 0

                                       FORGET
                                       INPUT FROM logic, engineering, calculation, silicon west side
                                       output to materials

                                       FORGET
                                       INPUT FROM last west SIDE
                                       OUTPUT TO results
                                   END
                                   """.stripTrailing().stripIndent());
        helper.succeedWhen(() -> {
            boolean hasCalculation = count(results, AEItems.CALCULATION_PROCESSOR.asItem().asItem()) > 0;
            boolean hasEngineering = count(results, AEItems.ENGINEERING_PROCESSOR.asItem().asItem()) > 0;
            boolean hasLogic = count(results, AEItems.LOGIC_PROCESSOR.asItem().asItem()) > 0;
            if (hasCalculation && hasEngineering && hasLogic) {
                helper.succeed();
            } else {
                helper.fail("Missing processors: " + (hasCalculation ? "" : "calculation ") + (
                        hasEngineering
                        ? ""
                        : "engineering "
                ) + (hasLogic ? "" : "logic"));
            }
        });
    }
}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\package-info.java

@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@FieldsAreNonnullByDefault
package ca.teamdman.sfm.gametest;

import net.minecraft.FieldsAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;

import javax.annotation.ParametersAreNonnullByDefault;

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\declarative\SFMTestSpec.java

package ca.teamdman.sfm.gametest.declarative;

import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;

public class SFMTestSpec {
    private final List<TestBlockDef<?>> blocks = new ArrayList<>();
    private @Nullable String program = null;
    private final List<String> preConditions = new ArrayList<>();
    private final List<String> postConditions = new ArrayList<>();

    public SFMTestSpec setProgram(String program) {
        this.program = program.stripTrailing().stripIndent();
        return this;
    }

    public SFMTestSpec addBlock(TestBlockDef<?> def) {
        this.blocks.add(def);
        return this;
    }

    public SFMTestSpec preCondition(String conditionDsl) {
        this.preConditions.add(conditionDsl);
        return this;
    }

    public SFMTestSpec postCondition(String conditionDsl) {
        this.postConditions.add(conditionDsl);
        return this;
    }

    public String program() {
        return program;
    }

    public List<TestBlockDef<?>> blocks() {
        return blocks;
    }

    public List<String> preConditions() {
        return preConditions;
    }

    public List<String> postConditions() {
        return postConditions;
    }
}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\SFMIfStatementGameTests.java

package ca.teamdman.sfm.gametest;

import ca.teamdman.sfm.SFM;
import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity;
import ca.teamdman.sfm.common.item.DiskItem;
import ca.teamdman.sfm.common.localization.LocalizationKeys;
import ca.teamdman.sfm.common.net.ServerboundOutputInspectionRequestPacket;
import ca.teamdman.sfm.common.program.LabelPositionHolder;
import ca.teamdman.sfm.common.program.ProgramContext;
import ca.teamdman.sfm.common.program.linting.GatherWarningsProgramBehaviour;
import ca.teamdman.sfm.common.registry.SFMBlocks;
import ca.teamdman.sfm.common.registry.SFMItems;
import ca.teamdman.sfml.ast.OutputStatement;
import ca.teamdman.sfml.ast.Program;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.Container;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Blocks;
import net.minecraftforge.gametest.GameTestHolder;

import java.util.ArrayList;
import java.util.List;

@SuppressWarnings({
        "RedundantSuppression",
        "DataFlowIssue",
        "deprecation",
        "OptionalGetWithoutIsPresent",
        "DuplicatedCode",
})
@GameTestHolder(SFM.MOD_ID)
public class SFMIfStatementGameTests extends SFMGameTestBase {

    @GameTest(template = "3x2x1")
    public static void comparison_gt(GameTestHelper helper) {
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var left = (Container) helper.getBlockEntity(leftPos);
        var right = (Container) helper.getBlockEntity(rightPos);
        var manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        left.setItem(0, new ItemStack(Items.DIAMOND, 64));
        left.setItem(1, new ItemStack(Items.DIAMOND, 64));
        left.setItem(2, new ItemStack(Items.IRON_INGOT, 12));
        right.setItem(0, new ItemStack(Items.STICK, 13));
        right.setItem(1, new ItemStack(Items.STICK, 64));
        right.setItem(2, new ItemStack(Items.DIRT, 1));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   NAME "comparison_gt test"
                                   EVERY 20 TICKS DO
                                       IF left HAS GT 100 diamond THEN
                                           -- should happen
                                           INPUT diamond FROM left
                                           OUTPUT diamond TO right
                                       END
                                       IF left HAS GT 300 iron_ingot THEN
                                           -- should not happen
                                           INPUT iron_ingot FROM left
                                           OUTPUT iron_ingot TO right
                                       END
                                       IF right HAS > 10 stick THEN
                                           -- should happen
                                           INPUT stick FROM right
                                           OUTPUT stick TO left
                                       END
                                       if right has > 0 dirt then
                                           -- should happen
                                           input dirt from right
                                           output dirt to left
                                       end
                                   END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("left", helper.absolutePos(leftPos))
                .add("right", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            int leftDiamondCount = count(left, Items.DIAMOND);
            int leftIronCount = count(left, Items.IRON_INGOT);
            int leftStickCount = count(left, Items.STICK);
            int leftDirtCount = count(left, Items.DIRT);
            int rightDiamondCount = count(right, Items.DIAMOND);
            int rightIronCount = count(right, Items.IRON_INGOT);
            int rightStickCount = count(right, Items.STICK);
            int rightDirtCount = count(right, Items.DIRT);
            // the diamonds should have moved from left to right
            assertTrue(leftDiamondCount == 0, "left should have no diamonds");
            assertTrue(rightDiamondCount == 64 * 2, "right should have 100 diamonds");
            // the iron should have stayed in left
            assertTrue(leftIronCount == 12, "left should have 12 iron ingots");
            assertTrue(rightIronCount == 0, "right should have no iron ingots");
            // the sticks should have moved from right to left
            assertTrue(rightStickCount == 0, "right should have no sticks");
            assertTrue(leftStickCount == 77, "left should have 77 sticks");
            // the dirt should have moved from right to left
            assertTrue(rightDirtCount == 0, "right should have no dirt");
            assertTrue(leftDirtCount == 1, "left should have 1 dirt");
        });
    }

    @GameTest(template = "3x2x1")
    public static void comparison_ge(GameTestHelper helper) {
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var left = (Container) helper.getBlockEntity(leftPos);
        var right = (Container) helper.getBlockEntity(rightPos);
        var manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        left.setItem(0, new ItemStack(Items.DIAMOND, 64));
        left.setItem(1, new ItemStack(Items.DIAMOND, 64));
        left.setItem(2, new ItemStack(Items.IRON_INGOT, 12));
        right.setItem(0, new ItemStack(Items.STICK, 13));
        right.setItem(1, new ItemStack(Items.STICK, 64));
        right.setItem(2, new ItemStack(Items.DIRT, 1));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   NAME "comparison_ge test"
                                   EVERY 20 TICKS DO
                                       IF left HAS GE 129 diamond THEN
                                           -- should not happen
                                           INPUT diamond FROM left
                                           OUTPUT diamond TO right
                                       END
                                       IF left HAS GE 12 iron_ingot THEN
                                           -- should happen
                                           INPUT iron_ingot FROM left
                                           OUTPUT iron_ingot TO right
                                       END
                                       IF right HAS >= 13 stick THEN
                                           -- should happen
                                           INPUT stick FROM right
                                           OUTPUT stick TO left
                                       END
                                       if right has >= 1 dirt then
                                           -- should happen
                                           input dirt from right
                                           output dirt to left
                                       end
                                   END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("left", helper.absolutePos(leftPos))
                .add("right", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            int leftDiamondCount = count(left, Items.DIAMOND);
            int leftIronCount = count(left, Items.IRON_INGOT);
            int leftStickCount = count(left, Items.STICK);
            int leftDirtCount = count(left, Items.DIRT);
            int rightDiamondCount = count(right, Items.DIAMOND);
            int rightIronCount = count(right, Items.IRON_INGOT);
            int rightStickCount = count(right, Items.STICK);
            int rightDirtCount = count(right, Items.DIRT);
            // the diamonds should have moved from left to right
            assertTrue(leftDiamondCount == 64 * 2, "left should have 128 diamonds");
            assertTrue(rightDiamondCount == 0, "right should have no diamonds");
            // the iron should have moved from left to right
            assertTrue(leftIronCount == 0, "left should have no iron ingots");
            assertTrue(rightIronCount == 12, "right should have 12 iron ingots");
            // the sticks should have moved from right to left
            assertTrue(rightStickCount == 0, "right should have no sticks");
            assertTrue(leftStickCount == 77, "left should have 77 sticks");
            // the dirt should have moved from right to left
            assertTrue(rightDirtCount == 0, "right should have no dirt");
            assertTrue(leftDirtCount == 1, "left should have 1 dirt");
        });
    }


    @GameTest(template = "3x2x1")
    public static void comparison_eq(GameTestHelper helper) {
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var left = (Container) helper.getBlockEntity(leftPos);
        var right = (Container) helper.getBlockEntity(rightPos);
        var manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        left.setItem(0, new ItemStack(Items.DIAMOND, 64));
        left.setItem(1, new ItemStack(Items.DIAMOND, 64));
        left.setItem(2, new ItemStack(Items.IRON_INGOT, 12));
        right.setItem(0, new ItemStack(Items.STICK, 13));
        right.setItem(1, new ItemStack(Items.STICK, 64));
        right.setItem(2, new ItemStack(Items.DIRT, 1));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   NAME "comparison_eq test"
                                   EVERY 20 TICKS DO
                                       IF left HAS eq 129 diamond THEN
                                           -- should not happen
                                           INPUT diamond FROM left
                                           OUTPUT diamond TO right
                                       END
                                       IF left HAS = 12 iron_ingot THEN
                                           -- should happen
                                           INPUT iron_ingot FROM left
                                           OUTPUT iron_ingot TO right
                                       END
                                       IF right HAS eq 77 stick THEN
                                           -- should happen
                                           INPUT stick FROM right
                                           OUTPUT stick TO left
                                       END
                                       if right has = 1 dirt then
                                           -- should happen
                                           input dirt from right
                                           output dirt to left
                                       end
                                   END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("left", helper.absolutePos(leftPos))
                .add("right", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            int leftDiamondCount = count(left, Items.DIAMOND);
            int leftIronCount = count(left, Items.IRON_INGOT);
            int leftStickCount = count(left, Items.STICK);
            int leftDirtCount = count(left, Items.DIRT);
            int rightDiamondCount = count(right, Items.DIAMOND);
            int rightIronCount = count(right, Items.IRON_INGOT);
            int rightStickCount = count(right, Items.STICK);
            int rightDirtCount = count(right, Items.DIRT);
            // the diamonds should have moved from left to right
            assertTrue(leftDiamondCount == 64 * 2, "left should have 128 diamonds");
            assertTrue(rightDiamondCount == 0, "right should have no diamonds");
            // the iron should have moved from left to right
            assertTrue(leftIronCount == 0, "left should have no iron ingots");
            assertTrue(rightIronCount == 12, "right should have 12 iron ingots");
            // the sticks should have moved from right to left
            assertTrue(rightStickCount == 0, "right should have no sticks");
            assertTrue(leftStickCount == 77, "left should have 77 sticks");
            // the dirt should have moved from right to left
            assertTrue(rightDirtCount == 0, "right should have no dirt");
            assertTrue(leftDirtCount == 1, "left should have 1 dirt");
        });
    }


    @GameTest(template = "3x2x1")
    public static void comparison_lt(GameTestHelper helper) {
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var left = (Container) helper.getBlockEntity(leftPos);
        var right = (Container) helper.getBlockEntity(rightPos);
        var manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        left.setItem(0, new ItemStack(Items.DIAMOND, 64));
        left.setItem(1, new ItemStack(Items.DIAMOND, 64));
        left.setItem(2, new ItemStack(Items.IRON_INGOT, 12));
        right.setItem(0, new ItemStack(Items.STICK, 13));
        right.setItem(1, new ItemStack(Items.STICK, 64));
        right.setItem(2, new ItemStack(Items.DIRT, 1));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   NAME "comparison_lt test"
                                   EVERY 20 TICKS DO
                                       IF left HAS lt 10 diamond THEN
                                           -- should not happen
                                           INPUT diamond FROM left
                                           OUTPUT diamond TO right
                                       END
                                       IF left HAS < 200 iron_ingot THEN
                                           -- should happen
                                           INPUT iron_ingot FROM left
                                           OUTPUT iron_ingot TO right
                                       END
                                       IF right HAS < 78 stick THEN
                                           -- should happen
                                           INPUT stick FROM right
                                           OUTPUT stick TO left
                                       END
                                       if right has < 3 dirt then
                                           -- should happen
                                           input dirt from right
                                           output dirt to left
                                       end
                                   END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("left", helper.absolutePos(leftPos))
                .add("right", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            int leftDiamondCount = count(left, Items.DIAMOND);
            int leftIronCount = count(left, Items.IRON_INGOT);
            int leftStickCount = count(left, Items.STICK);
            int leftDirtCount = count(left, Items.DIRT);
            int rightDiamondCount = count(right, Items.DIAMOND);
            int rightIronCount = count(right, Items.IRON_INGOT);
            int rightStickCount = count(right, Items.STICK);
            int rightDirtCount = count(right, Items.DIRT);
            // the diamonds should have moved from left to right
            assertTrue(leftDiamondCount == 64 * 2, "left should have 128 diamonds");
            assertTrue(rightDiamondCount == 0, "right should have no diamonds");
            // the iron should have moved from left to right
            assertTrue(leftIronCount == 0, "left should have no iron ingots");
            assertTrue(rightIronCount == 12, "right should have 12 iron ingots");
            // the sticks should have moved from right to left
            assertTrue(rightStickCount == 0, "right should have no sticks");
            assertTrue(leftStickCount == 77, "left should have 77 sticks");
            // the dirt should have moved from right to left
            assertTrue(rightDirtCount == 0, "right should have no dirt");
            assertTrue(leftDirtCount == 1, "left should have 1 dirt");
        });
    }


    @GameTest(template = "3x2x1")
    public static void comparison_le(GameTestHelper helper) {
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var left = (Container) helper.getBlockEntity(leftPos);
        var right = (Container) helper.getBlockEntity(rightPos);
        var manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        left.setItem(0, new ItemStack(Items.DIAMOND, 64));
        left.setItem(1, new ItemStack(Items.DIAMOND, 64));
        left.setItem(2, new ItemStack(Items.IRON_INGOT, 12));
        right.setItem(0, new ItemStack(Items.STICK, 13));
        right.setItem(1, new ItemStack(Items.STICK, 64));
        right.setItem(2, new ItemStack(Items.DIRT, 1));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   NAME "comparison_le test"
                                   EVERY 20 TICKS DO
                                       IF left HAS le 10 diamond THEN
                                           -- should not happen
                                           INPUT diamond FROM left
                                           OUTPUT diamond TO right
                                       END
                                       IF left HAS <= 12 iron_ingot THEN
                                           -- should happen
                                           INPUT iron_ingot FROM left
                                           OUTPUT iron_ingot TO right
                                       END
                                       IF right HAS le 77 stick THEN
                                           -- should happen
                                           INPUT stick FROM right
                                           OUTPUT stick TO left
                                       END
                                       if right has <= 1 dirt then
                                           -- should happen
                                           input dirt from right
                                           output dirt to left
                                       end
                                   END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("left", helper.absolutePos(leftPos))
                .add("right", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            int leftDiamondCount = count(left, Items.DIAMOND);
            int leftIronCount = count(left, Items.IRON_INGOT);
            int leftStickCount = count(left, Items.STICK);
            int leftDirtCount = count(left, Items.DIRT);
            int rightDiamondCount = count(right, Items.DIAMOND);
            int rightIronCount = count(right, Items.IRON_INGOT);
            int rightStickCount = count(right, Items.STICK);
            int rightDirtCount = count(right, Items.DIRT);
            // the diamonds should have moved from left to right
            assertTrue(leftDiamondCount == 64 * 2, "left should have 128 diamonds");
            assertTrue(rightDiamondCount == 0, "right should have no diamonds");
            // the iron should have moved from left to right
            assertTrue(leftIronCount == 0, "left should have no iron ingots");
            assertTrue(rightIronCount == 12, "right should have 12 iron ingots");
            // the sticks should have moved from right to left
            assertTrue(rightStickCount == 0, "right should have no sticks");
            assertTrue(leftStickCount == 77, "left should have 77 sticks");
            // the dirt should have moved from right to left
            assertTrue(rightDirtCount == 0, "right should have no dirt");
            assertTrue(leftDirtCount == 1, "left should have 1 dirt");
        });
    }

    @GameTest(template = "3x2x1")
    public static void forget_1(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Blocks.DIRT, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM a
                                           IF a has gt 0 dirt THEN
                                               FORGET a
                                           END
                                           OUTPUT TO b -- nothing happens :D
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftChest.getStackInSlot(0).getCount() == 64, "Dirt should not depart");
            assertTrue(rightChest.getStackInSlot(0).isEmpty(), "Dirt should not arrive");

        });
    }


    @GameTest(template = "3x2x1")
    public static void forget_2(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Blocks.DIRT, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM a,b,c
                                           IF a has gt 0 dirt THEN
                                               FORGET
                                           END
                                           OUTPUT TO dest -- nothing happens :D
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(leftPos))
                .add("c", helper.absolutePos(leftPos))
                .add("dest", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftChest.getStackInSlot(0).getCount() == 64, "Dirt should not depart");
            assertTrue(rightChest.getStackInSlot(0).isEmpty(), "Dirt should not arrive");

        });
    }


    @GameTest(template = "3x2x1")
    public static void forget_slot(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Blocks.DIRT, 64), false);
        leftChest.insertItem(1, new ItemStack(Items.IRON_INGOT, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM a SLOTS 1-26 -- iron ingot
                                           INPUT FROM c SLOTS 0    -- dirt blocks
                                           IF a SLOTS 0 has gt 0 dirt THEN
                                               FORGET a -- forgets the iron ingot
                                           END
                                           OUTPUT TO b -- will move the dirt block, the next tick the ingots
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("c", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftChest.getStackInSlot(0).isEmpty(), "Dirt should depart");
            assertTrue(leftChest.getStackInSlot(1).getCount() == 64, "Iron ingots should not depart");
            assertTrue(rightChest.getStackInSlot(0).getCount() == 64, "Dirt should arrive in size");
            assertTrue(rightChest.getStackInSlot(0).getItem() == Items.DIRT, "Dirt should arrive in type");

        });
    }

    @GameTest(template = "3x2x1", batch = "linting")
    public static void count_execution_paths_conditional_1(GameTestHelper helper) {
        // place inventories
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        // place manager
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // set the labels
        LabelPositionHolder labelPositionHolder = LabelPositionHolder.empty()
                .add("left", helper.absolutePos(leftPos))
                .add("right", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        // load the program
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           IF left HAS gt 0 stone THEN
                                               INPUT FROM left
                                           END
                                           OUTPUT TO right
                                       END
                                   """.stripTrailing().stripIndent());
        assertManagerRunning(manager);
        var program = manager.getProgram();

        // ensure no warnings
        var warnings = DiskItem.getWarnings(manager.getDisk());
        assertTrue(warnings.isEmpty(), "expected 0 warning, got " + warnings.size());

        // count the execution paths
        GatherWarningsProgramBehaviour simulation = new GatherWarningsProgramBehaviour(warnings::addAll);
        program.tick(ProgramContext.createSimulationContext(
                program,
                labelPositionHolder,
                0,
                simulation
        ));

        List<Integer> expectedPathSizes = new ArrayList<>(List.of(1, 2));
        assertTrue(
                simulation.getSeenPaths().size() == expectedPathSizes.size(),
                "expected " + expectedPathSizes.size() + " execution paths, got " + simulation.getSeenPaths().size()
        );
        int[] actualPathIOSizes = simulation.getSeenIOStatementCountForEachPath();
        // don't assume the order, just that each path size has occurred the specified number of times
        for (int i = 0; i < actualPathIOSizes.length; i++) {
            int pathSize = actualPathIOSizes[i];
            if (!expectedPathSizes.remove((Integer) pathSize)) {
                helper.fail("unexpected path size " + pathSize + " at index " + i + " of " + simulation
                        .getSeenPaths()
                        .size() + " paths");
            }
        }
        helper.succeed();
    }

    @GameTest(template = "3x2x1", batch = "linting")
    public static void count_execution_paths_conditional_1b(GameTestHelper helper) {
        // place inventories
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        // place manager
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // set the labels
        LabelPositionHolder.empty()
                .add("left", helper.absolutePos(leftPos))
                .save(manager.getDisk());

        // load the program
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           IF left HAS gt 0 stone THEN
                                               INPUT FROM left
                                           END
                                       END
                                   """.stripTrailing().stripIndent());
        assertManagerRunning(manager);

        // assert expected warnings
        var warnings = DiskItem.getWarnings(manager.getDisk());
        assertTrue(warnings.size() == 1, "expected 1 warning, got " + warnings.size());
        assertTrue(warnings
                           .get(0)
                           .getKey()
                           .equals(LocalizationKeys.PROGRAM_WARNING_UNUSED_INPUT_LABEL // should be unused input
                                           .key()
                                           .get()), "expected output without matching input warning");
        helper.succeed();
    }

    @GameTest(template = "3x2x1", batch = "linting")
    public static void count_execution_paths_conditional_2(GameTestHelper helper) {
        // place inventories
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        // place manager
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // set the labels
        LabelPositionHolder labelPositionHolder = LabelPositionHolder.empty()
                .add("left1", helper.absolutePos(leftPos))
                .add("left2", helper.absolutePos(leftPos))
                .add("right", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        // load the program
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           IF left2 HAS gt 0 stone THEN
                                               INPUT FROM left1
                                           END
                                           IF left1 HAS gt 0 stone THEN
                                               INPUT FROM left2
                                           END
                                           OUTPUT TO right
                                       END
                                   """.stripTrailing().stripIndent());
        assertManagerRunning(manager);
        var program = manager.getProgram();

        // ensure no warnings
        var warnings = DiskItem.getWarnings(manager.getDisk());
        assertTrue(warnings.isEmpty(), "expected 0 warning, got " + warnings.size());

        // count the execution paths
        GatherWarningsProgramBehaviour simulation = new GatherWarningsProgramBehaviour(warnings::addAll);
        program.tick(ProgramContext.createSimulationContext(
                program,
                labelPositionHolder,
                0,
                simulation
        ));
        List<Integer> expectedPathSizes = new ArrayList<>(List.of(1, 2, 2, 3));
        assertTrue(
                simulation.getSeenPaths().size() == expectedPathSizes.size(),
                "expected " + expectedPathSizes.size() + " execution paths, got " + simulation.getSeenPaths().size()
        );
        int[] actualPathIOSizes = simulation.getSeenIOStatementCountForEachPath();
        // don't assume the order, just that each path size has occurred the specified number of times
        for (int i = 0; i < actualPathIOSizes.length; i++) {
            int pathSize = actualPathIOSizes[i];
            if (!expectedPathSizes.remove((Integer) pathSize)) {
                helper.fail("unexpected path size " + pathSize + " at index " + i + " of " + simulation
                        .getSeenPaths()
                        .size() + " paths");
            }
        }
        helper.succeed();
    }


    @GameTest(template = "3x2x1", batch = "linting")
    public static void conditional_output_inspection(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Blocks.DIRT, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));


        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        // set the program
        String code = """
                    EVERY 20 TICKS DO
                        IF a HAS = 64 dirt THEN
                            INPUT RETAIN 32 FROM a
                        END
                        OUTPUT TO b
                    END
                """.stripTrailing().stripIndent();
        manager.setProgram(code);
        assertManagerRunning(manager);

        // compile new program for inspection
        Program program = compile(code);


        OutputStatement outputStatement = (OutputStatement) program
                .triggers()
                .get(0)
                .getBlock()
                .getStatements()
                .get(1);

        String inspectionResults = ServerboundOutputInspectionRequestPacket.getOutputStatementInspectionResultsString(
                manager,
                program,
                outputStatement
        );

        //noinspection TrailingWhitespacesInTextBlock
        String expected = """
                OUTPUT TO b
                -- predictions may differ from actual execution results
                -- POSSIBILITY 0 -- all false
                OVERALL a HAS = 64 dirt -- false
                
                    -- predicted inputs:
                    none
                    -- predicted outputs:
                    none
                -- POSSIBILITY 1 -- all true
                OVERALL a HAS = 64 dirt -- true
                
                    -- predicted inputs:
                    INPUT 32 minecraft:dirt FROM a SLOTS 0
                    -- predicted outputs:
                    OUTPUT 32 minecraft:dirt TO b
                """.stripLeading().stripIndent().stripTrailing();
        if (!inspectionResults.equals(expected)) {
            System.out.println("Received results:");
            System.out.println(inspectionResults);
            System.out.println("Expected:");
            System.out.println(expected);

            // get the position of the difference and show it
            for (int i = 0; i < inspectionResults.length(); i++) {
                if (inspectionResults.charAt(i) != expected.charAt(i)) {
                    System.out.println("Difference at position "
                                       + i
                                       + ":"
                                       + inspectionResults.charAt(i)
                                       + " vs "
                                       + expected.charAt(i));
                    break;
                }
            }

            helper.fail("inspection didn't match results");
        }

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftChest.getStackInSlot(0).getCount() == 32, "Dirt did not depart");
            assertTrue(rightChest.getStackInSlot(0).getCount() == 32, "Dirt did not arrive");
        });
    }


    @GameTest(template = "3x4x3")
    public static void move_if_powered(GameTestHelper helper) {
        BlockPos managerPos = new BlockPos(1, 2, 1);
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        BlockPos leftPos = managerPos.east();
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos rightPos = managerPos.west();
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos topPos = managerPos.above();
        helper.setBlock(topPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        BlockPos leverPos = managerPos.north();
        helper.setBlock(leverPos, Blocks.LEVER);
        helper.pullLever(leverPos);

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Items.DIRT, 64), false);
        leftChest.insertItem(1, new ItemStack(Items.DIRT, 64), false);
        leftChest.insertItem(2, new ItemStack(Items.STONE, 64), false);
        leftChest.insertItem(3, new ItemStack(Items.IRON_INGOT, 64), false);
        leftChest.insertItem(4, new ItemStack(Items.GOLD_INGOT, 64), false);
        leftChest.insertItem(5, new ItemStack(Items.GOLD_NUGGET, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           IF redstone GT 0 THEN
                                                INPUT FROM left
                                                OUTPUT TO right
                                           END
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("left", helper.absolutePos(leftPos))
                .add("right", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(count(leftChest, null) == 0, "everything should depart");
            assertTrue(count(rightChest, Items.GOLD_NUGGET) == 64, "gold nuggets should arrive");
            assertTrue(count(rightChest, Items.IRON_INGOT) == 64, "iron ingots should arrive");
            assertTrue(count(rightChest, Items.GOLD_INGOT) == 64, "gold ingots should arrive");
            assertTrue(count(rightChest, Items.DIRT) == 64 * 2, "dirt should arrive");
            assertTrue(count(rightChest, Items.STONE) == 64, "stone should arrive");
        });
    }

    @GameTest(template = "3x2x1")
    public static void has_or(GameTestHelper helper) {
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var left = (Container) helper.getBlockEntity(leftPos);
        var right = (Container) helper.getBlockEntity(rightPos);
        var manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        left.setItem(0, new ItemStack(Items.DIAMOND, 64));
        left.setItem(1, new ItemStack(Items.DIAMOND, 64));
        left.setItem(2, new ItemStack(Items.IRON_INGOT, 12));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   NAME "has_or test"
                                   EVERY 20 TICKS DO
                                       IF left HAS LT 150 diamond OR iron_ingot THEN
                                            INPUT FROM left
                                            OUTPUT TO right
                                       END
                                   END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("left", helper.absolutePos(leftPos))
                .add("right", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // left should be empty
            assertTrue(count(left, Items.DIAMOND) == 0, "left should have no diamonds");
            assertTrue(count(left, Items.IRON_INGOT) == 0, "left should have no iron ingots");
            // right should have all the items
            assertTrue(count(right, Items.DIAMOND) == 64 * 2, "right should have 128 diamonds");
            assertTrue(count(right, Items.IRON_INGOT) == 12, "right should have 12 iron ingots");
        });
    }
}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\compat\thermal\SFMThermalCompatGameTests.java

package ca.teamdman.sfm.gametest.compat.thermal;

import ca.teamdman.sfm.SFM;
import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity;
import ca.teamdman.sfm.common.program.LabelPositionHolder;
import ca.teamdman.sfm.common.registry.SFMBlocks;
import ca.teamdman.sfm.common.registry.SFMItems;
import ca.teamdman.sfm.common.registry.SFMResourceTypes;
import ca.teamdman.sfm.gametest.SFMGameTestBase;
import cofh.thermal.expansion.block.entity.machine.MachineFurnaceTile;
import cofh.thermal.expansion.block.entity.machine.MachineInsolatorTile;
import mekanism.common.registries.MekanismBlocks;
import mekanism.common.tile.TileEntityFluidTank;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.material.Fluids;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.gametest.GameTestHolder;
import net.minecraftforge.registries.ForgeRegistries;

import java.util.ArrayList;

@SuppressWarnings({"DataFlowIssue"})
@GameTestHolder(SFM.MOD_ID)
public class SFMThermalCompatGameTests extends SFMGameTestBase {

    @GameTest(template = "25x3x25", timeoutTicks = 20 * 20)
    public static void thermal_furnace_array(GameTestHelper helper) {
        // designate positions
        var furnacePositions = new ArrayList<BlockPos>();
        var resultChestPositions = new ArrayList<BlockPos>();
        var ingredientChestPositions = new ArrayList<BlockPos>();
        var managerPos = new BlockPos(0, 2, 0);
        var powerPos = new BlockPos(1, 2, 0);

        // set up power
        helper.setBlock(powerPos, MekanismBlocks.ULTIMATE_ENERGY_CUBE.getBlock());
        helper
                .getBlockEntity(powerPos)
                .getCapability(ForgeCapabilities.ENERGY, Direction.UP)
                .ifPresent(energy -> energy.receiveEnergy(Integer.MAX_VALUE, false));

        // set up furnaces
        var furnaceBlock = ForgeRegistries.BLOCKS.getValue(new ResourceLocation("thermal", "machine_furnace"));
        for (int x = 0; x < 25; x++) {
            for (int z = 1; z < 25; z++) {
                helper.setBlock(new BlockPos(x, 2, z), SFMBlocks.CABLE_BLOCK.get());
                helper.setBlock(new BlockPos(x, 3, z), furnaceBlock);
                furnacePositions.add(new BlockPos(x, 3, z));
                var furnace = (MachineFurnaceTile) helper.getBlockEntity(new BlockPos(x, 3, z));
                furnace.setSideConfig(Direction.UP, MachineFurnaceTile.SideConfig.SIDE_INPUT);
                furnace.setSideConfig(Direction.DOWN, MachineFurnaceTile.SideConfig.SIDE_OUTPUT);
            }
        }

        // set up destinations
        for (int i = 2; i <= 3; i++) {
            BlockPos pos = new BlockPos(i, 2, 0);
            helper.setBlock(pos, SFMBlocks.TEST_BARREL_BLOCK.get());
            resultChestPositions.add(pos);
        }

        // set up ingredients
        for (int i = 5; i <= 6; i++) {
            BlockPos pos = new BlockPos(i, 2, 0);
            helper.setBlock(pos, SFMBlocks.TEST_BARREL_BLOCK.get());
            ingredientChestPositions.add(pos);
            for (int slot = 0; slot < 27; slot++) {
                getItemHandler(helper, pos).insertItem(slot, new ItemStack(Items.CHICKEN, 64), false);
            }
        }

        // set up the manager
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // create the program
        var program = """
                    NAME "thermal furnace array test"
                    EVERY 5 TICKS DO
                        INPUT forge_energy:forge:energy FROM power NORTH SIDE
                        OUTPUT forge_energy:forge:energy TO furnaces
                    END
                    EVERY 20 TICKS DO
                        INPUT FROM ingredients
                        OUTPUT RETAIN 2 TO EACH furnaces TOP SIDE
                    FORGET
                        INPUT FROM furnaces BOTTOM SIDE
                        OUTPUT TO results TOP SIDE
                    END
                """;

        // set the labels
        LabelPositionHolder.empty()
                .addAll("furnaces", furnacePositions.stream().map(helper::absolutePos).toList())
                .addAll("ingredients", ingredientChestPositions.stream().map(helper::absolutePos).toList())
                .addAll("results", resultChestPositions.stream().map(helper::absolutePos).toList())
                .add("power", helper.absolutePos(powerPos))
                .save(manager.getDisk());

        // load the program
        manager.setProgram(program.stripIndent());
        helper.succeedWhen(() -> {
            // the result chests must be full of cooked chicken
            for (BlockPos resultChestPosition : resultChestPositions) {
                boolean hasEnoughChicken = count(getItemHandler(helper, resultChestPosition), Items.COOKED_CHICKEN)
                                           >= 64 * 27;
                if (!hasEnoughChicken) {
                    helper.fail("Not enough cooked chicken in chest at " + resultChestPosition);
                }
            }
        });
    }


    @SuppressWarnings("DuplicatedCode")
    @GameTest(template = "25x3x25", timeoutTicks = 20 * 20)
    public static void thermal_phyto_array(GameTestHelper helper) {
        // designate positions
        var phytoPositions = new ArrayList<BlockPos>();
        var resultChestPositions = new ArrayList<BlockPos>();
        var seedChestPositions = new ArrayList<BlockPos>();
        var managerPos = new BlockPos(0, 2, 0);
        var powerPos = new BlockPos(1, 2, 0);
        var waterPos = new BlockPos(2, 2, 0);

        // set up power
        helper.setBlock(powerPos, MekanismBlocks.ULTIMATE_ENERGY_CUBE.getBlock());
        helper
                .getBlockEntity(powerPos)
                .getCapability(ForgeCapabilities.ENERGY, Direction.UP)
                .ifPresent(energy -> energy.receiveEnergy(Integer.MAX_VALUE, false));

        // set up water
        helper.setBlock(waterPos, MekanismBlocks.CREATIVE_FLUID_TANK.getBlock());
        TileEntityFluidTank tank = (TileEntityFluidTank) helper.getBlockEntity(waterPos);
        tank.setFluidInTank(0, new FluidStack(Fluids.WATER, Integer.MAX_VALUE));

        // set up phytos
        var phytoBlock = ForgeRegistries.BLOCKS.getValue(new ResourceLocation("thermal", "machine_insolator"));
        SFM.LOGGER.debug("Setting up phytos with block {}", phytoBlock);
        for (int x = 0; x < 25; x++) {
            for (int z = 1; z < 25; z++) {
                BlockPos cableBelow = new BlockPos(x, 2, z);
                helper.setBlock(cableBelow, SFMBlocks.CABLE_BLOCK.get());
                BlockPos phytoPos = new BlockPos(x, 3, z);
                helper.setBlock(phytoPos, phytoBlock);
                phytoPositions.add(phytoPos);
                var phyto = (MachineInsolatorTile) helper.getBlockEntity(phytoPos);
                phyto.setSideConfig(Direction.UP, MachineInsolatorTile.SideConfig.SIDE_INPUT);
                phyto.setSideConfig(Direction.DOWN, MachineInsolatorTile.SideConfig.SIDE_OUTPUT);
            }
        }

        // set up destinations
        SFM.LOGGER.debug("Setting up destinations");
        for (int i = 3; i <= 4; i++) {
            BlockPos pos = new BlockPos(i, 2, 0);
            helper.setBlock(pos, SFMBlocks.TEST_BARREL_BLOCK.get());
            resultChestPositions.add(pos);
        }

        // set up ingredients
        //noinspection NonStrictComparisonCanBeEquality
        for (int i = 6; i <= 6; i++) {
            BlockPos pos = new BlockPos(i, 2, 0);
            helper.setBlock(pos, SFMBlocks.TEST_BARREL_BLOCK.get());
            seedChestPositions.add(pos);
            var items = new Item[]{
                    Items.BEETROOT_SEEDS,
                    Items.MELON_SEEDS,
                    Items.PUMPKIN_SEEDS,
                    Items.WHEAT_SEEDS,
                    SFMResourceTypes.ITEM.get().getItemFromRegistryKey(new ResourceLocation("thermal", "phytogro")),
                    SFMResourceTypes.ITEM.get().getItemFromRegistryKey(new ResourceLocation("thermal", "phytogro")),
                    SFMResourceTypes.ITEM.get().getItemFromRegistryKey(new ResourceLocation("thermal", "phytogro")),
                    SFMResourceTypes.ITEM.get().getItemFromRegistryKey(new ResourceLocation("thermal", "phytogro")),
                    SFMResourceTypes.ITEM.get().getItemFromRegistryKey(new ResourceLocation("thermal", "phytogro")),
                    };
            int slot = 0;
            for (Item item : items) {
                getItemHandler(helper, pos).insertItem(slot++, new ItemStack(item, 64), false);
            }
        }

        // set up the manager
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // create the program
        var program = """
                    NAME "thermal phyto array test"
                    
                    EVERY TICK DO
                        INPUT forge_energy:forge:energy FROM power NORTH SIDE
                        OUTPUT forge_energy:forge:energy TO phytos
                    END
                    
                    EVERY 20 TICKS DO
                        INPUT *seed* FROM seeds
                        OUTPUT RETAIN 2 EACH *seed* TO phytos TOP SIDE SLOTS 0
                        OUTPUT RETAIN 2 *seed* TO EACH phytos TOP SIDE SLOTS 0
                    FORGET
                        INPUT FROM seeds
                        OUTPUT RETAIN 2 phytogro TO EACH phytos TOP SIDE SLOTS 1
                    FORGET
                        INPUT fluid:: FROM water TOP SIDE
                        OUTPUT fluid:: TO phytos TOP SIDE
                    FORGET
                        INPUT FROM phytos BOTTOM SIDE
                        OUTPUT TO results TOP SIDE
                    FORGET
                        INPUT forge_energy:forge:energy FROM power NORTH SIDE
                        OUTPUT forge_energy:forge:energy TO phytos
                    END
                """.stripTrailing().stripIndent();

        // set the labels
        LabelPositionHolder.empty()
                .addAll("phytos", phytoPositions.stream().map(helper::absolutePos).toList())
                .addAll("seeds", seedChestPositions.stream().map(helper::absolutePos).toList())
                .addAll("results", resultChestPositions.stream().map(helper::absolutePos).toList())
                .add("power", helper.absolutePos(powerPos))
                .add("water", helper.absolutePos(waterPos))
                .save(manager.getDisk());

        // load the program
        manager.setProgram(program.stripIndent());
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            SFM.LOGGER.warn("TODO: finish implementing thermal_phyto_array test");
        });
    }
}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\SFMPerformanceGameTests.java

package ca.teamdman.sfm.gametest;

import ca.teamdman.sfm.SFM;
import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity;
import ca.teamdman.sfm.common.item.DiskItem;
import ca.teamdman.sfm.common.program.LabelPositionHolder;
import ca.teamdman.sfm.common.registry.SFMBlocks;
import ca.teamdman.sfm.common.registry.SFMItems;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BarrelBlockEntity;
import net.minecraftforge.gametest.GameTestHolder;
import net.minecraftforge.gametest.PrefixGameTestTemplate;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@SuppressWarnings({"DataFlowIssue", "DuplicatedCode", "DefaultAnnotationParam"})
@GameTestHolder(SFM.MOD_ID)
@PrefixGameTestTemplate(value=true)
public class SFMPerformanceGameTests extends SFMGameTestBase {
    @GameTest(template = "25x3x25", batch = "laggy")
    public static void move_many_inventories(GameTestHelper helper) {
        // fill the platform with cables and barrels
        var sourceBlocks = new ArrayList<BlockPos>();
        var destBlocks = new ArrayList<BlockPos>();
        for (int x = 0; x < 25; x++) {
//            for (int z = 0; z < 25; z++) {
            for (int z = 0; z < 24; z++) {
                helper.setBlock(new BlockPos(x, 2, z), SFMBlocks.CABLE_BLOCK.get());
                helper.setBlock(new BlockPos(x, 3, z), SFMBlocks.TEST_BARREL_BLOCK.get());
                if (z % 2 == 0) {
                    sourceBlocks.add(new BlockPos(x, 3, z));
                    // fill the source chests with ingots
                    BarrelBlockEntity barrel = (BarrelBlockEntity) helper.getBlockEntity(new BlockPos(x, 3, z));
                    for (int i = 0; i < barrel.getContainerSize(); i++) {
                        barrel.setItem(i, new ItemStack(Items.IRON_INGOT, 64));
                    }
                } else {
                    destBlocks.add(new BlockPos(x, 3, z));
                }
            }
        }

        // fill in the blocks needed for the test
        helper.setBlock(new BlockPos(0, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(0, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // create the program
        var program = """
                    NAME "many inventory lag test"
                
                    EVERY 20 TICKS DO
                        INPUT FROM a
                        OUTPUT TO b
                    END
                """.stripTrailing().stripIndent();

        // set the labels
        LabelPositionHolder.empty()
                .addAll("a", sourceBlocks.stream().map(helper::absolutePos).toList())
                .addAll("b", destBlocks.stream().map(helper::absolutePos).toList())
                .save(manager.getDisk());

        // load the program
        manager.setProgram(program);
        assertTrue(
                manager.getState() == ManagerBlockEntity.State.RUNNING,
                "Program did not start running " + DiskItem.getErrors(manager.getDisk())
        );

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // ensure all the source chests are empty
            sourceBlocks.forEach(pos -> {
                BarrelBlockEntity barrel = (BarrelBlockEntity) helper.getBlockEntity(pos);
                for (int i = 0; i < barrel.getContainerSize(); i++) {
                    ItemStack found = barrel.getItem(i);
                    assertTrue(
                            found.isEmpty(),
                            "Items did not leave, pos=" + helper.absolutePos(pos) + " i=" + i + " found=" + found
                    );
                }
            });
            // ensure all the dest chests are full
            destBlocks.forEach(pos -> {
                BarrelBlockEntity barrel = (BarrelBlockEntity) helper.getBlockEntity(pos);
                for (int i = 0; i < barrel.getContainerSize(); i++) {
                    assertTrue(barrel.getItem(i).getCount() == 64, "Items did not arrive");
                }
            });


        });
    }

    @GameTest(template = "25x3x25", batch = "laggy")
    public static void move_many_full(GameTestHelper helper) {
        // fill the platform with cables and barrels
        var sourceBlocks = new ArrayList<BlockPos>();
        var destBlocks = new ArrayList<BlockPos>();
        for (int x = 0; x < 25; x++) {
//            for (int z = 0; z < 25; z++) {
            for (int z = 0; z < 24; z++) {
                helper.setBlock(new BlockPos(x, 2, z), SFMBlocks.CABLE_BLOCK.get());
                helper.setBlock(new BlockPos(x, 3, z), SFMBlocks.TEST_BARREL_BLOCK.get());
                if (z % 2 == 0) {
                    sourceBlocks.add(new BlockPos(x, 3, z));
                } else {
                    destBlocks.add(new BlockPos(x, 3, z));
                }

                // fill the source chests with ingots
                BarrelBlockEntity barrel = (BarrelBlockEntity) helper.getBlockEntity(new BlockPos(x, 3, z));
                for (int i = 0; i < barrel.getContainerSize(); i++) {
                    barrel.setItem(i, new ItemStack(Items.IRON_INGOT, 64));
                }
            }
        }

        // fill in the blocks needed for the test
        helper.setBlock(new BlockPos(0, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(0, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // create the program
        var program = """
                    NAME "move many full"
                
                    EVERY 20 TICKS DO
                        INPUT FROM a
                        OUTPUT TO b
                    END
                """.stripTrailing().stripIndent();

        // set the labels
        LabelPositionHolder.empty()
                .addAll("a", sourceBlocks.stream().map(helper::absolutePos).toList())
                .addAll("b", destBlocks.stream().map(helper::absolutePos).toList())
                .save(manager.getDisk());

        // load the program
        manager.setProgram(program);
        assertTrue(
                manager.getState() == ManagerBlockEntity.State.RUNNING,
                "Program did not start running " + DiskItem.getErrors(manager.getDisk())
        );

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // ensure all the source chests are full
            sourceBlocks.forEach(pos -> {
                BarrelBlockEntity barrel = (BarrelBlockEntity) helper.getBlockEntity(pos);
                for (int i = 0; i < barrel.getContainerSize(); i++) {
                    assertTrue(barrel.getItem(i).getCount() == 64, "Items did not stay");
                }
            });
            // ensure all the dest chests are full
            destBlocks.forEach(pos -> {
                BarrelBlockEntity barrel = (BarrelBlockEntity) helper.getBlockEntity(pos);
                for (int i = 0; i < barrel.getContainerSize(); i++) {
                    assertTrue(barrel.getItem(i).getCount() == 64, "Items did not arrive");
                }
            });


        });
    }

    /**
     * Creates many inventories.
     * Half of them will be full, the other half will be empty.
     * The half that is full will have three different items, one type per row:
     * - iron ingots
     * - gold ingots
     * - diamonds
     * The program should use a regular expression to match only ingots in the form *:*_ingot
     */
    @GameTest(template = "25x3x25", batch = "laggy")
    public static void move_many_regex(GameTestHelper helper) {
        // fill the platform with cables and barrels
        var sourceBlocks = new ArrayList<BlockPos>();
        var destBlocks = new ArrayList<BlockPos>();

        var ironIngots = new AtomicInteger(0);
        var goldIngots = new AtomicInteger(0);
        var diamonds = new AtomicInteger(0);

        for (int x = 0; x < 25; x++) {
            for (int z = 0; z < 24; z++) { // make sure we have an even number to split
                // place a cable below
                helper.setBlock(new BlockPos(x, 2, z), SFMBlocks.CABLE_BLOCK.get());
                // place the barrel on top
                helper.setBlock(new BlockPos(x, 3, z), SFMBlocks.TEST_BARREL_BLOCK.get());
                if (z % 2 == 0) {
                    sourceBlocks.add(new BlockPos(x, 3, z));
                    BarrelBlockEntity barrel = (BarrelBlockEntity) helper.getBlockEntity(new BlockPos(x, 3, z));
                    for (int i = 0; i < barrel.getContainerSize(); i++) {
                        if (i % 3 == 0) {
                            barrel.setItem(i, new ItemStack(Items.IRON_INGOT, 64));
                            ironIngots.addAndGet(64);
                        } else if (i % 3 == 1) {
                            barrel.setItem(i, new ItemStack(Items.GOLD_INGOT, 64));
                            goldIngots.addAndGet(64);
                        } else {
                            barrel.setItem(i, new ItemStack(Items.DIAMOND, 64));
                            diamonds.addAndGet(64);
                        }
                    }
                } else {
                    destBlocks.add(new BlockPos(x, 3, z));
                }
            }
        }

        // create the manager block and add the disk
        helper.setBlock(new BlockPos(0, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(0, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // create the program
        var program = """
                    NAME "move many regex"
                
                    EVERY 20 TICKS DO
                        INPUT *:*_ingot FROM a
                        OUTPUT TO b
                    END
                """.stripTrailing().stripIndent();

        // set the labels
        LabelPositionHolder.empty()
                .addAll("a", sourceBlocks.stream().map(helper::absolutePos).toList())
                .addAll("b", destBlocks.stream().map(helper::absolutePos).toList())
                .save(manager.getDisk());

        // load the program
        manager.setProgram(program);
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // ensure the source chests only have the non-ingot items
            sourceBlocks.forEach(pos -> {
                BarrelBlockEntity barrel = (BarrelBlockEntity) helper.getBlockEntity(pos);
                for (int i = 0; i < barrel.getContainerSize(); i++) {
                    if (i % 3 == 0) {
                        assertTrue(barrel.getItem(i).isEmpty(), "Items did not depart");
                    } else if (i % 3 == 1) {
                        assertTrue(barrel.getItem(i).isEmpty(), "Items did not depart");
                    } else {
                        assertTrue(barrel.getItem(i).getItem() == Items.DIAMOND, "Non-matching didn't stay");
                    }
                }
            });
            // ensure the destination chests only have the ingot items
            int diamondStart = diamonds.get();
            destBlocks.forEach(pos -> {
                BarrelBlockEntity barrel = (BarrelBlockEntity) helper.getBlockEntity(pos);
                for (int i = 0; i < barrel.getContainerSize(); i++) {
                    Item item = barrel.getItem(i).getItem();
                    if (item == Items.IRON_INGOT) {
                        ironIngots.addAndGet(-barrel.getItem(i).getCount());
                    } else if (item == Items.GOLD_INGOT) {
                        goldIngots.addAndGet(-barrel.getItem(i).getCount());
                    } else if (item == Items.DIAMOND) {
                        diamonds.addAndGet(-barrel.getItem(i).getCount());
                    }
                }
            });
            assertTrue(ironIngots.get() == 0, "Iron ingots did not arrive");
            assertTrue(goldIngots.get() == 0, "Gold ingots did not arrive");
            assertTrue(diamonds.get() == diamondStart, "Diamonds did not stay");
        });
    }

    /**
     * Creates four chests: a, b, c, d
     * a: 9x64 iron ingots
     * b: 9x64 gold ingots
     * c: 9x64 diamonds
     * d: 26x64 cobblestone, 1x64 copper ingot
     * <p>
     * Every 20 ticks, the program should:
     * - move all ingots from a to b, b to c, c to d, d to a
     * <p>
     * The program should use a regular expression to match only ingots in the form *:*_ingot
     */
    @GameTest(template = "3x4x3", batch = "laggy")
    public static void move_regex_circle(GameTestHelper helper) {
        var managerPos = new BlockPos(1, 2, 1);
        var aPos = new BlockPos(1, 2, 0);
        var bPos = new BlockPos(2, 2, 1);
        var cPos = new BlockPos(1, 2, 2);
        var dPos = new BlockPos(0, 2, 1);

        // place and fill the chests
        helper.setBlock(aPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(bPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(cPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(dPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        var a = (BarrelBlockEntity) helper.getBlockEntity(aPos);
        var b = (BarrelBlockEntity) helper.getBlockEntity(bPos);
        var c = (BarrelBlockEntity) helper.getBlockEntity(cPos);
        var d = (BarrelBlockEntity) helper.getBlockEntity(dPos);
        for (int i = 0; i < 27; i++) {
            if (i < 9) {
                a.setItem(i, new ItemStack(Items.IRON_INGOT, 64));
                b.setItem(i, new ItemStack(Items.GOLD_INGOT, 64));
                c.setItem(i, new ItemStack(Items.DIAMOND, 64));
            }
            d.setItem(i, new ItemStack(Items.COBBLESTONE, 64));
        }
        d.setItem(26, new ItemStack(Items.COPPER_INGOT, 64));

        // create the manager block and add the disk
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // create the program
        var program = """
                    NAME "move regex circle"
                
                    EVERY 20 TICKS DO
                        INPUT *:*_ingot FROM a
                        OUTPUT TO b
                    END
                    EVERY 20 TICKS DO
                        INPUT *:*_ingot FROM b
                        OUTPUT TO c
                    END
                    EVERY 20 TICKS DO
                        INPUT *:*_ingot FROM c
                        OUTPUT TO d
                    END
                    EVERY 20 TICKS DO
                        INPUT *:*_ingot FROM d
                        OUTPUT TO a
                    END
                """.stripTrailing().stripIndent();

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(aPos))
                .add("b", helper.absolutePos(bPos))
                .add("c", helper.absolutePos(cPos))
                .add("d", helper.absolutePos(dPos))
                .save(manager.getDisk());

        // load the program
        manager.setProgram(program);

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {

        });
    }

    /**
     * In FTB Skies, I was generating resources using sieves and gravel.
     * This would give a bunch of raw metals, which I would then smelt into ingots.
     * Those ingots would be moved into gold barrels; very large inventories; and I had a lot of them.
     * Then I had a crafting station with its own adjacent gold barrel.
     * I wrote a program to keep that gold barrel stocked with essential ingredients.
     */
    @GameTest(template = "25x4x25")
    public static void gather_supplies(GameTestHelper helper) {
        var items = new Item[]{
                Items.GOLD_INGOT,
                Items.GOLD_BLOCK,
                Items.IRON_INGOT,
                Items.IRON_BLOCK,
                Items.DIAMOND,
                Items.DIAMOND_BLOCK,
                Items.EMERALD,
                Items.EMERALD_BLOCK,
                Items.LAPIS_LAZULI,
                Items.LAPIS_BLOCK,
                Items.REDSTONE,
                Items.REDSTONE_BLOCK,
                Items.COAL,
                Items.COAL_BLOCK,
                Items.NETHERITE_INGOT,
                Items.NETHERITE_BLOCK,
                Items.TORCH,
                Items.BUCKET,
                Items.CHEST,
                Items.CRAFTING_TABLE,
                Items.FURNACE,
                Items.COBBLESTONE,
                Items.OAK_LOG,
                Items.OAK_PLANKS
        };

        // create a bunch of storage inventories
        List<BlockPos> storage = new ArrayList<>();
        int itemIndex = 0;
        for (int i = 0; i < 24; i++) {
            for (int j = 3; j < 25; j++) {
                for (int k = 0; k < 3; k++) {
                    BlockPos pos = new BlockPos(i, k + 2, j);
                    if (j == 3) {
                        if (k == 0) {
                            helper.setBlock(pos, SFMBlocks.CABLE_BLOCK.get());
                        }
                    } else {
                        if (i % 3 == 0 || i % 3 == 2) {
                            helper.setBlock(pos, SFMBlocks.TEST_BARREL_BLOCK.get());
                            // fill the barrel with some items
                            BarrelBlockEntity barrel = (BarrelBlockEntity) helper.getBlockEntity(pos);
                            for (int slot = 0; slot < barrel.getContainerSize(); slot++) {
                                barrel.setItem(slot, new ItemStack(items[itemIndex++ % items.length], 64));
                            }

                            storage.add(pos);
                        } else {
                            helper.setBlock(pos, SFMBlocks.CABLE_BLOCK.get());
                        }
                    }
                }
            }
        }

        // add the crafting station
        helper.setBlock(new BlockPos(0, 2, 1), Blocks.CRAFTING_TABLE);
        helper.setBlock(new BlockPos(0, 2, 0), SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.CABLE_BLOCK.get());
        helper.setBlock(new BlockPos(1, 2, 1), SFMBlocks.CABLE_BLOCK.get());
        helper.setBlock(new BlockPos(1, 2, 2), SFMBlocks.CABLE_BLOCK.get());

        // add the manager
        helper.setBlock(new BlockPos(2, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        var manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(2, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // create the program
        var program = """
                NAME "gather supplies"
                
                EVERY 20 TICKS DO
                   INPUT
                       retain 64 gold_ingot,
                       retain 64 gold_block,
                       retain 64 iron_ingot,
                       retain 64 iron_block,
                       retain 64 diamond,
                       retain 64 diamond_block,
                       retain 64 emerald,
                       retain 64 emerald_block,
                       retain 64 lapis_lazuli,
                       retain 64 lapis_block,
                       retain 64 "redstone",
                       retain 64 redstone_block,
                       retain 64 coal,
                       retain 64 coal_block,
                       retain 64 netherite_ingot,
                       retain 64 netherite_block,
                       retain 64 torch,
                       retain 16 bucket,
                       retain 64 chest,
                       retain 64 crafting_table,
                       retain 64 furnace,
                       retain 64 cobblestone,
                       retain 64 *:*_log,
                       retain 64 *:*_planks
                    FROM chest
                    INPUT EXCEPT
                       gold_ingot,
                       gold_block,
                       iron_ingot,
                       iron_block,
                       diamond,
                       diamond_block,
                       emerald,
                       emerald_block,
                       lapis_lazuli,
                       lapis_block,
                       "redstone",
                       redstone_block,
                       coal,
                       coal_block,
                       netherite_ingot,
                       netherite_block,
                       torch,
                       bucket,
                       chest,
                       crafting_table,
                       furnace,
                       cobblestone,
                       *:*_log,
                       *:*_planks
                   FROM chest
                   OUTPUT TO storage
                END
                
                EVERY 20 TICKS DO
                   INPUT FROM storage
                   OUTPUT
                       retain 64 gold_ingot,
                       retain 64 gold_block,
                       retain 64 iron_ingot,
                       retain 64 iron_block,
                       retain 64 diamond,
                       retain 64 diamond_block,
                       retain 64 emerald,
                       retain 64 emerald_block,
                       retain 64 lapis_lazuli,
                       retain 64 lapis_block,
                       retain 64 "redstone",
                       retain 64 redstone_block,
                       retain 64 coal,
                       retain 64 coal_block,
                       retain 64 netherite_ingot,
                       retain 64 netherite_block,
                       retain 64 torch,
                       retain 16 bucket,
                       retain 64 chest,
                       retain 64 crafting_table,
                       retain 64 furnace,
                       retain 64 cobblestone,
                       retain 64 *:*_log,
                       retain 64 *:*_planks
                   TO chest
                END
                """.stripTrailing().stripIndent();

        // set the labels
        LabelPositionHolder.empty()
                .addAll("storage", storage.stream().map(helper::absolutePos).toList())
                .add("chest", helper.absolutePos(new BlockPos(0, 2, 0)))
                .save(manager.getDisk());

        // load the program
        manager.setProgram(program);

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // the inventory should be stocked with a stack of each item
            BarrelBlockEntity barrel = (BarrelBlockEntity) helper.getBlockEntity(new BlockPos(0, 2, 0));
            for (Item item : items) {
                for (int slot = 0; slot < barrel.getContainerSize(); slot++) {
                    ItemStack stack = barrel.getItem(slot);
                    if (stack.getItem() == item) {
                        assertTrue(
                                stack.getCount() == stack.getMaxStackSize(),
                                "Item " + item + " is not fully stocked"
                        );
                    }
                }
            }
        });
    }
}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\SFMProgramLinterGameTests.java

package ca.teamdman.sfm.gametest;

import ca.teamdman.sfm.SFM;
import ca.teamdman.sfm.common.item.DiskItem;
import ca.teamdman.sfm.common.localization.LocalizationKeys;
import ca.teamdman.sfm.common.registry.SFMBlocks;
import ca.teamdman.sfm.common.registry.SFMItems;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.gametest.GameTestHolder;

@GameTestHolder(SFM.MOD_ID)
public class SFMProgramLinterGameTests extends SFMGameTestBase {
    @GameTest(template = "3x2x1")
    public static void mekanism_null_io_direction(GameTestHelper helper) {
        helper.succeed();
//        new SFMChestTestBuilder(helper)
//                .addChest();
    }


    @GameTest(template = "1x2x1")
    public static void disk_name(GameTestHelper helper) {
        BlockPos chestPos = new BlockPos(0, 2, 0);
        helper.setBlock(chestPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        var chest = getItemHandler(helper, chestPos);

        {
            ItemStack disk = new ItemStack(SFMItems.DISK_ITEM.get());
            String programString = """
                    NAME "bruh"
                    EVERY 20 TICKS DO
                    END
                    """;
            DiskItem.setProgram(disk, programString);
            DiskItem.compileAndUpdateErrorsAndWarnings(disk, null);
            chest.insertItem(0, disk, false);
            assertTrue(DiskItem.getProgramName(disk).equals("bruh"), "program name should be bruh for disk 1");
            assertTrue(DiskItem.getWarnings(disk).isEmpty(), "there should be no warnings on disk 1");
            assertTrue(DiskItem.getErrors(disk).isEmpty(), "there should be no errors on disk 1");
            assertTrue(disk.getHoverName().getString().equals("bruh"), "display name should be \"bruh\" for disk 1");
        }
        {
            ItemStack disk = new ItemStack(SFMItems.DISK_ITEM.get());
            String programString = """
                    EVERY 20 TICKS DO
                    END
                    """;
            DiskItem.setProgram(disk, programString);
            DiskItem.compileAndUpdateErrorsAndWarnings(disk, null);
            chest.insertItem(1, disk, false);
            assertTrue(DiskItem.getProgramName(disk).isEmpty(), "program name should be empty for disk 2");
            assertTrue(DiskItem.getWarnings(disk).isEmpty(), "there should be no warnings on disk 2");
            assertTrue(DiskItem.getErrors(disk).isEmpty(), "there should be no errors on disk 2");
            assertTrue(
                    disk.getHoverName().contains(LocalizationKeys.DISK_ITEM.getComponent()),
                    "display name should be default for disk 2"
            );
        }
        helper.succeed();
    }
}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\compat\multiple\package-info.java

@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@FieldsAreNonnullByDefault
package ca.teamdman.sfm.gametest.compat.multiple;

import net.minecraft.FieldsAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;

import javax.annotation.ParametersAreNonnullByDefault;

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\compat\industrialforegoing\package-info.java

@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@FieldsAreNonnullByDefault
package ca.teamdman.sfm.gametest.compat.industrialforegoing;

import net.minecraft.FieldsAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;

import javax.annotation.ParametersAreNonnullByDefault;

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\SFMTestBuilder.java

package ca.teamdman.sfm.gametest;

import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity;
import ca.teamdman.sfm.common.program.LabelPositionHolder;
import ca.teamdman.sfm.common.registry.SFMBlocks;
import ca.teamdman.sfm.common.registry.SFMItems;
import ca.teamdman.sfm.common.util.NotStored;
import ca.teamdman.sfm.common.util.SFMItemUtils;
import ca.teamdman.sfm.common.util.Stored;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTestAssertException;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.items.IItemHandler;
import org.jetbrains.annotations.Nullable;

import java.util.*;

public abstract class SFMTestBuilder extends SFMGameTestBase {
    protected final GameTestHelper helper;
    protected @Nullable ManagerBlockEntity manager;
    protected Map<String, IItemHandler> chests = new HashMap<>();
    protected Map<String, BlockPos> positions = new HashMap<>();
    protected LabelPositionHolder labelHolder = LabelPositionHolder.empty();
    protected @Nullable String program;
    protected List<Runnable> postConditions = new ArrayList<>();
    protected List<Runnable> preConditions = new ArrayList<>();

    public SFMTestBuilder(GameTestHelper helper) {
        this.helper = helper;
    }

    public SFMTestBuilder setProgram(String program) {
        // need to strip to ensure no indent despite java multiline string usage
        this.program = program.stripTrailing().stripIndent();
        return this;
    }

    public SFMTestBuilder preContents(
            String name,
            List<ItemStack> stacks
    ) {
        preConditions.add(() -> {
            IItemHandler chest = chests.get(name);
            if (chest == null) {
                throw new IllegalArgumentException("Chest not found: " + name);
            }
            for (int i = 0; i < stacks.size(); i++) {
                chest.insertItem(i, stacks.get(i), false);
            }
        });
        return this;
    }

    public SFMTestBuilder postContents(
            String name,
            List<ItemStack> expected
    ) {
        postConditions.add(() -> {
            IItemHandler chest = chests.get(name);
            if (chest == null) {
                throw new IllegalArgumentException("Chest not found: " + name);
            }
            for (int i = 0; i < chest.getSlots(); i++) {
                ItemStack expectedStack = i < expected.size() ? expected.get(i) : ItemStack.EMPTY;
                ItemStack actualStack = chest.getStackInSlot(i);
                assertTrue(
                        expectedStack.isEmpty() && actualStack.isEmpty() || SFMItemUtils.isSameItemSameAmount(
                                expectedStack,
                                actualStack
                        ),
                        String.format("Expected %s in chest %s slot %d, but found %s",
                                      expectedStack, name, i, actualStack
                        )
                );
            }
        });
        return this;
    }

    public void run() {
        setupStructure(BlockPos.ZERO);
        for (Runnable preCondition : preConditions) {
            preCondition.run();
        }
        assert manager != null;
        labelHolder.save(Objects.requireNonNull(manager.getDisk()));
        assertManagerDidThingWithoutLagging(
                helper,
                manager,
                () -> {
                    // first, assertions as normal
                    {
                        for (Runnable assertion : postConditions) {
                            assertion.run();
                        }
                    }

                    // second, move an item and ensure that the assertions fail somewhere
                    {
                        // shuffle the candidate chests
                        ArrayList<Map.Entry<String, IItemHandler>> chests = new ArrayList<>(this.chests.entrySet());
                        Collections.shuffle(chests);

                        // find a chest with something we can take
                        Map.Entry<String, IItemHandler> source = null;
                        for (int i = 0; i < chests.size(); i++) {
                            IItemHandler chest = chests.get(i).getValue();
                            if (count(chest, null) > 0) {
                                source = chests.remove(i);
                                break;
                            }
                        }
                        assertTrue(
                                source != null,
                                "Chaos failed to find an item to move?? What is this test doing that there's no items??"
                        );

                        // find a chest to move it to
                        Map.Entry<String, IItemHandler> dest = chests.remove(0);

                        // take out an item
                        ItemStack taken = ItemStack.EMPTY;
                        int takenSlot = -1;
                        int destSlot = -1;
                        for (int trySourceSlot = 0; trySourceSlot < source.getValue().getSlots(); trySourceSlot++) {
                            taken = source.getValue().extractItem(trySourceSlot, 1, false);
                            if (!taken.isEmpty()) {
                                takenSlot = trySourceSlot;
                                break;
                            }
                        }
                        assertTrue(
                                !taken.isEmpty(),
                                "Chaos failed to take an item from "
                                + source.getKey()
                                + " slot "
                                + takenSlot
                        );

                        // insert the item
                        for (int tryDestSlot = 0; tryDestSlot < dest.getValue().getSlots(); tryDestSlot++) {
                            ItemStack remainder = dest.getValue().insertItem(tryDestSlot, taken, false);
                            if (remainder.isEmpty()) {
                                taken = taken.copy(); // because we pass the ownership, we should copy before we call extract which will mutate
                                destSlot = tryDestSlot;
                                break;
                            }
                        }

                        // assert a move occurred
                        assertTrue(
                                destSlot != -1,
                                "Chaos failed to insert the taken item, took "
                                + taken
                                + " from "
                                + source.getKey()
                                + " slot "
                                + takenSlot
                                + " to put in"
                                + dest.getKey()
                        );

                        // assert that the assertions fail
                        boolean tripped = false;
                        for (Runnable assertion : postConditions) {
                            try {
                                assertion.run();
                            } catch (GameTestAssertException e) {
                                tripped = true;
                                break;
                            }
                        }
                        assertTrue(
                                tripped,
                                "Assertions did not fail after chaos, moved "
                                + taken
                                + " from "
                                + source.getKey()
                                + " slot "
                                + takenSlot
                                + " to "
                                + dest.getKey()
                        );

                        // take out the moved item
                        ItemStack undo = dest.getValue().extractItem(destSlot, taken.getCount(), false);
                        assertTrue(
                                 SFMItemUtils.isSameItemSameAmount(undo, taken),
                                "Chaos failed to take the moved item, took "
                                + undo
                                + " from "
                                + dest.getKey()
                                + " slot "
                                + destSlot
                                + " instead of "
                                + taken
                        );

                        // put the item back
                        ItemStack remainder = source.getValue().insertItem(takenSlot, taken, false);
                        assertTrue(
                                remainder.isEmpty(),
                                "Chaos failed to put the taken item back, took "
                                + taken
                                + " from "
                                + source.getKey()
                                + " slot "
                                + takenSlot
                        );

                    }
                },
                helper::succeed
        );
    }

    protected void addChest(
            String name,
            @Stored BlockPos pos
    ) {
        helper.setBlock(pos, SFMBlocks.TEST_BARREL_BLOCK.get());
        IItemHandler chest = getItemHandler(helper, pos);
        chests.put(name, chest);
        positions.put(name, pos);
        labelHolder.add(name, helper.absolutePos(pos));
    }

    @SuppressWarnings("SameParameterValue")
    protected abstract void setupStructure(@NotStored BlockPos offset);

    protected void setupManager(@NotStored BlockPos offset) {
        BlockPos managerPos = new BlockPos(1, 2, 0).offset(offset);
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        assertTrue(manager != null, "Manager not found");
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        assertTrue(program != null, "Program not set");
        manager.setProgram(program.stripTrailing().stripIndent());
    }
}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\compat\package-info.java

@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@FieldsAreNonnullByDefault
package ca.teamdman.sfm.gametest.compat;

import net.minecraft.FieldsAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;

import javax.annotation.ParametersAreNonnullByDefault;

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\compat\ae2\package-info.java

@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@FieldsAreNonnullByDefault
package ca.teamdman.sfm.gametest.compat.ae2;

import net.minecraft.FieldsAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;

import javax.annotation.ParametersAreNonnullByDefault;

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\LeftRightTopManagerTest.java

package ca.teamdman.sfm.gametest;


import ca.teamdman.sfm.common.util.NotStored;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTestHelper;

@SuppressWarnings("unused")
public class LeftRightTopManagerTest extends LeftRightManagerTest {
    public LeftRightTopManagerTest(GameTestHelper helper) {
        super(helper);
    }

    @Override
    protected void setupStructure(@NotStored BlockPos offset) {
        setupChests(offset.offset(0, 0, 1));
        setupManager(offset.offset(0, 0, 1));
    }

    @Override
    protected void setupChests(@NotStored BlockPos offset) {
        super.setupChests(offset);
        addChest("top", new BlockPos(1, 3, 0).offset(offset));
    }
}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\compat\thermal\package-info.java

@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@FieldsAreNonnullByDefault
package ca.teamdman.sfm.gametest.compat.thermal;

import net.minecraft.FieldsAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;

import javax.annotation.ParametersAreNonnullByDefault;

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\SFM_A_GameTestStructureGeneratorTest.java

package ca.teamdman.sfm.gametest;

import ca.teamdman.sfm.SFM;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.gametest.framework.GameTestRegistry;
import net.minecraft.gametest.framework.TestFunction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.StructureBlockEntity;
import net.minecraft.world.level.block.state.properties.StructureMode;
import net.minecraftforge.gametest.GameTestHolder;

import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Path;
import java.util.Set;
import java.util.stream.Collectors;

@GameTestHolder(SFM.MOD_ID)
public class SFM_A_GameTestStructureGeneratorTest extends SFMGameTestBase {
    @GameTest(template = "25x4x25")
    public static void create_structure_nbt(GameTestHelper helper) {
        StructureBlockEntity structureBlockEntity;
        structureBlockEntity = (StructureBlockEntity) helper.getBlockEntity(new BlockPos(0, 0, 0));
        assert structureBlockEntity != null;
        String oldName = structureBlockEntity.getStructureName();
        StructureMode oldMode = structureBlockEntity.getMode();
        Vec3i oldSize = structureBlockEntity.getStructureSize();
        try {
            structureBlockEntity.setMode(StructureMode.SAVE);
            int existingCount = 0;
            Set<String> structureNames = GameTestRegistry
                    .getAllTestFunctions()
                    .stream()
                    // sfmmekanismcompatgametests.mek_chemtank_infusion_full
                    .filter(testFunction -> testFunction.getTestName().startsWith("sfm"))
                    .map(TestFunction::getStructureName)
                    .map(structureName -> structureName.split("sfm:")[1])
                    .collect(Collectors.toSet());
            for (String structureName : structureNames) {
                // extract size
                Vec3i size;
                // sfm:sfmmekanismcompatgametests.3x2x1
                String[] chunks = structureName.split("\\.");
                if (chunks.length != 2) {
                    SFM.LOGGER.warn(
                            "Structure name does not contain dimensions: {}",
                            structureName
                    );
                    continue;
                }
                String dimensions = chunks[1];
                String[] xyz = dimensions.split("x");
                size = new Vec3i(Integer.parseInt(xyz[0]), Integer.parseInt(xyz[1]), Integer.parseInt(xyz[2]));
                structureBlockEntity.setStructureSize(size);

                // update structure size
                structureBlockEntity.setStructureSize(size);

                // rename the structure
                structureBlockEntity.setStructureName(structureName);

                // perform save
                structureBlockEntity.saveStructure();
                Path structurePath = helper
                        .getLevel()
                        .getStructureManager()
                        .getPathToGeneratedStructure(new ResourceLocation("minecraft", structureName), ".nbt");

                // copy file
                Path repoDir = structurePath
                        .toAbsolutePath()
                        .getParent()
                        .getParent()
                        .getParent()
                        .getParent()
                        .getParent()
                        .getParent()
                        .getParent();
                Path targetDir = repoDir.resolve("src/gametest/resources/data/sfm/structures");
//                    SFM.LOGGER.info("Copying {} to {}", structurePath, targetDir);
                try {
                    try {
                        java.nio.file.Files.copy(
                                structurePath,
                                targetDir.resolve(structurePath.getFileName())
                        );
                    } catch (FileAlreadyExistsException e) {
                        existingCount++;
                    }
                } catch (Exception e) {
                    SFM.LOGGER.error("Failed to copy structure", e);
                }
            }
            if (existingCount > 0) {
                SFM.LOGGER.warn("Skipped {} existing structures", existingCount);
            }
            helper.succeed();
        } catch (Throwable t) {
            SFM.LOGGER.error("Failed to create structures", t);
            helper.fail("Failed to create structures");
        } finally {
            structureBlockEntity.setStructureName(oldName);
            structureBlockEntity.setMode(oldMode);
            structureBlockEntity.setStructureSize(oldSize);

        }
    }
}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\declarative\SFMDeclarativeTestBuilder.java

package ca.teamdman.sfm.gametest.declarative;

import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity;
import ca.teamdman.sfm.common.program.DefaultProgramBehaviour;
import ca.teamdman.sfm.common.program.LabelPositionHolder;
import ca.teamdman.sfm.common.program.ProgramContext;
import ca.teamdman.sfm.common.registry.SFMBlocks;
import ca.teamdman.sfm.common.registry.SFMItems;
import ca.teamdman.sfm.common.util.NotStored;
import ca.teamdman.sfm.gametest.SFMGameTestBase;
import ca.teamdman.sfml.ast.ASTBuilder;
import ca.teamdman.sfml.ast.BoolExpr;
import ca.teamdman.sfml.ast.IfStatement;
import ca.teamdman.sfml.ast.Program;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTestAssertException;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import org.apache.commons.lang3.mutable.Mutable;
import org.apache.commons.lang3.mutable.MutableObject;
import org.jetbrains.annotations.NotNull;

import java.util.List;
import java.util.Objects;
import java.util.Set;

public class SFMDeclarativeTestBuilder extends SFMGameTestBase {
    private final GameTestHelper helper;
    private final SFMTestSpec spec;
    private int conditionIndex = 0;

    public SFMDeclarativeTestBuilder(
            GameTestHelper helper,
            SFMTestSpec spec
    ) {
        this.helper = helper;
        this.spec = spec;
    }

    public void run() {
        BlockPos managerPos = calculateManagerPos();
        placeBlocks(managerPos);
        ManagerBlockEntity manager = setupManager(managerPos);
        labelBlocks(managerPos, manager);
        runPreConditions(manager);
        succeedIfManagerDidThingWithoutLagging(
                helper,
                manager,
                () -> runPostConditions(manager)
        );
        // todo: chaos
    }

    private @NotNull BlockPos calculateManagerPos() {
        return BoundingBox.encapsulatingPositions(
                        spec.blocks()
                                .stream()
                                .map(TestBlockDef::posRelativeToManager)
                                .toList()
                )
                .map(bounds -> {
                    int x = bounds.getXSpan();
                    int y = bounds.getYSpan();
                    int z = bounds.getZSpan();
                    return new BlockPos(x / 2, y / 2 + 2, z / 2);
                })
                .orElse(new BlockPos(0, 1, 0));
    }

    private void placeBlocks(@NotStored BlockPos managerPos) {
        for (TestBlockDef<?> blockDef : spec.blocks()) {
            placeBlock(blockDef, managerPos);
        }
    }

    private <T extends BlockEntity> void placeBlock(
            TestBlockDef<T> def,
            @NotStored BlockPos managerPos
    ) {
        BlockPos blockPos = managerPos.offset(def.posRelativeToManager());
        helper.setBlock(blockPos, def.block());
        if (def.blockEntityConfigurer() != null) {
            BlockEntity be = helper.getBlockEntity(blockPos);
            //noinspection unchecked
            Objects.requireNonNull(def.blockEntityConfigurer()).accept((T) be);
        }
    }

    private ManagerBlockEntity setupManager(@NotStored BlockPos managerPos) {
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        if (helper.getBlockEntity(managerPos) instanceof ManagerBlockEntity manager) {
            manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
            manager.setProgram(spec.program());
//            manager.setLogLevel(Level.DEBUG);
            return manager;
        } else {
            throw new GameTestAssertException("Manager block entity not found!");
        }
    }

    private void runPreConditions(ManagerBlockEntity manager) {
        runConditions(manager, "Pre-condition", spec.preConditions());
    }

    private void runPostConditions(ManagerBlockEntity manager) {
        runConditions(manager, "Post-condition", spec.postConditions());
    }

    private void runConditions(
            ManagerBlockEntity manager,
            String conditionKind,
            List<String> conditions
    ) {
        if (conditions.isEmpty()) return;
        List<BoolExpr> expressions = conditions.stream().map(this::getCondition).toList();
        ProgramContext programContext = new ProgramContext(
                new Program(new ASTBuilder(), "temp lol", List.of(), Set.of(), Set.of()),
                manager,
                DefaultProgramBehaviour::new
        );
        for (BoolExpr expr : expressions) {
            boolean passed = expr.test(programContext);
            if (!passed) {
                helper.fail(conditionKind + " " + conditionIndex + " failed: " + expr);
            }
            conditionIndex++;
        }
    }

    private BoolExpr getCondition(String line) {
        // This is where you’d parse lines like:
        //   “a BOTTOM SIDE HAS EQ 1000 fe::”
        // Or something like: “b BOTTOM SIDE HAS EQ 0 fe::”
        Mutable<BoolExpr> rtn = new MutableObject<>();
        String program = "EVERY 20 TICKS DO IF " + line + " THEN END END";
        Program.compile(
                program,
                success -> {
                    BoolExpr condition = (
                            (IfStatement) success
                                    .triggers()
                                    .get(0)
                                    .getBlock()
                                    .getStatements()
                                    .get(0)
                    ).condition();
                    rtn.setValue(condition);
                },
                failure -> {
                    StringBuilder msg = new StringBuilder("Failed to compile program: ").append(program);
                    msg.append('\n');
                    failure.forEach(e -> msg.append(e.toString()).append('\n'));
                    throw new IllegalStateException(msg.toString());
                }
        );
        return rtn.getValue();
    }

    private void labelBlocks(
            @NotStored BlockPos managerPos,
            ManagerBlockEntity manager
    ) {
        LabelPositionHolder labelHolder = LabelPositionHolder.empty();
        for (TestBlockDef<?> def : spec.blocks()) {
            // Actually add the label to the holder
            BlockPos absolutePos = helper.absolutePos(managerPos.offset(def.posRelativeToManager()));
            labelHolder.add(def.label(), absolutePos);
        }
        labelHolder.save(Objects.requireNonNull(manager.getDisk()));

    }
}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\compat\mekanism\SFMMekanismCompatGameTests.java

package ca.teamdman.sfm.gametest.compat.mekanism;

import ca.teamdman.sfm.SFM;
import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity;
import ca.teamdman.sfm.common.compat.SFMMekanismCompat;
import ca.teamdman.sfm.common.program.LabelPositionHolder;
import ca.teamdman.sfm.common.registry.SFMBlocks;
import ca.teamdman.sfm.common.registry.SFMItems;
import ca.teamdman.sfm.gametest.SFMGameTestBase;
import ca.teamdman.sfm.gametest.declarative.SFMDeclarativeTestBuilder;
import ca.teamdman.sfm.gametest.declarative.SFMTestSpec;
import ca.teamdman.sfm.gametest.declarative.TestBlockDef;
import mekanism.api.RelativeSide;
import mekanism.api.chemical.infuse.InfusionStack;
import mekanism.api.math.FloatingLong;
import mekanism.common.lib.transmitter.TransmissionType;
import mekanism.common.registries.MekanismBlocks;
import mekanism.common.registries.MekanismInfuseTypes;
import mekanism.common.tier.BinTier;
import mekanism.common.tier.ChemicalTankTier;
import mekanism.common.tier.EnergyCubeTier;
import mekanism.common.tile.TileEntityBin;
import mekanism.common.tile.TileEntityChemicalTank;
import mekanism.common.tile.TileEntityEnergyCube;
import mekanism.common.tile.component.config.DataType;
import mekanism.common.tile.multiblock.TileEntityInductionPort;
import mekanism.common.util.UnitDisplayUtils;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestAssertException;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.material.Fluids;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.gametest.GameTestHolder;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@SuppressWarnings({"DuplicatedCode", "DataFlowIssue"})
@GameTestHolder(SFM.MOD_ID)
public class SFMMekanismCompatGameTests extends SFMGameTestBase {
    @GameTest(template = "3x2x1")
    public static void mek_chemtank_infusion_empty(GameTestHelper helper) {
        // designate positions
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);

        // set up the world
        helper.setBlock(leftPos, MekanismBlocks.ULTIMATE_CHEMICAL_TANK.getBlock());
        var leftTank = ((TileEntityChemicalTank) helper.getBlockEntity(leftPos));
        helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_CHEMICAL_TANK.getBlock());
        var rightTank = ((TileEntityChemicalTank) helper.getBlockEntity(rightPos));
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var manager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));

        // set up the program
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   EVERY 20 TICKS DO
                                      INPUT infusion:*:* FROM a NORTH SIDE -- mek can extract from front by default
                                      OUTPUT infusion:*:* TO b TOP SIDE -- mek can insert to top by default
                                   END
                                   """.stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(manager.getDisk());


        // ensure it can move into an empty tank
        leftTank.getInfusionTank().setStack(new InfusionStack(MekanismInfuseTypes.REDSTONE.get(), 1_000_000L));
        rightTank.getInfusionTank().setStack(InfusionStack.EMPTY);
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftTank.getInfusionTank().getStack().isEmpty(), "Contents did not depart");
            assertTrue(rightTank.getInfusionTank().getStack().getAmount() == 1_000_000L, "Contents did not arrive");
        });
    }

    @GameTest(template = "3x2x1")
    public static void mek_chemtank_infusion_some(GameTestHelper helper) {
        // designate positions
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);

        // set up the world
        helper.setBlock(leftPos, MekanismBlocks.ULTIMATE_CHEMICAL_TANK.getBlock());
        var leftTank = ((TileEntityChemicalTank) helper.getBlockEntity(leftPos));
        helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_CHEMICAL_TANK.getBlock());
        var rightTank = ((TileEntityChemicalTank) helper.getBlockEntity(rightPos));
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var manager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));

        // set up the program
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   EVERY 20 TICKS DO
                                      INPUT infusion:*:* FROM a NORTH SIDE -- mek can extract from front by default
                                      OUTPUT infusion:*:* TO b TOP SIDE -- mek can insert to top by default
                                   END
                                   """.stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(manager.getDisk());


        // ensure it can move when there's already some in the destination
        leftTank.getInfusionTank().setStack(new InfusionStack(MekanismInfuseTypes.REDSTONE.get(), 1_000_000L));
        rightTank.getInfusionTank().setStack(new InfusionStack(MekanismInfuseTypes.REDSTONE.get(), 1_000_000L));
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftTank.getInfusionTank().getStack().isEmpty(), "Contents did not depart");
            assertTrue(rightTank.getInfusionTank().getStack().getAmount() == 2_000_000L, "Contents did not arrive");
        });
    }

    @GameTest(template = "3x2x1")
    public static void mek_chemtank_infusion_full(GameTestHelper helper) {
        // designate positions
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);

        // set up the world
        helper.setBlock(leftPos, MekanismBlocks.ULTIMATE_CHEMICAL_TANK.getBlock());
        var leftTank = ((TileEntityChemicalTank) helper.getBlockEntity(leftPos));
        helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_CHEMICAL_TANK.getBlock());
        var rightTank = ((TileEntityChemicalTank) helper.getBlockEntity(rightPos));
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var manager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));

        // set up the program
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   EVERY 20 TICKS DO
                                     INPUT infusion:*:* FROM a NORTH SIDE -- mek can extract from front by default
                                     OUTPUT infusion:*:* TO b TOP SIDE -- mek can insert to top by default
                                   END
                                   """.stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        // ensure it can move into a nearly full tank
        leftTank.getInfusionTank().setStack(new InfusionStack(MekanismInfuseTypes.REDSTONE.get(), 2_000_000L));
        rightTank
                .getInfusionTank()
                .setStack(new InfusionStack(
                        MekanismInfuseTypes.REDSTONE.get(),
                        ChemicalTankTier.ULTIMATE.getStorage() - 1_000_000L
                ));
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftTank.getInfusionTank().getStack().getAmount() == 1_000_000L, "Contents did not depart");
            assertTrue(
                    rightTank.getInfusionTank().getStack().getAmount() == ChemicalTankTier.ULTIMATE.getStorage(),
                    "Contents did not arrive"
            );
        });
    }


    @GameTest(template = "3x2x1")
    public static void mek_bin_empty(GameTestHelper helper) {
        // designate positions
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);

        // set up the world
        helper.setBlock(leftPos, MekanismBlocks.ULTIMATE_BIN.getBlock());
        var left = ((TileEntityBin) helper.getBlockEntity(leftPos));
        helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_BIN.getBlock());
        var right = ((TileEntityBin) helper.getBlockEntity(rightPos));
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var manager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));

        // set up the program
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   EVERY 20 TICKS DO
                                     INPUT FROM a NORTH SIDE
                                     OUTPUT TO b TOP SIDE
                                   END
                                   """.stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        left.getBinSlot().setStack(new ItemStack(Items.COAL, BinTier.ULTIMATE.getStorage()));
        right.getBinSlot().setEmpty();
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(left.getBinSlot().getCount() == BinTier.ULTIMATE.getStorage() - 64, "Contents did not depart");
            assertTrue(right.getBinSlot().getCount() == 64, "Contents did not arrive");
            assertTrue(right.getBinSlot().getStack().getItem() == Items.COAL, "Contents wrong type");

        });
    }


    @GameTest(template = "3x2x1")
    public static void mek_bin_some(GameTestHelper helper) {
        // designate positions
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);

        // set up the world
        helper.setBlock(leftPos, MekanismBlocks.ULTIMATE_BIN.getBlock());
        var left = ((TileEntityBin) helper.getBlockEntity(leftPos));
        helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_BIN.getBlock());
        var right = ((TileEntityBin) helper.getBlockEntity(rightPos));
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var manager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));

        // set up the program
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   EVERY 20 TICKS DO
                                     INPUT FROM a NORTH SIDE
                                     OUTPUT TO b TOP SIDE
                                   END
                                   """.stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        left.getBinSlot().setStack(new ItemStack(Items.DIAMOND, 100));
        right.getBinSlot().setStack(new ItemStack(Items.DIAMOND, 100));
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(left.getBinSlot().getCount() == 100 - 64, "Contents did not depart");
            assertTrue(right.getBinSlot().getCount() == 100 + 64, "Contents did not arrive");
            assertTrue(right.getBinSlot().getStack().getItem() == Items.DIAMOND, "Contents wrong type");

        });
    }


    @GameTest(template = "3x2x1")
    public static void mek_bin_full(GameTestHelper helper) {
        // designate positions
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);

        // set up the world
        helper.setBlock(leftPos, MekanismBlocks.ULTIMATE_BIN.getBlock());
        var left = ((TileEntityBin) helper.getBlockEntity(leftPos));
        helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_BIN.getBlock());
        var right = ((TileEntityBin) helper.getBlockEntity(rightPos));
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var manager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));

        // set up the program
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   EVERY 20 TICKS DO
                                     INPUT FROM a NORTH SIDE
                                     OUTPUT TO b TOP SIDE
                                   END
                                   """.stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        left.getBinSlot().setStack(new ItemStack(Items.STICK, BinTier.ULTIMATE.getStorage()));
        right.getBinSlot().setStack(new ItemStack(Items.STICK, BinTier.ULTIMATE.getStorage() - 32));
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(left.getBinSlot().getCount() == BinTier.ULTIMATE.getStorage() - 32, "Contents did not depart");
            assertTrue(right.getBinSlot().getCount() == BinTier.ULTIMATE.getStorage(), "Contents did not arrive");
            assertTrue(right.getBinSlot().getStack().getItem() == Items.STICK, "Contents wrong type");

        });
    }


    @GameTest(template = "3x2x1")
    public static void mek_energy_empty(GameTestHelper helper) {
        // designate positions
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);

        // set up the world
        helper.setBlock(leftPos, MekanismBlocks.ULTIMATE_ENERGY_CUBE.getBlock());
        var left = ((TileEntityEnergyCube) helper.getBlockEntity(leftPos));
        helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_ENERGY_CUBE.getBlock());
        var right = ((TileEntityEnergyCube) helper.getBlockEntity(rightPos));
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var manager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));

        // set up the program
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   EVERY 20 TICKS DO
                                     INPUT forge_energy:forge:energy FROM a NORTH SIDE
                                     OUTPUT forge_energy:forge:energy TO b TOP SIDE
                                   END
                                   """.stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        left.setEnergy(0, EnergyCubeTier.ULTIMATE.getMaxEnergy());
        right.setEnergy(0, FloatingLong.ZERO);
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(left.getEnergy(0).equals(FloatingLong.ZERO), "Contents did not depart");
            assertTrue(right.getEnergy(0).equals(EnergyCubeTier.ULTIMATE.getMaxEnergy()), "Contents did not arrive");

        });
    }


    @GameTest(template = "3x2x1")
    public static void mek_energy_some(GameTestHelper helper) {
        // designate positions
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);

        // set up the world
        helper.setBlock(leftPos, MekanismBlocks.ULTIMATE_ENERGY_CUBE.getBlock());
        var left = ((TileEntityEnergyCube) helper.getBlockEntity(leftPos));
        helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_ENERGY_CUBE.getBlock());
        var right = ((TileEntityEnergyCube) helper.getBlockEntity(rightPos));
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var manager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));

        // set up the program
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   EVERY 20 TICKS DO
                                     INPUT forge_energy:forge:energy FROM a NORTH SIDE
                                     OUTPUT forge_energy:forge:energy TO b TOP SIDE
                                   END
                                   """.stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        left.setEnergy(0, FloatingLong.create(1_000));
        right.setEnergy(0, FloatingLong.create(1_000));
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(left.getEnergy(0).equals(FloatingLong.ZERO), "Contents did not depart");
            assertTrue(right.getEnergy(0).equals(FloatingLong.create(2_000)), "Contents did not arrive");

        });
    }


    @GameTest(template = "3x2x1")
    public static void mek_energy_full(GameTestHelper helper) {
        // designate positions
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);

        // set up the world
        helper.setBlock(leftPos, MekanismBlocks.ULTIMATE_ENERGY_CUBE.getBlock());
        var left = ((TileEntityEnergyCube) helper.getBlockEntity(leftPos));
        helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_ENERGY_CUBE.getBlock());
        var right = ((TileEntityEnergyCube) helper.getBlockEntity(rightPos));
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var manager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));

        // set up the program
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   EVERY 20 TICKS DO
                                     INPUT forge_energy:forge:energy FROM a NORTH SIDE
                                     OUTPUT forge_energy:forge:energy TO b TOP SIDE
                                   END
                                   """.stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        left.setEnergy(0, EnergyCubeTier.ULTIMATE.getMaxEnergy());
        right.setEnergy(0, EnergyCubeTier.ULTIMATE.getMaxEnergy().subtract(1_000));
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(
                    left.getEnergy(0).equals(EnergyCubeTier.ULTIMATE.getMaxEnergy().subtract(1_000)),
                    "Contents did not depart"
            );
            assertTrue(right.getEnergy(0).equals(EnergyCubeTier.ULTIMATE.getMaxEnergy()), "Contents did not arrive");

        });
    }


    @GameTest(template = "3x2x1")
    public static void mek_energy_one(GameTestHelper helper) {
        // designate positions
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);

        // set up the world
        helper.setBlock(leftPos, MekanismBlocks.ULTIMATE_ENERGY_CUBE.getBlock());
        var left = ((TileEntityEnergyCube) helper.getBlockEntity(leftPos));
        helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_ENERGY_CUBE.getBlock());
        var right = ((TileEntityEnergyCube) helper.getBlockEntity(rightPos));
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var manager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));

        // set up the program
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   EVERY 20 TICKS DO
                                     INPUT 1 forge_energy:forge:energy FROM a NORTH SIDE
                                     OUTPUT forge_energy:forge:energy TO b TOP SIDE
                                   END
                                   """.stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        left.setEnergy(0, FloatingLong.create(100));
        right.setEnergy(0, FloatingLong.ZERO);
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(
                    left
                            .getEnergy(0)
                            .equals(FloatingLong
                                            .create(100)
                                            .subtract(UnitDisplayUtils.EnergyUnit.FORGE_ENERGY.convertFrom(1))),
                    "Contents did not depart"
            );
            assertTrue(
                    right.getEnergy(0).equals(UnitDisplayUtils.EnergyUnit.FORGE_ENERGY.convertFrom(1)),
                    "Contents did not arrive"
            );

        });
    }


    @GameTest(template = "25x3x25")
    public static void many_lava_cauldrons(GameTestHelper helper) {
        // designate positions
        var sourceBlocks = new ArrayList<BlockPos>();
        var destBlocks = new ArrayList<BlockPos>();
        var managerPos = new BlockPos(0, 2, 0);

        // set up cauldrons
        for (int x = 0; x < 25; x++) {
            for (int z = 1; z < 25; z++) {
                helper.setBlock(new BlockPos(x, 2, z), SFMBlocks.CABLE_BLOCK.get());
                helper.setBlock(new BlockPos(x, 3, z), Blocks.LAVA_CAULDRON);
                sourceBlocks.add(new BlockPos(x, 3, z));
            }
        }

        // set up tanks
        for (int i = 1; i < 25; i++) {
            BlockPos tankPos = new BlockPos(i, 2, 0);
            helper.setBlock(tankPos, MekanismBlocks.BASIC_FLUID_TANK.getBlock());
            destBlocks.add(tankPos);
        }

        // set up the manager
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // create the program
        var program = """
                    NAME "many inventory lag test"
                
                    EVERY 20 TICKS DO
                        INPUT fluid:*:* FROM source
                        OUTPUT fluid:*:* TO dest TOP SIDE
                    END
                """;

        // set the labels
        LabelPositionHolder.empty()
                .addAll("source", sourceBlocks.stream().map(helper::absolutePos).toList())
                .addAll("dest", destBlocks.stream().map(helper::absolutePos).toList())
                .save(manager.getDisk());

        // load the program
        manager.setProgram(program);
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            sourceBlocks.forEach(pos -> helper.assertBlock(
                    pos,
                    Blocks.CAULDRON::equals,
                    () -> "Cauldron did not empty"
            ));
            int found = destBlocks
                    .stream()
                    .map(helper::getBlockEntity)
                    .map(be -> be.getCapability(ForgeCapabilities.FLUID_HANDLER))
                    .map(x -> x.orElse(null))
                    .peek(Objects::requireNonNull)
                    .map(x -> x.getFluidInTank(0))
                    .mapToInt(FluidStack::getAmount)
                    .sum();
            assertTrue(found == 1000 * 25 * 24, "Not all fluids were moved (found " + found + ")");


        });
    }

    @GameTest(template = "3x4x3")
    public static void multi_fluid(GameTestHelper helper) {
        var a1Pos = new BlockPos(2, 2, 1);
        var a2Pos = new BlockPos(1, 2, 0);
        var b1Pos = new BlockPos(1, 2, 2);
        var b2Pos = new BlockPos(0, 2, 1);
        var managerPos = new BlockPos(1, 2, 1);
        helper.setBlock(a1Pos, MekanismBlocks.BASIC_FLUID_TANK.getBlock());
        helper.setBlock(a2Pos, MekanismBlocks.BASIC_FLUID_TANK.getBlock());
        helper.setBlock(b1Pos, MekanismBlocks.BASIC_FLUID_TANK.getBlock());
        helper.setBlock(b2Pos, MekanismBlocks.BASIC_FLUID_TANK.getBlock());
        var a1 = helper
                .getBlockEntity(a1Pos)
                .getCapability(ForgeCapabilities.FLUID_HANDLER, Direction.NORTH)
                .orElse(null);
        var a2 = helper
                .getBlockEntity(a2Pos)
                .getCapability(ForgeCapabilities.FLUID_HANDLER, Direction.NORTH)
                .orElse(null);
        var b1 = helper
                .getBlockEntity(b1Pos)
                .getCapability(ForgeCapabilities.FLUID_HANDLER, Direction.NORTH)
                .orElse(null);
        var b2 = helper
                .getBlockEntity(b2Pos)
                .getCapability(ForgeCapabilities.FLUID_HANDLER, Direction.NORTH)
                .orElse(null);

        a1.fill(new FluidStack(Fluids.WATER, 3000), IFluidHandler.FluidAction.EXECUTE);
        a2.fill(new FluidStack(Fluids.LAVA, 3000), IFluidHandler.FluidAction.EXECUTE);

        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var manager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   EVERY 20 TICKS DO
                                     INPUT fluid:: FROM a NORTH SIDE
                                     OUTPUT fluid::lava, fluid::water TO b TOP SIDE
                                   END
                                   """.stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(a1Pos))
                .add("a", helper.absolutePos(a2Pos))
                .add("b", helper.absolutePos(b1Pos))
                .add("b", helper.absolutePos(b2Pos))
                .save(manager.getDisk());

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(a1.getFluidInTank(0).isEmpty(), "a1 did not empty");
            assertTrue(a2.getFluidInTank(0).isEmpty(), "a2 did not empty");
            assertTrue(b1.getFluidInTank(0).getFluid() == Fluids.WATER, "b1 did not fill with water");
            assertTrue(b2.getFluidInTank(0).getFluid() == Fluids.LAVA, "b2 did not fill with lava");
        });
    }


    @GameTest(template = "25x3x25")
    public static void mek_induction(GameTestHelper helper) {
        // designate positions
        var managerPos = new BlockPos(1, 3, 0);
        var powerCubePos = new BlockPos(1, 2, 0);
        var inductionBeginPos = new BlockPos(0, 2, 1);
        var inductionInput = new BlockPos(1, 3, 1);

        // set up induction matrix
        for (int x = 0; x < 18; x++) {
            for (int z = 0; z < 18; z++) {
                for (int y = 0; y < 18; y++) {
                    //noinspection ExtractMethodRecommender
                    boolean isOutside = x == 0 || x == 17 || z == 0 || z == 17 || y == 0 || y == 17;
                    Block block;
                    if (isOutside) {
                        block = MekanismBlocks.INDUCTION_CASING.getBlock();
                    } else {
                        if (y == 1) {
                            block = MekanismBlocks.ULTIMATE_INDUCTION_CELL.getBlock();
                        } else {
                            block = MekanismBlocks.ULTIMATE_INDUCTION_PROVIDER.getBlock();
                        }
                    }
                    helper.setBlock(inductionBeginPos.offset(x, y, z), block);
                }
            }
        }
        helper.setBlock(inductionInput, MekanismBlocks.INDUCTION_PORT.getBlock());
        var inductionPort = (TileEntityInductionPort) helper.getBlockEntity(inductionInput);

        // set up the energy source
        helper.setBlock(powerCubePos, MekanismBlocks.CREATIVE_ENERGY_CUBE.getBlock());

        TileEntityEnergyCube powerCube = (TileEntityEnergyCube) helper.getBlockEntity(powerCubePos);
        powerCube.setEnergy(0, EnergyCubeTier.CREATIVE.getMaxEnergy());

        // set up the manager
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // create the program
        long incr = 10_000_000_000L;
        var startingAmount = FloatingLong.create(0L);
        var program = """
                    NAME "induction matrix test"
                    EVERY 20 TICKS DO
                        INPUT %d mekanism_energy:: FROM source NORTH SIDE
                        OUTPUT mekanism_energy:: TO dest NORTH SIDE
                    END
                """.formatted(incr);

        // set the labels
        LabelPositionHolder.empty()
                .addAll("source", List.of(helper.absolutePos(powerCubePos)))
                .addAll("dest", List.of(helper.absolutePos(inductionInput)))
                .save(manager.getDisk());

        // we can't prefill since we can't wait a delay AND use succeedIfManagerDidThing
        // pre-fill the matrix by a little bit
        // we want to make sure SFM doesn't have problems inserting beyond MAX_INT
//        var startingAmount = FloatingLong.create(Integer.MAX_VALUE + incr);
//            inductionPort.insertEnergy(startingAmount, Action.EXECUTE);

        // launch the program
        manager.setProgram(program);
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            if (!inductionPort.getMultiblock().isFormed()) {
                throw new GameTestAssertException("Induction matrix did not form");
            }

            var expected = startingAmount.add(incr);
            FloatingLong energy = inductionPort.getEnergy(0);
            boolean success = energy.equals(expected);
            assertTrue(
                    success,
                    "Expected energy did not match"
            );

        });
    }

    @GameTest(template = "3x2x1")
    public static void mek_cube(GameTestHelper helper) {
        SFMTestSpec spec = new SFMTestSpec()
                .setProgram("""
                                        EVERY 20 TICKS DO
                                            INPUT fe:: FROM a BOTTOM SIDE
                                            OUTPUT fe:: TO b TOP SIDE
                                        END
                                    """)
                .addBlock(TestBlockDef.<TileEntityEnergyCube>of(
                        "a",
                        new BlockPos(1, 0, 0),
                        mekanism.common.registries.MekanismBlocks.BASIC_ENERGY_CUBE.getBlock(),
                        (tileEntityCube) -> {
                            tileEntityCube.setEnergy(0, SFMMekanismCompat.createForgeEnergy(1000));
                            SFMMekanismCompat.configureExclusiveIO(
                                    tileEntityCube,
                                    TransmissionType.ENERGY,
                                    RelativeSide.BOTTOM,
                                    DataType.OUTPUT
                            );
                        }
                ))
                .addBlock(TestBlockDef.<TileEntityEnergyCube>of(
                        "b",
                        new BlockPos(-1, 0, 0),
                        mekanism.common.registries.MekanismBlocks.BASIC_ENERGY_CUBE.getBlock(),
                        (tileEntityCube) -> SFMMekanismCompat.configureExclusiveIO(
                                tileEntityCube,
                                TransmissionType.ENERGY,
                                RelativeSide.TOP,
                                DataType.INPUT
                        )
                ))
                .preCondition("ONE a HAS EQ 1000 fe::")
                .preCondition("ONE b HAS EQ 0 fe::")
                .postCondition("ONE a HAS EQ 0 fe::")
                .postCondition("ONE b HAS EQ 1000 fe::");
        new SFMDeclarativeTestBuilder(helper, spec).run();
    }

}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\SFMWithGameTests.java

package ca.teamdman.sfm.gametest;

import ca.teamdman.sfm.SFM;
import ca.teamdman.sfm.common.registry.SFMResourceTypes;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraftforge.gametest.GameTestHolder;

import java.util.Arrays;
import java.util.function.BiConsumer;

@SuppressWarnings({
        "RedundantSuppression",
        "DataFlowIssue",
        "deprecation",
        "OptionalGetWithoutIsPresent",
        "DuplicatedCode",
        "ArraysAsListWithZeroOrOneArgument"
})
@GameTestHolder(SFM.MOD_ID)
public class SFMWithGameTests extends SFMGameTestBase {
    /// Some tests assume that some items have certain tags.
    ///
    /// To avoid problems between versions, we will validate those assumptions here.
    @GameTest(template = "1x1x1")
    public static void validate_tags(GameTestHelper helper) {
        BiConsumer<Item, String> assertTag = (item, findTag) -> {
            boolean hasTag = SFMResourceTypes.ITEM
                    .get()
                    .getTagsForStack(new ItemStack(item))
                    .anyMatch(tag -> tag.toString().equals(findTag) || !findTag.contains(":") && tag
                            .getPath()
                            .equals(findTag));
            assertTrue(hasTag, "Item " + item + " should have tag " + findTag);
        };

        // Assert mineable tags
        assertTag.accept(Items.DIRT, "minecraft:mineable/shovel");
        assertTag.accept(Items.STONE, "minecraft:mineable/pickaxe");
        assertTag.accept(Items.OAK_PLANKS, "minecraft:planks");
        assertTag.accept(Items.OAK_PLANKS, "minecraft:mineable/axe");
        assertTag.accept(Items.IRON_INGOT, "ingots");
        assertTag.accept(Items.GOLD_INGOT, "ingots");
        assertTag.accept(Items.GOLD_NUGGET, "nuggets");
        assertTag.accept(Items.CHEST, "chests");
        helper.succeed();
    }

    @GameTest(template = "3x2x1")
    public static void move_with_tag_mineable(GameTestHelper helper) {
        new LeftRightManagerTest(helper)
                .setProgram("""
                                    EVERY 20 TICKS DO
                                        INPUT WITH TAG minecraft:mineable/shovel FROM left
                                        OUTPUT TO right
                                    END
                                    """)
                .preContents("left", Arrays.asList(
                        enchant(new ItemStack(Items.DIRT, 64), Enchantments.SHARPNESS, 100), // Slot 0
                        new ItemStack(Items.DIRT, 64),                                       // Slot 1
                        new ItemStack(Items.STONE, 64)                                       // Slot 2
                ))
                .postContents("left", Arrays.asList(
                        ItemStack.EMPTY,                    // Slot 0 (Dirt moved)
                        ItemStack.EMPTY,                    // Slot 1 (Dirt moved)
                        new ItemStack(Items.STONE, 64)      // Slot 2 (Stone remains)
                ))
                .postContents("right", Arrays.asList(
                        enchant(new ItemStack(Items.DIRT, 64), Enchantments.SHARPNESS, 100), // Slot 0
                        new ItemStack(Items.DIRT, 64)                                        // Slot 1
                        // The rest are empty by default
                ))
                .run();
    }

    @GameTest(template = "3x2x1")
    public static void move_with_tag_ingots(GameTestHelper helper) {
        new LeftRightManagerTest(helper)
                .setProgram("""
                                    EVERY 20 TICKS DO
                                        INPUT WITH TAG ingots FROM left
                                        OUTPUT TO right
                                    END
                                    """)
                .preContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),
                        new ItemStack(Items.DIRT, 64),
                        new ItemStack(Items.IRON_INGOT, 64),
                        new ItemStack(Items.GOLD_INGOT, 64),
                        new ItemStack(Items.GOLD_NUGGET, 64),
                        new ItemStack(Items.CHEST, 64)
                ))
                .postContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),
                        new ItemStack(Items.DIRT, 64),
                        ItemStack.EMPTY,
                        ItemStack.EMPTY,
                        new ItemStack(Items.GOLD_NUGGET, 64),
                        new ItemStack(Items.CHEST, 64)
                ))
                .postContents("right", Arrays.asList(
                        new ItemStack(Items.IRON_INGOT, 64),
                        new ItemStack(Items.GOLD_INGOT, 64)
                ))
                .run();
    }

    @GameTest(template = "3x2x1")
    public static void move_with_tag_disjunction(GameTestHelper helper) {
        new LeftRightManagerTest(helper)
                .setProgram("""
                                    EVERY 20 TICKS DO
                                        INPUT WITH (TAG minecraft:mineable/shovel OR TAG minecraft:mineable/pickaxe) FROM left
                                        OUTPUT TO right
                                    END
                                    """)
                .preContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),       // Slot 0
                        new ItemStack(Items.STONE, 64),      // Slot 1
                        new ItemStack(Items.OAK_PLANKS, 64)  // Slot 2
                ))
                .postContents("left", Arrays.asList(
                        ItemStack.EMPTY,                    // Slot 0 (Dirt moved)
                        ItemStack.EMPTY,                    // Slot 1 (Stone moved)
                        new ItemStack(Items.OAK_PLANKS, 64) // Slot 2 (Planks remain)
                ))
                .postContents("right", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),      // Slot 0
                        new ItemStack(Items.STONE, 64)      // Slot 1
                        // The rest are empty
                ))
                .run();
    }

    @GameTest(template = "3x2x1")
    public static void move_with_tag_negation(GameTestHelper helper) {
        new LeftRightManagerTest(helper)
                .setProgram("""
                                    EVERY 20 TICKS DO
                                        INPUT WITH NOT TAG minecraft:mineable/pickaxe FROM left
                                        OUTPUT TO right
                                    END
                                    """)
                .preContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),       // Slot 0
                        new ItemStack(Items.STONE, 64),      // Slot 1
                        new ItemStack(Items.OAK_PLANKS, 64)  // Slot 2
                ))
                .postContents("left", Arrays.asList(
                        ItemStack.EMPTY,                    // Slot 0 (Dirt moved)
                        new ItemStack(Items.STONE, 64),     // Slot 1 (Stone remains)
                        ItemStack.EMPTY                     // Slot 2 (Planks moved)
                ))
                .postContents("right", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),      // Slot 0
                        new ItemStack(Items.OAK_PLANKS, 64) // Slot 1
                ))
                .run();
    }

    @GameTest(template = "3x2x1")
    public static void move_with_tag_conjunction(GameTestHelper helper) {
        new LeftRightManagerTest(helper)
                .setProgram("""
                                    EVERY 20 TICKS DO
                                        INPUT WITH TAG minecraft:planks AND TAG minecraft:mineable/axe FROM left
                                        OUTPUT TO right
                                    END
                                    """)
                .preContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),      // Slot 0
                        new ItemStack(Items.STONE, 64),     // Slot 1
                        new ItemStack(Items.OAK_PLANKS, 64) // Slot 2
                ))
                .postContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),     // Slot 0 (Dirt remains)
                        new ItemStack(Items.STONE, 64),    // Slot 1 (Stone remains)
                        ItemStack.EMPTY                    // Slot 2 (Planks moved)
                ))
                .postContents("right", Arrays.asList(
                        new ItemStack(Items.OAK_PLANKS, 64) // Slot 0
                ))
                .run();
    }

    @GameTest(template = "3x2x1")
    public static void move_with_complex_withClause(GameTestHelper helper) {
        new LeftRightManagerTest(helper)
                .setProgram("""
                                    EVERY 20 TICKS DO
                                        INPUT WITH (TAG minecraft:planks OR (TAG minecraft:mineable/shovel AND NOT TAG minecraft:mineable/axe)) FROM left
                                        OUTPUT TO right
                                    END
                                    """)
                .preContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),      // Slot 0
                        new ItemStack(Items.STONE, 64),     // Slot 1
                        new ItemStack(Items.OAK_PLANKS, 64) // Slot 2
                ))
                .postContents("left", Arrays.asList(
                        ItemStack.EMPTY,                   // Slot 0 (Dirt moved)
                        new ItemStack(Items.STONE, 64),    // Slot 1 (Stone remains)
                        ItemStack.EMPTY                    // Slot 2 (Planks moved)
                ))
                .postContents("right", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),      // Slot 0
                        new ItemStack(Items.OAK_PLANKS, 64) // Slot 1
                ))
                .run();
    }

    @GameTest(template = "3x2x1")
    public static void move_with_nested_withClause(GameTestHelper helper) {
        new LeftRightManagerTest(helper)
                .setProgram("""
                                    EVERY 20 TICKS DO
                                        INPUT WITH ((TAG minecraft:mineable/shovel AND NOT TAG minecraft:mineable/pickaxe) OR (TAG minecraft:planks AND TAG minecraft:mineable/axe)) FROM left
                                        OUTPUT TO right
                                    END
                                    """)
                .preContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),      // Slot 0
                        new ItemStack(Items.STONE, 64),     // Slot 1
                        new ItemStack(Items.OAK_PLANKS, 64) // Slot 2
                ))
                .postContents("left", Arrays.asList(
                        ItemStack.EMPTY,                   // Slot 0 (Dirt moved)
                        new ItemStack(Items.STONE, 64),    // Slot 1 (Stone remains)
                        ItemStack.EMPTY                    // Slot 2 (Planks moved)
                ))
                .postContents("right", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),      // Slot 0
                        new ItemStack(Items.OAK_PLANKS, 64) // Slot 1
                ))
                .run();
    }

    @GameTest(template = "3x2x1")
    public static void move_with_not_and_or_combination(GameTestHelper helper) {
        new LeftRightManagerTest(helper)
                .setProgram("""
                                    EVERY 20 TICKS DO
                                        INPUT WITH NOT TAG minecraft:mineable/shovel AND TAG minecraft:mineable/axe OR TAG minecraft:planks FROM left
                                        OUTPUT TO right
                                    END
                                    """)
                .preContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),      // Slot 0
                        new ItemStack(Items.OAK_PLANKS, 64),// Slot 1
                        new ItemStack(Items.STONE, 64)      // Slot 2
                ))
                .postContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),     // Slot 0 (Dirt remains)
                        ItemStack.EMPTY,                   // Slot 1 (Planks moved)
                        new ItemStack(Items.STONE, 64)     // Slot 2 (Stone remains)
                ))
                .postContents("right", Arrays.asList(
                        new ItemStack(Items.OAK_PLANKS, 64) // Slot 0
                ))
                .run();
    }

    @GameTest(template = "3x2x1")
    public static void move_without_tag_mineable(GameTestHelper helper) {
        new LeftRightManagerTest(helper)
                .setProgram("""
                                    EVERY 20 TICKS DO
                                        INPUT WITHOUT TAG minecraft:mineable/pickaxe FROM left
                                        OUTPUT TO right
                                    END
                                    """)
                .preContents("left", Arrays.asList(
                        enchant(new ItemStack(Items.DIRT, 64), Enchantments.SHARPNESS, 100),
                        new ItemStack(Items.DIRT, 64),
                        new ItemStack(Items.STONE, 64)
                ))
                .postContents("left", Arrays.asList(
                        ItemStack.EMPTY,
                        ItemStack.EMPTY,
                        new ItemStack(Items.STONE, 64)
                ))
                .postContents("right", Arrays.asList(
                        enchant(new ItemStack(Items.DIRT, 64), Enchantments.SHARPNESS, 100),
                        new ItemStack(Items.DIRT, 64)
                ))
                .run();
    }

    @GameTest(template = "3x2x1")
    public static void move_without_tag_ingots(GameTestHelper helper) {
        new LeftRightManagerTest(helper)
                .setProgram("""
                                    EVERY 20 TICKS DO
                                        INPUT WITHOUT NOT TAG ingots FROM left
                                        OUTPUT TO right
                                    END
                                    """)
                .preContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),
                        new ItemStack(Items.DIRT, 64),
                        new ItemStack(Items.IRON_INGOT, 64),
                        new ItemStack(Items.GOLD_INGOT, 64),
                        new ItemStack(Items.GOLD_NUGGET, 64),
                        new ItemStack(Items.CHEST, 64)
                ))
                .postContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),
                        new ItemStack(Items.DIRT, 64),
                        ItemStack.EMPTY,
                        ItemStack.EMPTY,
                        new ItemStack(Items.GOLD_NUGGET, 64),
                        new ItemStack(Items.CHEST, 64)
                ))
                .postContents("right", Arrays.asList(
                        new ItemStack(Items.IRON_INGOT, 64),
                        new ItemStack(Items.GOLD_INGOT, 64)
                ))
                .run();
    }

    @GameTest(template = "3x2x1")
    public static void move_without_tag_disjunction(GameTestHelper helper) {
        new LeftRightManagerTest(helper)
                .setProgram("""
                                    EVERY 20 TICKS DO
                                        INPUT WITHOUT (NOT TAG minecraft:mineable/shovel AND NOT TAG minecraft:mineable/pickaxe) FROM left
                                        OUTPUT TO right
                                    END
                                    """)
                .preContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),
                        new ItemStack(Items.STONE, 64),
                        new ItemStack(Items.OAK_PLANKS, 64)
                ))
                .postContents("left", Arrays.asList(
                        ItemStack.EMPTY,
                        ItemStack.EMPTY,
                        new ItemStack(Items.OAK_PLANKS, 64)
                ))
                .postContents("right", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),
                        new ItemStack(Items.STONE, 64)
                ))
                .run();
    }

    @GameTest(template = "3x2x1")
    public static void move_without_tag_negation(GameTestHelper helper) {
        new LeftRightManagerTest(helper)
                .setProgram("""
                                    EVERY 20 TICKS DO
                                        INPUT WITHOUT TAG minecraft:mineable/pickaxe FROM left
                                        OUTPUT TO right
                                    END
                                    """)
                .preContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),
                        new ItemStack(Items.STONE, 64),
                        new ItemStack(Items.OAK_PLANKS, 64)
                ))
                .postContents("left", Arrays.asList(
                        ItemStack.EMPTY,
                        new ItemStack(Items.STONE, 64),
                        ItemStack.EMPTY
                ))
                .postContents("right", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),
                        new ItemStack(Items.OAK_PLANKS, 64)
                ))
                .run();
    }

    @GameTest(template = "3x2x1")
    public static void move_without_tag_conjunction(GameTestHelper helper) {
        new LeftRightManagerTest(helper)
                .setProgram("""
                                    EVERY 20 TICKS DO
                                        INPUT WITHOUT (NOT TAG minecraft:planks OR NOT TAG minecraft:mineable/axe) FROM left
                                        OUTPUT TO right
                                    END
                                    """)
                .preContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),
                        new ItemStack(Items.STONE, 64),
                        new ItemStack(Items.OAK_PLANKS, 64)
                ))
                .postContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),
                        new ItemStack(Items.STONE, 64),
                        ItemStack.EMPTY
                ))
                .postContents("right", Arrays.asList(
                        new ItemStack(Items.OAK_PLANKS, 64)
                ))
                .run();
    }

    @GameTest(template = "3x2x1")
    public static void move_without_complex_withClause(GameTestHelper helper) {
        new LeftRightManagerTest(helper)
                .setProgram("""
                                    EVERY 20 TICKS DO
                                        INPUT WITHOUT ((NOT TAG minecraft:planks) AND (NOT TAG minecraft:mineable/shovel OR TAG minecraft:mineable/axe)) FROM left
                                        OUTPUT TO right
                                    END
                                    """)
                .preContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),
                        new ItemStack(Items.STONE, 64),
                        new ItemStack(Items.OAK_PLANKS, 64)
                ))
                .postContents("left", Arrays.asList(
                        ItemStack.EMPTY,
                        new ItemStack(Items.STONE, 64),
                        ItemStack.EMPTY
                ))
                .postContents("right", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),
                        new ItemStack(Items.OAK_PLANKS, 64)
                ))
                .run();
    }

    @GameTest(template = "3x2x1")
    public static void move_without_nested_withClause(GameTestHelper helper) {
        new LeftRightManagerTest(helper)
                .setProgram("""
                                    EVERY 20 TICKS DO
                                        INPUT WITHOUT (((NOT TAG minecraft:mineable/shovel) OR TAG minecraft:mineable/pickaxe) AND ((NOT TAG minecraft:planks) OR (NOT TAG minecraft:mineable/axe))) FROM left
                                        OUTPUT TO right
                                    END
                                    """)
                .preContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),
                        new ItemStack(Items.STONE, 64),
                        new ItemStack(Items.OAK_PLANKS, 64)
                ))
                .postContents("left", Arrays.asList(
                        ItemStack.EMPTY,
                        new ItemStack(Items.STONE, 64),
                        ItemStack.EMPTY
                ))
                .postContents("right", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),
                        new ItemStack(Items.OAK_PLANKS, 64)
                ))
                .run();
    }

    @GameTest(template = "3x2x1")
    public static void move_without_not_and_or_combination(GameTestHelper helper) {
        new LeftRightManagerTest(helper)
                .setProgram("""
                                    EVERY 20 TICKS DO
                                        INPUT WITHOUT ((TAG minecraft:mineable/shovel OR NOT TAG minecraft:mineable/axe) AND (NOT TAG minecraft:planks)) FROM left
                                        OUTPUT TO right
                                    END
                                    """)
                .preContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),
                        new ItemStack(Items.OAK_PLANKS, 64),
                        new ItemStack(Items.STONE, 64)
                ))
                .postContents("left", Arrays.asList(
                        new ItemStack(Items.DIRT, 64),
                        ItemStack.EMPTY,
                        new ItemStack(Items.STONE, 64)
                ))
                .postContents("right", Arrays.asList(
                        new ItemStack(Items.OAK_PLANKS, 64)
                ))
                .run();
    }

    @GameTest(template = "3x2x1")
    public static void move_one_each_with_tag_mineable_axe(GameTestHelper helper) {
        new LeftRightManagerTest(helper)
                .setProgram("""
                EVERY 20 TICKS DO
                    INPUT FROM left
                    OUTPUT 1 EACH WITH TAG minecraft:mineable/axe TO right
                END
                """)
                .preContents("left", Arrays.asList(
                        new ItemStack(Items.OAK_LOG, 64),
                        new ItemStack(Items.BIRCH_LOG, 64),
                        new ItemStack(Items.SPRUCE_LOG, 64)
                ))
                .postContents("left", Arrays.asList(
                        new ItemStack(Items.OAK_LOG, 63),
                        new ItemStack(Items.BIRCH_LOG, 63),
                        new ItemStack(Items.SPRUCE_LOG, 63)
                ))
                .postContents("right", Arrays.asList(
                        new ItemStack(Items.OAK_LOG, 1),
                        new ItemStack(Items.BIRCH_LOG, 1),
                        new ItemStack(Items.SPRUCE_LOG, 1)
                ))
                .run();
    }

}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\LeftRightManagerTest.java

package ca.teamdman.sfm.gametest;

import ca.teamdman.sfm.common.util.Stored;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTestHelper;

public class LeftRightManagerTest extends SFMTestBuilder {

    public LeftRightManagerTest(GameTestHelper helper) {
        super(helper);
    }

    @Override
    protected void setupStructure(@Stored BlockPos offset) {
        setupChests(offset);
        setupManager(offset);
    }

    protected void setupChests(@Stored BlockPos offset) {
        addChest("left", new BlockPos(2, 2, 0).offset(offset));
        addChest("right", new BlockPos(0, 2, 0).offset(offset));
    }
}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\SFMCorrectnessGameTests.java

package ca.teamdman.sfm.gametest;

import ca.teamdman.sfm.SFM;
import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity;
import ca.teamdman.sfm.common.blockentity.PrintingPressBlockEntity;
import ca.teamdman.sfm.common.cablenetwork.CableNetwork;
import ca.teamdman.sfm.common.cablenetwork.CableNetworkManager;
import ca.teamdman.sfm.common.config.SFMConfig;
import ca.teamdman.sfm.common.config.SFMServerConfig.LevelsToShards;
import ca.teamdman.sfm.common.item.DiskItem;
import ca.teamdman.sfm.common.item.FormItem;
import ca.teamdman.sfm.common.localization.LocalizationKeys;
import ca.teamdman.sfm.common.program.LabelPositionHolder;
import ca.teamdman.sfm.common.program.ProgramContext;
import ca.teamdman.sfm.common.program.linting.GatherWarningsProgramBehaviour;
import ca.teamdman.sfm.common.registry.SFMBlocks;
import ca.teamdman.sfm.common.registry.SFMItems;
import ca.teamdman.sfm.common.util.SFMDirections;
import ca.teamdman.sfm.common.util.SFMItemUtils;
import it.unimi.dsi.fastutil.Pair;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestAssertException;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.Container;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.*;
import net.minecraft.world.item.enchantment.EnchantmentInstance;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.DirectionalBlock;
import net.minecraft.world.level.block.LayeredCauldronBlock;
import net.minecraft.world.level.block.entity.BarrelBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.gametest.GameTestHolder;
import net.minecraftforge.items.wrapper.InvWrapper;
import org.apache.logging.log4j.Level;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.stream.IntStream;

// https://github.dev/CompactMods/CompactMachines
// https://github.com/SocketMods/BaseDefense/blob/3b3cb4af26f4553c3438417cbb95f0d3fb707751/build.gradle#L74
// https://github.com/sinkillerj/ProjectE/blob/mc1.16.x/build.gradle#L54
// https://github.com/mekanism/Mekanism/blob/1.16.x/build.gradle
// https://github.com/TwistedGate/ImmersivePetroleum/blob/1.16.5/build.gradle#L107
// https://github.com/MinecraftForge/MinecraftForge/blob/d7b137d1446377bfd1958f8a0e24f63819b81bfc/src/test/java/net/minecraftforge/debug/misc/GameTestTest.java#L155
// https://docs.minecraftforge.net/en/1.19.x/misc/gametest/
// https://github.com/MinecraftForge/MinecraftForge/blob/1.19.x/src/test/java/net/minecraftforge/debug/misc/GameTestTest.java#LL101-L116C6
// https://github.com/XFactHD/FramedBlocks/blob/1.19.4/src/main/java/xfacthd/framedblocks/api/test/TestUtils.java#L65-L87
@SuppressWarnings({"DataFlowIssue", "deprecation", "OptionalGetWithoutIsPresent"})
@GameTestHolder(SFM.MOD_ID)
public class SFMCorrectnessGameTests extends SFMGameTestBase {
    /**
     * Ensure that the manager state gets updated as the disk is inserted and the program is set
     */
    @GameTest(template = "1x2x1")
    public static void manager_state_update(GameTestHelper helper) {
        helper.setBlock(new BlockPos(0, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(0, 2, 0));
        assertTrue(manager.getState() == ManagerBlockEntity.State.NO_DISK, "Manager did not start with no disk");
        assertTrue(manager.getDisk() == null, "Manager did not start with no disk");
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        assertTrue(manager.getState() == ManagerBlockEntity.State.NO_PROGRAM, "Disk did not start with no program");
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM a
                                           OUTPUT TO b
                                       END
                                   """.stripTrailing().stripIndent());
        assertManagerRunning(manager);
        helper.succeed();
    }

    @GameTest(template = "3x2x1")
    public static void move_1_stack(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Blocks.DIRT, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM a
                                           OUTPUT TO b
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftChest.getStackInSlot(0).isEmpty(), "Dirt did not move");
            assertTrue(rightChest.getStackInSlot(0).getCount() == 64, "Dirt did not move");
        });
    }

    @GameTest(template = "3x2x1")
    public static void move_full_chest(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());


        var leftChest = getItemHandler(helper, leftPos);

        var rightChest = getItemHandler(helper, rightPos);

        for (int i = 0; i < leftChest.getSlots(); i++) {
            leftChest.insertItem(i, new ItemStack(Blocks.DIRT, 64), false);
        }

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM a
                                           OUTPUT TO b
                                       END
                                   """.stripTrailing().stripIndent());
        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(
                    IntStream.range(0, leftChest.getSlots()).allMatch(slot -> leftChest.getStackInSlot(slot).isEmpty()),
                    "Dirt did not leave"
            );
            int count = rightChest.getSlots() * 64;
            int total = 0;
            for (int i = 0; i < rightChest.getSlots(); i++) {
                ItemStack x = rightChest.getStackInSlot(i);
                if (x.is(Items.DIRT)) {
                    total += rightChest.getStackInSlot(i).getCount();
                }
            }
            assertTrue(total == count, "Dirt did not arrive");
        });
    }


    @GameTest(template = "3x4x3")
    public static void many_outputs(GameTestHelper helper) {
        BlockPos managerPos = new BlockPos(1, 2, 1);
        BlockPos sourcePos = new BlockPos(1, 3, 1);
        BlockPos dest1Pos = new BlockPos(2, 2, 1);
        BlockPos dest2Pos = new BlockPos(0, 2, 1);

        // set up inventories
        helper.setBlock(sourcePos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(dest1Pos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(dest2Pos, SFMBlocks.TEST_BARREL_BLOCK.get());


        var sourceInv = getItemHandler(helper, sourcePos);

        var dest1Inv = getItemHandler(helper, dest1Pos);

        var dest2Inv = getItemHandler(helper, dest2Pos);

        for (int i = 0; i < sourceInv.getSlots(); i++) {
            sourceInv.insertItem(i, new ItemStack(Blocks.DIRT, 64), false);
        }

        // set up manager
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM source
                                           OUTPUT 64 dirt TO EACH dest
                                       END
                                   """.stripTrailing().stripIndent());
        // set the labels
        LabelPositionHolder.empty()
                .add("source", helper.absolutePos(sourcePos))
                .add("dest", helper.absolutePos(dest1Pos))
                .add("dest", helper.absolutePos(dest2Pos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            int found = IntStream
                    .range(0, sourceInv.getSlots())
                    .mapToObj(sourceInv::getStackInSlot)
                    .mapToInt(ItemStack::getCount)
                    .sum();
            assertTrue(found == 64 * (sourceInv.getSlots() - 2), "Dirt did not leave (found " + found + " (" + (
                    found > 64 ? found / 64 + "x stacks + " + found % 64 : found
            ) + " dirt))");
            int total;
            total = 0;
            for (int i = 0; i < dest1Inv.getSlots(); i++) {
                ItemStack x = dest1Inv.getStackInSlot(i);
                if (x.is(Items.DIRT)) {
                    total += dest1Inv.getStackInSlot(i).getCount();
                }
            }
            assertTrue(total == 64, "Dirt did not arrive properly 1");
            total = 0;
            for (int i = 0; i < dest2Inv.getSlots(); i++) {
                ItemStack x = dest2Inv.getStackInSlot(i);
                if (x.is(Items.DIRT)) {
                    total += dest2Inv.getStackInSlot(i).getCount();
                }
            }
            assertTrue(total == 64, "Dirt did not arrive properly 2");
        });
    }

    @GameTest(template = "3x2x1")
    public static void retain_5(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Blocks.DIRT, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   EVERY 20 TICKS DO
                                      INPUT RETAIN 5 FROM a
                                      OUTPUT TO b
                                   END
                                   """.stripTrailing().stripIndent());
        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftChest.getStackInSlot(0).getCount() == 5, "Dirt did not move");
            assertTrue(rightChest.getStackInSlot(0).getCount() == 64 - 5, "Dirt did not move");
        });
    }

    @GameTest(template = "3x2x1")
    public static void move_multiple_item_names(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var leftChest = getItemHandler(helper, leftPos);
        var rightChest = getItemHandler(helper, rightPos);

        leftChest.insertItem(0, new ItemStack(Items.IRON_INGOT, 64), false);
        leftChest.insertItem(1, new ItemStack(Items.STONE, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT
                                               RETAIN 5 iron_ingot,
                                               RETAIN 3 stone
                                           FROM a TOP SIDE
                                   
                                           OUTPUT
                                               2 iron_ingot,
                                               RETAIN 10 stone
                                           TO b
                                       END
                                   """.stripTrailing().stripIndent());
        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftChest.getStackInSlot(0).getCount() == 64 - 2, "Iron ingots did not retain");
            assertTrue(leftChest.getStackInSlot(1).getCount() == 64 - 10, "Stone did not retain");
            assertTrue(rightChest.getStackInSlot(0).getCount() == 2, "Iron ingots did not move");
            assertTrue(rightChest.getStackInSlot(1).getCount() == 10, "Stone did not move");
        });
    }

    /**
     * Ensure that cauldrons can be treated as water fluid holders
     */
    @GameTest(template = "3x2x1")
    public static void move_cauldron_water(GameTestHelper helper) {
        // fill in the blocks needed for the test
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos left = new BlockPos(2, 2, 0);
        helper.setBlock(left, Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3));
        BlockPos right = new BlockPos(0, 2, 0);
        helper.setBlock(right, Blocks.CAULDRON);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(left))
                .add("b", helper.absolutePos(right))
                .save(Objects.requireNonNull(manager.getDisk()));

        // load the program
        manager.setProgram("""
                                       NAME "cauldron water test"
                                   
                                       EVERY 20 TICKS DO
                                           INPUT fluid:minecraft:water FROM a
                                           OUTPUT fluid:*:* TO b
                                       END
                                   """.stripTrailing().stripIndent());

        assertManagerRunning(manager);
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            helper.assertBlock(left, b -> b == Blocks.CAULDRON, "cauldron didn't empty");
            helper.assertBlockState(
                    right,
                    s -> s.getBlock() == Blocks.WATER_CAULDRON
                         && s.getValue(LayeredCauldronBlock.LEVEL) == 3,
                    () -> "cauldron didn't fill"
            );

        });
    }

    /**
     * Ensure that a cauldrons can be treated as a lava fluid holder
     */
    @GameTest(template = "3x2x1")
    public static void move_cauldron_lava(GameTestHelper helper) {
        // fill in the blocks needed for the test
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos left = new BlockPos(2, 2, 0);
        helper.setBlock(left, Blocks.LAVA_CAULDRON.defaultBlockState());
        BlockPos right = new BlockPos(0, 2, 0);
        helper.setBlock(right, Blocks.CAULDRON);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(left))
                .add("b", helper.absolutePos(right))
                .save(Objects.requireNonNull(manager.getDisk()));

        // load the program
        manager.setProgram("""
                                       NAME "cauldron lava test"
                                   
                                       EVERY 20 TICKS DO
                                           INPUT fluid:minecraft:lava FROM a
                                           OUTPUT fluid:*:* TO b
                                       END
                                   """.stripTrailing().stripIndent());

        assertManagerRunning(manager);
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            helper.assertBlock(left, b -> b == Blocks.CAULDRON, "cauldron didn't empty");
            helper.assertBlockState(right, s -> s.getBlock() == Blocks.LAVA_CAULDRON, () -> "cauldron didn't fill");

        });
    }

    @GameTest(template = "25x4x25")
    public static void cable_spiral(GameTestHelper helper) {
        BlockPos start = new BlockPos(0, 2, 0);
        BlockPos end = new BlockPos(12, 2, 12);

        var len = 24;
        var dir = Direction.EAST;
        var current = start;
        while (len > 0) {
            // fill len blocks
            for (int i = 0; i < len; i++) {
                helper.setBlock(current, SFMBlocks.CABLE_BLOCK.get());
                current = current.relative(dir);
            }
            // turn right
            dir = dir.getClockWise();
            len -= 1;
        }

        // fill in the blocks needed for the test
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        helper.setBlock(start, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(end, SFMBlocks.TEST_BARREL_BLOCK.get());

        // add some items
        Container startChest = (Container) helper.getBlockEntity(start);
        startChest.setItem(0, new ItemStack(Items.IRON_INGOT, 64));
        Container endChest = (Container) helper.getBlockEntity(end);


        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(start))
                .add("b", helper.absolutePos(end))
                .save(Objects.requireNonNull(manager.getDisk()));

        // load the program
        manager.setProgram("""
                                       NAME "long cable test"
                                   
                                       EVERY 20 TICKS DO
                                           INPUT FROM a
                                           OUTPUT TO b
                                       END
                                   """.stripTrailing().stripIndent());

        assertManagerRunning(manager);
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // ensure item arrived
            assertTrue(endChest.getItem(0).getCount() == 64, "Items did not move");
            // ensure item left
            assertTrue(startChest.getItem(0).isEmpty(), "Items did not leave");

        });
    }


    @GameTest(template = "3x4x3")
    public static void regression_crash_type_mixing(GameTestHelper helper) {
        // fill in the blocks needed for the test
        BlockPos managerPos = new BlockPos(1, 2, 1);
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());

        BlockPos left = new BlockPos(2, 2, 1);
        helper.setBlock(left, SFMBlocks.TEST_BARREL_BLOCK.get());
        // add sticks to the chest
        Container chest = (Container) helper.getBlockEntity(left);
        chest.setItem(0, new ItemStack(Items.STICK, 64));

        BlockPos right = new BlockPos(0, 2, 1);
        helper.setBlock(right, SFMBlocks.TEST_BARREL_BLOCK.get());

        BlockPos front = new BlockPos(1, 2, 2);
        helper.setBlock(front, Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3));

        BlockPos back = new BlockPos(1, 2, 0);
        helper.setBlock(back, Blocks.CAULDRON);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(left))
                .add("a", helper.absolutePos(front))
                .add("b", helper.absolutePos(right))
                .add("b", helper.absolutePos(back))
                .save(Objects.requireNonNull(manager.getDisk()));

        // load the program
        manager.setProgram("""
                                       NAME "water crash test"
                                   
                                       every 20 ticks do
                                           INPUT  item:minecraft:stick, fluid:minecraft:water FROM a
                                           OUTPUT stick, fluid:minecraft:water TO b
                                       end
                                   """.stripTrailing().stripIndent());

        assertManagerRunning(manager);
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            helper.assertBlock(front, b -> b == Blocks.CAULDRON, "cauldron didn't empty");
            helper.assertBlockState(
                    back,
                    s -> s.getBlock() == Blocks.WATER_CAULDRON
                         && s.getValue(LayeredCauldronBlock.LEVEL) == 3,
                    () -> "cauldron didn't fill"
            );
            // ensure sticks departed
            assertTrue(chest.getItem(0).getCount() == 0, "Items did not move");
            // ensure sticks arrived
            Container rightChest = (Container) helper.getBlockEntity(right);
            assertTrue(rightChest.getItem(0).getCount() == 64, "Items did not move");


        });
    }

    @GameTest(template = "25x4x25") // start with empty platform
    public static void cable_network_formation(GameTestHelper helper) {
        // create a row of cables
        for (int i = 0; i < 10; i++) {
            helper.setBlock(new BlockPos(i, 2, 0), SFMBlocks.CABLE_BLOCK.get());
        }

        var net = CableNetworkManager
                .getOrRegisterNetworkFromCablePosition(helper.getLevel(), helper.absolutePos(new BlockPos(0, 2, 0)))
                .get();
        // those cables should all be on the same network
        for (int i = 0; i < 10; i++) {
            assertTrue(CableNetworkManager
                               .getOrRegisterNetworkFromCablePosition(
                                       helper.getLevel(),
                                       helper.absolutePos(new BlockPos(i, 2, 0))
                               )
                               .get() == net, "Line of ten should be on same network");
        }

        // the network should only contain those cables
        assertTrue(net.getCableCount() == 10, "Network size should be ten");

        // break a block in the middle of the cable
        helper.setBlock(new BlockPos(5, 2, 0), Blocks.AIR);
        // the network should split
        net = CableNetworkManager
                .getOrRegisterNetworkFromCablePosition(helper.getLevel(), helper.absolutePos(new BlockPos(0, 2, 0)))
                .get();
        // now we have a network of 5 cables and a network of 4 cables
        for (int i = 0; i < 5; i++) {
            assertTrue(CableNetworkManager
                               .getOrRegisterNetworkFromCablePosition(
                                       helper.getLevel(),
                                       helper.absolutePos(new BlockPos(i, 2, 0))
                               )
                               .get() == net, "Row of five should be same network after splitting");
        }
        var old = net;
        net = CableNetworkManager
                .getOrRegisterNetworkFromCablePosition(helper.getLevel(), helper.absolutePos(new BlockPos(6, 2, 0)))
                .get();
        assertTrue(old != net, "Networks should be distinct after splitting");
        for (int i = 6; i < 10; i++) {
            assertTrue(CableNetworkManager
                               .getOrRegisterNetworkFromCablePosition(
                                       helper.getLevel(),
                                       helper.absolutePos(new BlockPos(i, 2, 0))
                               )
                               .get() == net, "Remaining row should be same network after splitting");
        }

        // repair the cable
        helper.setBlock(new BlockPos(5, 2, 0), SFMBlocks.CABLE_BLOCK.get());
        // the network should merge
        net = CableNetworkManager
                .getOrRegisterNetworkFromCablePosition(helper.getLevel(), helper.absolutePos(new BlockPos(0, 2, 0)))
                .get();
        for (int i = 0; i < 10; i++) {
            assertTrue(CableNetworkManager
                               .getOrRegisterNetworkFromCablePosition(
                                       helper.getLevel(),
                                       helper.absolutePos(new BlockPos(i, 2, 0))
                               )
                               .get() == net, "Networks should merge to same network after repairing");
        }

        // add cables in the corner
        helper.setBlock(new BlockPos(0, 2, 1), SFMBlocks.CABLE_BLOCK.get());
        helper.setBlock(new BlockPos(1, 2, 1), SFMBlocks.CABLE_BLOCK.get());
        assertTrue(CableNetworkManager
                           .getOrRegisterNetworkFromCablePosition(
                                   helper.getLevel(),
                                   helper.absolutePos(new BlockPos(0, 2, 0))
                           )
                           .get()
                           .getCableCount() == 12, "Network should grow to twelve after adding two cables");

        // punch out the corner, the network should shrink by 1
        helper.setBlock(new BlockPos(1, 2, 1), Blocks.AIR);
        assertTrue(CableNetworkManager
                           .getOrRegisterNetworkFromCablePosition(
                                   helper.getLevel(),
                                   helper.absolutePos(new BlockPos(0, 2, 0))
                           )
                           .get()
                           .getCableCount() == 11, "Network should shrink to eleven after removing a cable");


        // create a new network in a plus shape
        helper.setBlock(new BlockPos(15, 2, 15), SFMBlocks.CABLE_BLOCK.get());
        for (Direction value : SFMDirections.DIRECTIONS) {
            helper.setBlock(new BlockPos(15, 2, 15).relative(value), SFMBlocks.CABLE_BLOCK.get());
        }
        // should all be on the same network
        net = CableNetworkManager
                .getOrRegisterNetworkFromCablePosition(helper.getLevel(), helper.absolutePos(new BlockPos(15, 2, 15)))
                .get();
        for (Direction value : SFMDirections.DIRECTIONS) {
            assertTrue(CableNetworkManager
                               .getOrRegisterNetworkFromCablePosition(
                                       helper.getLevel(),
                                       helper.absolutePos(new BlockPos(15, 2, 15).relative(value))
                               )
                               .get()
                       == net, "Plus cables should all be on the same network");
        }

        // break the block in the middle
        helper.setBlock(new BlockPos(15, 2, 15), Blocks.AIR);
        // the network should split
        assertTrue(CableNetworkManager
                           .getOrRegisterNetworkFromCablePosition(
                                   helper.getLevel(),
                                   helper.absolutePos(new BlockPos(15, 2, 15))
                           )
                           .isEmpty(), "Network should not be present where the cable was removed from");
        var networks = new ArrayList<CableNetwork>();
        for (Direction value : SFMDirections.DIRECTIONS) {
            networks.add(CableNetworkManager
                                 .getOrRegisterNetworkFromCablePosition(
                                         helper.getLevel(),
                                         helper.absolutePos(new BlockPos(15, 2, 15).relative(value))
                                 )
                                 .get());
        }
        // make sure all the networks are different
        for (CableNetwork network : networks) {
            assertTrue(
                    networks.stream().filter(n -> n == network).count() == 1,
                    "Broken plus networks should be distinct"
            );
        }

        // add the block back
        helper.setBlock(new BlockPos(15, 2, 15), SFMBlocks.CABLE_BLOCK.get());
        // the network should merge
        net = CableNetworkManager
                .getOrRegisterNetworkFromCablePosition(helper.getLevel(), helper.absolutePos(new BlockPos(15, 2, 15)))
                .get();
        for (Direction value : SFMDirections.DIRECTIONS) {
            assertTrue(CableNetworkManager
                               .getOrRegisterNetworkFromCablePosition(
                                       helper.getLevel(),
                                       helper.absolutePos(new BlockPos(15, 2, 15).relative(value))
                               )
                               .get()
                       == net, "Plus networks did not merge after repairing");
        }

        // let's also test having cables in more than just a straight line
        // we want corners with multiple cables adjacent

        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 5; j++) {
                helper.setBlock(new BlockPos(7 + i, 2, 7 + j), SFMBlocks.CABLE_BLOCK.get());
            }
        }
        // make sure it's all in a single network
        assertTrue(CableNetworkManager
                           .getOrRegisterNetworkFromCablePosition(
                                   helper.getLevel(),
                                   helper.absolutePos(new BlockPos(7, 2, 7))
                           )
                           .get()
                           .getCableCount() == 25, "Network cable count should be 25");
        // cut a line through it
        for (int i = 0; i < 5; i++) {
            helper.setBlock(new BlockPos(7 + i, 2, 9), Blocks.AIR);
        }

        // make sure the network disappeared where it was cut
        assertTrue(CableNetworkManager
                           .getOrRegisterNetworkFromCablePosition(
                                   helper.getLevel(),
                                   helper.absolutePos(new BlockPos(7, 2, 9))
                           )
                           .isEmpty(), "Network should not be present where the cable was removed from");
        // make sure new network of 10 is formed
        assertTrue(CableNetworkManager
                           .getOrRegisterNetworkFromCablePosition(
                                   helper.getLevel(),
                                   helper.absolutePos(new BlockPos(7, 2, 8))
                           )
                           .get()
                           .getCableCount() == 10, "New network should be size ten");
        // make sure new network of 10 is formed
        assertTrue(CableNetworkManager
                           .getOrRegisterNetworkFromCablePosition(
                                   helper.getLevel(),
                                   helper.absolutePos(new BlockPos(7, 2, 11))
                           )
                           .get()
                           .getCableCount() == 10, "Other new network should be size ten");
        // make sure the new networks are distinct
        assertTrue(CableNetworkManager
                           .getOrRegisterNetworkFromCablePosition(
                                   helper.getLevel(),
                                   helper.absolutePos(new BlockPos(7, 2, 8))
                           )
                           .get() != CableNetworkManager
                           .getOrRegisterNetworkFromCablePosition(
                                   helper.getLevel(),
                                   helper.absolutePos(new BlockPos(7, 2, 11))
                           )
                           .get(), "New networks should be distinct");


        helper.succeed();
    }

    @GameTest(template = "3x2x1")
    public static void cable_network_rebuilding(GameTestHelper helper) {
        helper.setBlock(new BlockPos(0, 2, 0), SFMBlocks.CABLE_BLOCK.get());
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.CABLE_BLOCK.get());
        helper.setBlock(new BlockPos(2, 2, 0), SFMBlocks.CABLE_BLOCK.get());
        var network = CableNetworkManager.getOrRegisterNetworkFromCablePosition(
                helper.getLevel(),
                helper.absolutePos(new BlockPos(0, 2, 0))
        );
        assertTrue(network.isPresent(), "Network should be built");
        CableNetworkManager.unregisterNetworkForTestingPurposes(network.get());
        network = CableNetworkManager.getOrRegisterNetworkFromCablePosition(
                helper.getLevel(),
                helper.absolutePos(new BlockPos(0, 2, 0))
        );
        assertTrue(network.isPresent(), "Network should be rebuilt after clearing");
        assertTrue(network.get().getCableCount() == 3, "Network rebuilding should discover 3 cables");
        helper.succeed();
    }


    @GameTest(template = "3x2x1") // start with empty platform
    public static void CauldronLavaMovement(GameTestHelper helper) {
        // fill in the blocks needed for the test
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos left = new BlockPos(2, 2, 0);
        helper.setBlock(left, Blocks.LAVA_CAULDRON);
        BlockPos right = new BlockPos(0, 2, 0);
        helper.setBlock(right, Blocks.CAULDRON);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // create the program
        var program = """
                    NAME "cauldron water test"
                
                    EVERY 20 TICKS DO
                        INPUT fluid:minecraft:lava FROM a
                        OUTPUT fluid:*:* TO b
                    END
                """;

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(left))
                .add("b", helper.absolutePos(right))
                .save(Objects.requireNonNull(manager.getDisk()));

        // load the program
        manager.setProgram(program);

        assertManagerRunning(manager);
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            helper.assertBlock(left, b -> b == Blocks.CAULDRON, "cauldron didn't empty");
            helper.assertBlockState(right, s -> s.getBlock() == Blocks.LAVA_CAULDRON, () -> "cauldron didn't fill");

        });
    }

    @GameTest(template = "3x2x1")
    public static void move_slots(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Items.DIAMOND, 5), false);
        leftChest.insertItem(1, new ItemStack(Items.DIAMOND, 5), false);
        leftChest.insertItem(3, new ItemStack(Items.DIAMOND, 5), false);
        leftChest.insertItem(4, new ItemStack(Items.DIAMOND, 5), false);
        leftChest.insertItem(5, new ItemStack(Items.DIAMOND, 5), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM a TOP SIDE SLOTS 0,1,3-4,5
                                           OUTPUT TO a SLOTS 2
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftChest.getStackInSlot(0).isEmpty(), "slot 0 did not leave");
            assertTrue(leftChest.getStackInSlot(1).isEmpty(), "slot 1 did not leave");
            assertTrue(leftChest.getStackInSlot(3).isEmpty(), "slot 3 did not leave");
            assertTrue(leftChest.getStackInSlot(4).isEmpty(), "slot 4 did not leave");
            assertTrue(leftChest.getStackInSlot(5).isEmpty(), "slot 5 did not leave");
            assertTrue(leftChest.getStackInSlot(2).getCount() == 25, "Items did not transfer to slot 2");
            assertTrue(IntStream
                               .range(0, rightChest.getSlots())
                               .allMatch(slot -> rightChest.getStackInSlot(slot).isEmpty()), "Chest b is not empty");

        });
    }


    @GameTest(template = "3x4x3")
    public static void printing_press_clone_program(GameTestHelper helper) {
        var printingPos = new BlockPos(1, 2, 1);
        var pistonPos = new BlockPos(1, 4, 1);
        var woodPos = new BlockPos(0, 4, 1);
        var buttonPos = new BlockPos(0, 4, 0);
        var chestPos = new BlockPos(0, 2, 1);

        helper.setBlock(printingPos, SFMBlocks.PRINTING_PRESS_BLOCK.get());
        helper.setBlock(pistonPos, Blocks.PISTON.defaultBlockState().setValue(DirectionalBlock.FACING, Direction.DOWN));
        helper.setBlock(woodPos, Blocks.OAK_PLANKS);
        helper.setBlock(buttonPos, Blocks.STONE_BUTTON);
        helper.setBlock(chestPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var printingPress = (PrintingPressBlockEntity) helper.getBlockEntity(printingPos);
        Player player = helper.makeMockPlayer();
        player.setItemInHand(InteractionHand.MAIN_HAND, new ItemStack(Items.BLACK_DYE));
        BlockState pressState = helper.getBlockState(printingPos);
        pressState.getBlock().use(
                pressState,
                helper.getLevel(),
                helper.absolutePos(printingPos),
                player,
                InteractionHand.MAIN_HAND,
                new BlockHitResult(
                        new Vec3(0.5, 0.5, 0.5),
                        Direction.UP,
                        helper.absolutePos(printingPos),
                        false
                )
        );
        player.setItemInHand(InteractionHand.MAIN_HAND, new ItemStack(SFMItems.DISK_ITEM.get()));
        pressState.getBlock().use(
                pressState,
                helper.getLevel(),
                helper.absolutePos(printingPos),
                player,
                InteractionHand.MAIN_HAND,
                new BlockHitResult(
                        new Vec3(0.5, 0.5, 0.5),
                        Direction.UP,
                        helper.absolutePos(printingPos),
                        false
                )
        );
        var disk = new ItemStack(SFMItems.DISK_ITEM.get());
        DiskItem.setProgram(disk, """
                    EVERY 20 TICKS DO
                        INPUT FROM a TOP SIDE SLOTS 0,1,3-4,5
                        OUTPUT TO a SLOTS 2
                    END
                """.stripTrailing().stripIndent());
        player.setItemInHand(InteractionHand.MAIN_HAND, FormItem.getForm(disk));
        pressState.getBlock().use(
                pressState,
                helper.getLevel(),
                helper.absolutePos(printingPos),
                player,
                InteractionHand.MAIN_HAND,
                new BlockHitResult(
                        new Vec3(0.5, 0.5, 0.5),
                        Direction.UP,
                        helper.absolutePos(printingPos),
                        false
                )
        );

        BlockState buttonState = helper.getBlockState(buttonPos);
        buttonState.getBlock().use(
                buttonState,
                helper.getLevel(),
                helper.absolutePos(buttonPos),
                player,
                InteractionHand.MAIN_HAND,
                new BlockHitResult(
                        new Vec3(0.5, 0.5, 0.5),
                        Direction.UP,
                        helper.absolutePos(printingPos),
                        false
                )
        );

        helper.runAfterDelay(5, () -> {
            pressState.getBlock().use(
                    pressState,
                    helper.getLevel(),
                    helper.absolutePos(printingPos),
                    player,
                    InteractionHand.MAIN_HAND,
                    new BlockHitResult(
                            new Vec3(0.5, 0.5, 0.5),
                            Direction.UP,
                            helper.absolutePos(printingPos),
                            false
                    )
            );
            ItemStack held = player.getMainHandItem();
            if (held.is(SFMItems.DISK_ITEM.get()) && DiskItem.getProgram(held).equals(DiskItem.getProgram(disk))) {
                var chest = getItemHandler(helper, chestPos);
                chest.insertItem(0, held, false);
                assertTrue(printingPress.getInk().isEmpty(), "Ink was not consumed");
                assertTrue(printingPress.getPaper().isEmpty(), "Paper was not consumed");
                assertTrue(!printingPress.getForm().isEmpty(), "Form should not be consumed");
                helper.succeed();
            } else {
                helper.fail("Disk was not cloned");
            }
        });
    }

    @GameTest(template = "1x2x1")
    public static void printing_press_insertion_extraction(GameTestHelper helper) {
        var pos = new BlockPos(0, 2, 0);
        helper.setBlock(pos, SFMBlocks.PRINTING_PRESS_BLOCK.get());
        var printingPress = (PrintingPressBlockEntity) helper.getBlockEntity(pos);
        var player = helper.makeMockPlayer();
        // put black dye in player hand
        player.setItemInHand(InteractionHand.MAIN_HAND, new ItemStack(Items.BLACK_DYE, 23));
        // right click on printing press
        BlockState pressState = helper.getBlockState(pos);
        pressState.getBlock().use(
                pressState,
                helper.getLevel(),
                helper.absolutePos(pos),
                player,
                InteractionHand.MAIN_HAND,
                new BlockHitResult(
                        new Vec3(0.5, 0.5, 0.5),
                        Direction.UP,
                        helper.absolutePos(pos),
                        false
                )
        );
        // assert the ink was inserted
        assertTrue(!printingPress.getInk().isEmpty(), "Ink was not inserted");
        assertTrue(player.getMainHandItem().isEmpty(), "Ink was not taken from hand");
        // put book in player hand
        player.setItemInHand(InteractionHand.MAIN_HAND, new ItemStack(Items.BOOK));
        // right click on printing press
        pressState.getBlock().use(
                pressState,
                helper.getLevel(),
                helper.absolutePos(pos),
                player,
                InteractionHand.MAIN_HAND,
                new BlockHitResult(
                        new Vec3(0.5, 0.5, 0.5),
                        Direction.UP,
                        helper.absolutePos(pos),
                        false
                )
        );
        // assert the book was inserted
        assertTrue(!printingPress.getPaper().isEmpty(), "Paper was not inserted");
        assertTrue(player.getMainHandItem().isEmpty(), "Paper was not taken from hand");
        // put form in player hand
        var form = FormItem.getForm(new ItemStack(Items.WRITTEN_BOOK));
        player.setItemInHand(InteractionHand.MAIN_HAND, form.copy());
        // right click on printing press
        pressState.getBlock().use(
                pressState,
                helper.getLevel(),
                helper.absolutePos(pos),
                player,
                InteractionHand.MAIN_HAND,
                new BlockHitResult(
                        new Vec3(0.5, 0.5, 0.5),
                        Direction.UP,
                        helper.absolutePos(pos),
                        false
                )
        );
        // assert the form was inserted
        assertTrue(!printingPress.getForm().isEmpty(), "Form was not inserted");
        assertTrue(player.getMainHandItem().isEmpty(), "Form was not taken from hand");

        // pull out item
        player.setItemInHand(InteractionHand.MAIN_HAND, ItemStack.EMPTY);
        // right click on printing press
        pressState.getBlock().use(
                pressState,
                helper.getLevel(),
                helper.absolutePos(pos),
                player,
                InteractionHand.MAIN_HAND,
                new BlockHitResult(
                        new Vec3(0.5, 0.5, 0.5),
                        Direction.UP,
                        helper.absolutePos(pos),
                        false
                )
        );
        // assert the paper was extracted
        assertTrue(printingPress.getPaper().isEmpty(), "Paper was not extracted");
        assertTrue(!player.getMainHandItem().isEmpty(), "Paper was not given to player");
        assertTrue(player.getMainHandItem().is(Items.BOOK), "Paper doesn't match");
        assertTrue(player.getMainHandItem().getCount() == 1, "Paper wrong count");

        // pull out an item
        player.setItemInHand(InteractionHand.MAIN_HAND, ItemStack.EMPTY);
        // right click on printing press
        pressState.getBlock().use(
                pressState,
                helper.getLevel(),
                helper.absolutePos(pos),
                player,
                InteractionHand.MAIN_HAND,
                new BlockHitResult(
                        new Vec3(0.5, 0.5, 0.5),
                        Direction.UP,
                        helper.absolutePos(pos),
                        false
                )
        );
        // assert the form was extracted
        assertTrue(printingPress.getForm().isEmpty(), "Form was not extracted");
        assertTrue(!player.getMainHandItem().isEmpty(), "Form was not given to player");
        assertTrue(SFMItemUtils.isSameItemSameTags(player.getMainHandItem(), form), "Form doesn't match");
        // pull out item
        player.setItemInHand(InteractionHand.MAIN_HAND, ItemStack.EMPTY);
        // right click on printing press
        pressState.getBlock().use(
                pressState,
                helper.getLevel(),
                helper.absolutePos(pos),
                player,
                InteractionHand.MAIN_HAND,
                new BlockHitResult(
                        new Vec3(0.5, 0.5, 0.5),
                        Direction.UP,
                        helper.absolutePos(pos),
                        false
                )
        );
        // assert the ink was extracted
        assertTrue(printingPress.getInk().isEmpty(), "Ink was not extracted");
        assertTrue(!player.getMainHandItem().isEmpty(), "Ink was not given to player");
        assertTrue(player.getMainHandItem().is(Items.BLACK_DYE), "Ink doesn't match");
        assertTrue(player.getMainHandItem().getCount() == 23, "Ink wrong count");
        // try to pull out another item
        player.setItemInHand(InteractionHand.MAIN_HAND, ItemStack.EMPTY);
        // right click on printing press
        pressState.getBlock().use(
                pressState,
                helper.getLevel(),
                helper.absolutePos(pos),
                player,
                InteractionHand.MAIN_HAND,
                new BlockHitResult(
                        new Vec3(0.5, 0.5, 0.5),
                        Direction.UP,
                        helper.absolutePos(pos),
                        false
                )
        );
        // assert nothing was extracted
        assertTrue(player.getMainHandItem().isEmpty(), "Nothing should have been extracted");
        helper.succeed();
    }

    @GameTest(template = "3x4x3")
    public static void printing_press_clone_enchantment(GameTestHelper helper) {
        var printingPos = new BlockPos(1, 2, 1);
        var pistonPos = new BlockPos(1, 4, 1);
        var woodPos = new BlockPos(0, 4, 1);
        var buttonPos = new BlockPos(0, 4, 0);
        var chestPos = new BlockPos(0, 2, 1);

        helper.setBlock(printingPos, SFMBlocks.PRINTING_PRESS_BLOCK.get());
        helper.setBlock(pistonPos, Blocks.PISTON.defaultBlockState().setValue(DirectionalBlock.FACING, Direction.DOWN));
        helper.setBlock(woodPos, Blocks.OAK_PLANKS);
        helper.setBlock(buttonPos, Blocks.STONE_BUTTON);
        helper.setBlock(chestPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var printingPress = (PrintingPressBlockEntity) helper.getBlockEntity(printingPos);
        Player player = helper.makeMockPlayer();
        player.setItemInHand(InteractionHand.MAIN_HAND, new ItemStack(SFMItems.EXPERIENCE_GOOP_ITEM.get(), 10));
        BlockState pressState = helper.getBlockState(printingPos);
        pressState.getBlock().use(
                pressState,
                helper.getLevel(),
                helper.absolutePos(printingPos),
                player,
                InteractionHand.MAIN_HAND,
                new BlockHitResult(
                        new Vec3(0.5, 0.5, 0.5),
                        Direction.UP,
                        helper.absolutePos(printingPos),
                        false
                )
        );
        player.setItemInHand(InteractionHand.MAIN_HAND, new ItemStack(Items.BOOK));
        pressState.getBlock().use(
                pressState,
                helper.getLevel(),
                helper.absolutePos(printingPos),
                player,
                InteractionHand.MAIN_HAND,
                new BlockHitResult(
                        new Vec3(0.5, 0.5, 0.5),
                        Direction.UP,
                        helper.absolutePos(printingPos),
                        false
                )
        );
        ItemStack reference = EnchantedBookItem.createForEnchantment(new EnchantmentInstance(
                Enchantments.SHARPNESS,
                3
        ));
        player.setItemInHand(InteractionHand.MAIN_HAND, FormItem.getForm(reference));
        pressState.getBlock().use(
                pressState,
                helper.getLevel(),
                helper.absolutePos(printingPos),
                player,
                InteractionHand.MAIN_HAND,
                new BlockHitResult(
                        new Vec3(0.5, 0.5, 0.5),
                        Direction.UP,
                        helper.absolutePos(printingPos),
                        false
                )
        );

        BlockState buttonState = helper.getBlockState(buttonPos);
        buttonState.getBlock().use(
                buttonState,
                helper.getLevel(),
                helper.absolutePos(buttonPos),
                player,
                InteractionHand.MAIN_HAND,
                new BlockHitResult(
                        new Vec3(0.5, 0.5, 0.5),
                        Direction.UP,
                        helper.absolutePos(printingPos),
                        false
                )
        );

        helper.runAfterDelay(5, () -> {
            pressState.getBlock().use(
                    pressState,
                    helper.getLevel(),
                    helper.absolutePos(printingPos),
                    player,
                    InteractionHand.MAIN_HAND,
                    new BlockHitResult(
                            new Vec3(0.5, 0.5, 0.5),
                            Direction.UP,
                            helper.absolutePos(printingPos),
                            false
                    )
            );
            ItemStack held = player.getMainHandItem();
            if (SFMItemUtils.isSameItemSameTags(held, reference)) {
                var chest = getItemHandler(helper, chestPos);
                chest.insertItem(0, held, false);
                assertTrue(printingPress.getInk().getCount() == 9, "Ink was not consumed properly");
                assertTrue(printingPress.getPaper().isEmpty(), "Paper was not consumed");
                assertTrue(!printingPress.getForm().isEmpty(), "Form should not be consumed");
                helper.succeed();
            } else {
                helper.fail("cloned item wasnt same");
            }
        });
    }

    @GameTest(template = "3x4x3")
    public static void falling_anvil_program_form(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 1), Blocks.IRON_BLOCK);
        var pos = helper.absoluteVec(new Vec3(1.5, 3.5, 1.5));
        ItemStack disk = new ItemStack(SFMItems.DISK_ITEM.get());
        DiskItem.setProgram(disk, """
                    NAME "falling anvil test"
                    EVERY 20 TICKS DO
                        INPUT FROM a TOP SIDE SLOTS 0,1,3-4,5
                        OUTPUT TO a SLOTS 2
                    END
                """.stripTrailing().stripIndent());
        helper
                .getLevel()
                .addFreshEntity(new ItemEntity(
                        helper.getLevel(),
                        pos.x, pos.y, pos.z,
                        disk,
                        0, 0, 0
                ));
        helper.setBlock(new BlockPos(1, 4, 1), Blocks.ANVIL);
        helper.runAfterDelay(20, () -> {
            List<ItemEntity> found = helper
                    .getLevel()
                    .getEntitiesOfClass(
                            ItemEntity.class,
                            new AABB(helper.absolutePos(new BlockPos(1, 4, 1))).inflate(3)
                    );
            if (found.stream().anyMatch(e -> SFMItemUtils.isSameItemSameTags(e.getItem(), FormItem.getForm(disk)))) {
                helper.succeed();
            } else {
                helper.fail("no form found");
            }
        });
    }

    @GameTest(template = "3x4x3")
    public static void falling_anvil_enchantment_form(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 1), Blocks.IRON_BLOCK);
        var pos = helper.absoluteVec(new Vec3(1.5, 3.5, 1.5));
        ItemStack reference = EnchantedBookItem.createForEnchantment(new EnchantmentInstance(
                Enchantments.SHARPNESS,
                3
        ));
        helper
                .getLevel()
                .addFreshEntity(new ItemEntity(
                        helper.getLevel(),
                        pos.x, pos.y, pos.z,
                        reference,
                        0, 0, 0
                ));
        helper.setBlock(new BlockPos(1, 4, 1), Blocks.ANVIL);
        helper.runAfterDelay(20, () -> {
            List<ItemEntity> found = helper
                    .getLevel()
                    .getEntitiesOfClass(
                            ItemEntity.class,
                            new AABB(helper.absolutePos(new BlockPos(1, 4, 1))).inflate(3)
                    );
            if (found.stream().anyMatch(e -> SFMItemUtils.isSameItemSameTags(e.getItem(), FormItem.getForm(reference)))) {
                helper.succeed();
            } else {
                helper.fail("no form found");
            }
        });
    }

    @GameTest(template = "3x4x3")
    public static void falling_anvil_disenchant(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 1), Blocks.OBSIDIAN);
        var pos = helper.absoluteVec(new Vec3(1.5, 3.5, 1.5));
        helper
                .getLevel()
                .addFreshEntity(new ItemEntity(
                        helper.getLevel(),
                        pos.x, pos.y, pos.z,
                        new ItemStack(Items.BOOK, 16),
                        0, 0, 0
                ));
        var axe = new ItemStack(Items.GOLDEN_AXE);
        axe.enchant(Enchantments.BLOCK_EFFICIENCY, 3);
        axe.enchant(Enchantments.SHARPNESS, 2);
        helper.getLevel().addFreshEntity(new ItemEntity(
                helper.getLevel(),
                pos.x, pos.y, pos.z,
                axe,
                0, 0, 0
        ));
        helper.setBlock(new BlockPos(1, 4, 1), Blocks.ANVIL);
        helper.runAfterDelay(20, () -> {
            List<ItemEntity> found = helper
                    .getLevel()
                    .getEntitiesOfClass(
                            ItemEntity.class,
                            new AABB(helper.absolutePos(new BlockPos(1, 4, 1))).inflate(3)
                    );
            boolean foundDisenchantedAxe = found
                    .stream()
                    .anyMatch(e -> SFMItemUtils.isSameItemSameTags(e.getItem(), new ItemStack(Items.GOLDEN_AXE)));
            boolean foundEfficiencyBook = found
                    .stream()
                    .anyMatch(e -> SFMItemUtils.isSameItemSameTags(
                            e.getItem(),
                            EnchantedBookItem.createForEnchantment(new EnchantmentInstance(
                                    Enchantments.BLOCK_EFFICIENCY,
                                    3
                            ))
                    ));
            boolean foundSharpnessBook = found
                    .stream()
                    .anyMatch(e -> SFMItemUtils.isSameItemSameTags(
                            e.getItem(),
                            EnchantedBookItem.createForEnchantment(new EnchantmentInstance(Enchantments.SHARPNESS, 2))
                    ));
            boolean foundRemainingBooks = found
                                                  .stream()
                                                  .filter(e -> e.getItem().is(Items.BOOK))
                                                  .mapToInt(e -> e.getItem().getCount())
                                                  .sum() == 16 - 2;
            if (foundDisenchantedAxe && foundEfficiencyBook && foundSharpnessBook && foundRemainingBooks) {
                helper.succeed();
            } else {
                helper.fail("disenchant failed");
            }
        });
    }

    @GameTest(template = "3x4x3")
    public static void falling_anvil_xp_shard(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 1), Blocks.OBSIDIAN);
        var pos = helper.absoluteVec(new Vec3(1.5, 3.5, 1.5));
        ItemStack enchBook = EnchantedBookItem.createForEnchantment(new EnchantmentInstance(
                Enchantments.SHARPNESS,
                4
        ));
        EnchantedBookItem.addEnchantment(enchBook, new EnchantmentInstance(Enchantments.BLOCK_EFFICIENCY, 2));

        var cases = List.of(
                Pair.of(LevelsToShards.JustOne, 1),
                Pair.of(LevelsToShards.EachOne, 2),
                Pair.of(LevelsToShards.SumLevels, 6),
                Pair.of(LevelsToShards.SumLevelsScaledExponentially, 10)
        );

        var currentConfig = SFMConfig.SERVER.levelsToShards.get();
        falling_anvil_xp_shard_inner(helper, 1, currentConfig, pos, enchBook, cases.iterator());
    }

    @GameTest(template = "3x4x3")
    public static void falling_anvil_xp_shard_many(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 1), Blocks.OBSIDIAN);
        var pos = helper.absoluteVec(new Vec3(1.5, 3.5, 1.5));
        ItemStack enchBook = EnchantedBookItem.createForEnchantment(new EnchantmentInstance(
                Enchantments.SHARPNESS,
                4
        ));
        EnchantedBookItem.addEnchantment(enchBook, new EnchantmentInstance(Enchantments.BLOCK_EFFICIENCY, 2));

        var cases = List.of(
                Pair.of(LevelsToShards.JustOne, 10),
                Pair.of(LevelsToShards.EachOne, 20),
                Pair.of(LevelsToShards.SumLevels, 60),
                Pair.of(LevelsToShards.SumLevelsScaledExponentially, 100)
        );

        var currentConfig = SFMConfig.SERVER.levelsToShards.get();
        falling_anvil_xp_shard_inner(helper, 10, currentConfig, pos, enchBook, cases.iterator());
    }

    @GameTest(template = "1x2x1")
    public static void disk_item_clientside_regression(GameTestHelper helper) {
        var stack = new ItemStack(SFMItems.DISK_ITEM.get());
        stack.getDisplayName();
        stack.getHoverName();
        stack.getItem().getName(stack);
        stack.getItem().appendHoverText(stack, helper.getLevel(), new ArrayList<>(), TooltipFlag.Default.NORMAL);
        Vec3 pos = helper.absoluteVec(new Vec3(0.5, 2, 0.5));
        ItemEntity itemEntity = new ItemEntity(helper.getLevel(), pos.x, pos.y, pos.z, stack, 0, 0, 0);
        helper.getLevel().addFreshEntity(itemEntity);
        helper.succeed();
    }

    @GameTest(template = "1x2x1")
    public static void program_crlf_line_endings_conversion(GameTestHelper helper) {
        var managerPos = new BlockPos(0, 2, 0);
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        String program = """
                NAME "line endings test"
                EVERY 20 TICKS DO
                    INPUT FROM a
                    OUTPUT TO b
                END
                """.stripTrailing().stripIndent();
        String programWithWindowsLineEndings = program.replaceAll("\n", "\r\n");
        manager.setProgram(programWithWindowsLineEndings);
        var programString = manager.getProgramString();
        if (programString.equals(program)) {
            helper.succeed();
        } else {
            helper.fail(String.format(
                    "program string was not converted correctly: %s",
                    programString
            ));
        }
    }

    @GameTest(template = "3x2x1")
    public static void pattern_cache_regression_1(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Blocks.IRON_BLOCK, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           -- pattern caching behaviour should not short circuit this to match all
                                           -- since underscores wouldn't be matched by this
                                           INPUT "[a-z]*" FROM a
                                           OUTPUT TO b
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftChest.getStackInSlot(0).getCount() == 64, "should not depart");
            assertTrue(rightChest.getStackInSlot(0).isEmpty(), "should not arrive");

        });
    }

    @GameTest(template = "3x2x1")
    public static void pattern_cache_regression_2(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Blocks.IRON_BLOCK, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           -- pattern caching behaviour should not short circuit this to match all
                                           -- since underscores wouldn't be matched by this
                                           INPUT "[a-zA-Z]*" FROM a
                                           OUTPUT TO b
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftChest.getStackInSlot(0).getCount() == 64, "should not depart");
            assertTrue(rightChest.getStackInSlot(0).isEmpty(), "should not arrive");

        });
    }

    @GameTest(template = "3x2x1")
    public static void pattern_cache_regression_3(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Blocks.IRON_BLOCK, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           -- pattern caching behaviour should not short circuit this to match all
                                           -- since underscores wouldn't be matched by this
                                           INPUT "[a-z0-9]*" FROM a
                                           OUTPUT TO b
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftChest.getStackInSlot(0).getCount() == 64, "should not depart");
            assertTrue(rightChest.getStackInSlot(0).isEmpty(), "should not arrive");

        });
    }

    @GameTest(template = "3x2x1")
    public static void each_src_quantity(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Items.IRON_INGOT, 64), false);
        leftChest.insertItem(1, new ItemStack(Items.GOLD_INGOT, 64), false);
        leftChest.insertItem(2, new ItemStack(Items.NETHERITE_INGOT, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT 2 EACH *ingot* FROM a
                                           OUTPUT TO b
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // left should have 62 of each ingot
            assertTrue(count(leftChest, Items.IRON_INGOT) == 62, "Iron did not move");
            assertTrue(count(leftChest, Items.GOLD_INGOT) == 62, "Gold did not move");
            assertTrue(count(leftChest, Items.NETHERITE_INGOT) == 62, "Netherite did not move");
            // right should have 2 of each ingot
            assertTrue(count(rightChest, Items.IRON_INGOT) == 2, "Iron did not arrive");
            assertTrue(count(rightChest, Items.GOLD_INGOT) == 2, "Gold did not arrive");
            assertTrue(count(rightChest, Items.NETHERITE_INGOT) == 2, "Netherite did not arrive");

        });
    }

    @GameTest(template = "3x2x1")
    public static void each_src_quantity_retain(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Items.IRON_INGOT, 2), false);
        leftChest.insertItem(1, new ItemStack(Items.GOLD_INGOT, 2), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           -- move 2 of each ingot type
                                           -- keep 2 ingots total
                                           INPUT 2 EACH RETAIN 2 *ingot* FROM a
                                           OUTPUT TO b
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // two of the four ingots should have moved
            // for now we assume that gold will move since it is in the higher slot
            assertTrue(count(leftChest, Items.IRON_INGOT) == 2, "Iron moved");
            assertTrue(count(leftChest, Items.GOLD_INGOT) == 0, "Gold did not move");
            assertTrue(count(rightChest, Items.IRON_INGOT) == 0, "Iron arrive");
            assertTrue(count(rightChest, Items.GOLD_INGOT) == 2, "Gold did not arrive");

        });
    }

    @GameTest(template = "3x2x1")
    public static void each_src_quantity_each_retain(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Items.IRON_INGOT, 2), false);
        leftChest.insertItem(1, new ItemStack(Items.GOLD_INGOT, 2), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           -- move 2 of each ingot type
                                           -- keep 2 ingots of each type
                                           INPUT 2 EACH RETAIN 2 EACH *ingot* FROM a
                                           OUTPUT TO b
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // two of the four ingots should have moved
            // for now we assume that gold will move since it is in the higher slot
            assertTrue(count(leftChest, Items.IRON_INGOT) == 2, "Iron moved");
            assertTrue(count(leftChest, Items.GOLD_INGOT) == 2, "Gold did not move");
            assertTrue(count(rightChest, Items.IRON_INGOT) == 0, "Iron arrive");
            assertTrue(count(rightChest, Items.GOLD_INGOT) == 0, "Gold arrived");

        });
    }

    @GameTest(template = "3x2x1")
    public static void each_src_retain(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Items.IRON_INGOT, 64), false);
        leftChest.insertItem(1, new ItemStack(Items.GOLD_INGOT, 64), false);
        leftChest.insertItem(2, new ItemStack(Items.NETHERITE_INGOT, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           -- move all ingots
                                           -- keep 2 of each ingot type
                                           INPUT RETAIN 2 EACH *ingot* FROM a
                                           OUTPUT TO b
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // left should have 2 of each ingot
            assertTrue(count(leftChest, Items.IRON_INGOT) == 2, "Iron did not move");
            assertTrue(count(leftChest, Items.GOLD_INGOT) == 2, "Gold did not move");
            assertTrue(count(leftChest, Items.NETHERITE_INGOT) == 2, "Netherite did not move");
            // right should have 62 of each ingot
            assertTrue(count(rightChest, Items.IRON_INGOT) == 62, "Iron did not arrive");
            assertTrue(count(rightChest, Items.GOLD_INGOT) == 62, "Gold did not arrive");
            assertTrue(count(rightChest, Items.NETHERITE_INGOT) == 62, "Netherite did not arrive");

        });
    }

    @GameTest(template = "3x2x1")
    public static void each_dest_quantity(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Items.IRON_INGOT, 64), false);
        leftChest.insertItem(1, new ItemStack(Items.GOLD_INGOT, 64), false);
        leftChest.insertItem(2, new ItemStack(Items.NETHERITE_INGOT, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM a
                                           OUTPUT 2 each *ingot* TO b
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // left should have 62 of each ingot
            assertTrue(count(leftChest, Items.IRON_INGOT) == 62, "Iron did not move");
            assertTrue(count(leftChest, Items.GOLD_INGOT) == 62, "Gold did not move");
            assertTrue(count(leftChest, Items.NETHERITE_INGOT) == 62, "Netherite did not move");
            // right should have 2 of each ingot
            assertTrue(count(rightChest, Items.IRON_INGOT) == 2, "Iron did not arrive");
            assertTrue(count(rightChest, Items.GOLD_INGOT) == 2, "Gold did not arrive");
            assertTrue(count(rightChest, Items.NETHERITE_INGOT) == 2, "Netherite did not arrive");

        });
    }

    @GameTest(template = "3x2x1")
    public static void each_dest_quantity_retain(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Items.IRON_INGOT, 2), false);
        leftChest.insertItem(1, new ItemStack(Items.GOLD_INGOT, 2), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM a
                                           OUTPUT 2 EACH RETAIN 3 *ingot* TO b
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // two of the four ingots should have moved
            // for now we assume that gold will move since it is in the higher slot
            assertTrue(count(leftChest, Items.IRON_INGOT) == 0, "Iron moved");
            assertTrue(count(leftChest, Items.GOLD_INGOT) == 1, "Gold did not move");
            assertTrue(count(rightChest, Items.IRON_INGOT) == 2, "Iron arrive");
            assertTrue(count(rightChest, Items.GOLD_INGOT) == 1, "Gold did not arrive");

        });
    }

    @GameTest(template = "3x2x1")
    public static void each_dest_quantity_each_retain(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Items.IRON_INGOT, 8), false);
        leftChest.insertItem(1, new ItemStack(Items.GOLD_INGOT, 8), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM a
                                           OUTPUT 4 EACH RETAIN 2 EACH *ingot* TO b
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // two of the four ingots should have moved
            // for now we assume that gold will move since it is in the higher slot
            assertTrue(count(leftChest, Items.IRON_INGOT) == 6, "Iron depart fail");
            assertTrue(count(leftChest, Items.GOLD_INGOT) == 6, "Gold depart fail");
            assertTrue(count(rightChest, Items.IRON_INGOT) == 2, "Iron arrive fail");
            assertTrue(count(rightChest, Items.GOLD_INGOT) == 2, "Gold arrive fail");

        });
    }

    @GameTest(template = "3x2x1")
    public static void each_dest_retain(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Items.IRON_INGOT, 64), false);
        leftChest.insertItem(1, new ItemStack(Items.GOLD_INGOT, 64), false);
        leftChest.insertItem(2, new ItemStack(Items.NETHERITE_INGOT, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM a
                                           OUTPUT RETAIN 2 EACH *ingot* TO b
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // left should have 2 of each ingot
            assertTrue(count(leftChest, Items.IRON_INGOT) == 62, "Iron did not move");
            assertTrue(count(leftChest, Items.GOLD_INGOT) == 62, "Gold did not move");
            assertTrue(count(leftChest, Items.NETHERITE_INGOT) == 62, "Netherite did not move");
            // right should have 62 of each ingot
            assertTrue(count(rightChest, Items.IRON_INGOT) == 2, "Iron did not arrive");
            assertTrue(count(rightChest, Items.GOLD_INGOT) == 2, "Gold did not arrive");
            assertTrue(count(rightChest, Items.NETHERITE_INGOT) == 2, "Netherite did not arrive");

        });
    }

    @GameTest(template = "3x2x1")
    public static void sfm_v4_12_0_changelog(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        Item[] items = new Item[]{
                Items.NETHERITE_INGOT,
                Items.NETHERITE_INGOT,
                Items.GOLD_INGOT,
                Items.GOLD_INGOT,
                Items.COPPER_INGOT,
                Items.COPPER_INGOT,
                Items.SANDSTONE,
                Items.STONE,
                Items.COBBLESTONE,
                Items.OAK_LOG,
                Items.DARK_OAK_LOG,
                Items.ACACIA_LOG,
                };
        for (int i = 0; i < items.length; i++) {
            leftChest.insertItem(i, new ItemStack(items[i], 64), false);
        }

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       NAME "SFM 4.12.0 change overview"
                                       EVERY 20 TICKS DO
                                           INPUT fluid:: FROM a
                                           INPUT 1 *log FROM a
                                           INPUT EXCEPT *log FROM a
                                           OUTPUT
                                               1 EACH minecraft:*ingot,
                                               1 EACH RETAIN 12 EACH minecraft:*stone,
                                               *log,
                                           EXCEPT cobblestone, iron_ingot,
                                           TO b
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            Item[] expected = new Item[]{
                    Items.NETHERITE_INGOT,
                    Items.GOLD_INGOT,
                    Items.COPPER_INGOT,
                    Items.SANDSTONE,
                    Items.STONE,
                    Items.OAK_LOG,
                    };
            int[] found = new int[expected.length];
            slots:
            for (int i = 0; i < rightChest.getSlots(); i++) {
                ItemStack stack = rightChest.getStackInSlot(i);
                if (stack.isEmpty()) continue;
                Item item = stack.getItem();
                for (int j = 0; j < expected.length; j++) {
                    if (item == expected[j]) {
                        found[j] += stack.getCount();
                        continue slots;
                    }
                }
                assertTrue(false, "Unexpected item in chest: " + item);
            }
            for (int i = 0; i < found.length; i++) {
                assertTrue(found[i] == 1, "Expected " + expected[i] + " to be 1, but was " + found[i]);
            }

        });
    }

    @GameTest(template = "3x2x1")
    public static void forget_input_count_state(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Blocks.DIRT, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT 10 FROM a,b
                                           OUTPUT 1 to z
                                           FORGET b
                                           OUTPUT to z
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(leftPos))
                .add("z", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftChest.getStackInSlot(0).getCount() == 64 - 10, "did not remain");
            assertTrue(rightChest.getStackInSlot(0).getCount() == 10, "did not arrive");

        });
    }

    @GameTest(template = "3x2x1")
    public static void reorder_1(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Items.IRON_INGOT, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           FROM a
                                           INPUT iron_ingot
                                   
                                           TO b OUTPUT
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftChest.getStackInSlot(0).isEmpty(), "should depart");
            assertTrue(rightChest.getStackInSlot(0).getCount() == 64, "should arrive in size");
            assertTrue(rightChest.getStackInSlot(0).getItem() == Items.IRON_INGOT, "should arrive in type");

        });
    }

    @GameTest(template = "3x2x1")
    public static void reorder_2(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Items.IRON_INGOT, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           FROM a
                                           INPUT iron_ingot
                                   
                                           OUTPUT TO b
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftChest.getStackInSlot(0).isEmpty(), "should depart");
            assertTrue(rightChest.getStackInSlot(0).getCount() == 64, "should arrive in size");
            assertTrue(rightChest.getStackInSlot(0).getItem() == Items.IRON_INGOT, "should arrive in type");

        });
    }

    @GameTest(template = "3x2x1")
    public static void reorder_3(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Items.IRON_INGOT, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM a
                                   
                                           TO b
                                           OUTPUT iron_ingot
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftChest.getStackInSlot(0).isEmpty(), "should depart");
            assertTrue(rightChest.getStackInSlot(0).getCount() == 64, "should arrive in size");
            assertTrue(rightChest.getStackInSlot(0).getItem() == Items.IRON_INGOT, "should arrive in type");

        });
    }

    @GameTest(template = "3x4x3")
    public static void round_robin_by_block_1(GameTestHelper helper) {
        BlockPos managerPos = new BlockPos(1, 2, 1);
        BlockPos sourcePos = new BlockPos(1, 3, 1);
        BlockPos dest1Pos = new BlockPos(2, 2, 1);
        BlockPos dest2Pos = new BlockPos(0, 2, 1);

        // set up inventories
        helper.setBlock(sourcePos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(dest1Pos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(dest2Pos, SFMBlocks.TEST_BARREL_BLOCK.get());


        var sourceInv = getItemHandler(helper, sourcePos);

        var dest1Inv = getItemHandler(helper, dest1Pos);

        var dest2Inv = getItemHandler(helper, dest2Pos);

        for (int i = 0; i < sourceInv.getSlots(); i++) {
            sourceInv.insertItem(i, new ItemStack(Blocks.DIRT, 64), false);
        }

        // set up manager
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM source
                                           OUTPUT 128 dirt TO dest ROUND ROBIN BY BLOCK
                                       END
                                   """.stripTrailing().stripIndent());
        // set the labels
        LabelPositionHolder.empty()
                .add("source", helper.absolutePos(sourcePos))
                .add("dest", helper.absolutePos(dest1Pos))
                .add("dest", helper.absolutePos(dest2Pos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(count(sourceInv, Items.DIRT) == 64 * (27 - 2), "source count bad");
            int count1 = count(dest1Inv, Items.DIRT);
            int count2 = count(dest2Inv, Items.DIRT);
            assertTrue(count1 == 128 && count2 == 0 || count1 == 0 && count2 == 128, "first tick arrival count bad");


        });
    }

    @GameTest(template = "3x4x3")
    public static void round_robin_by_block_2(GameTestHelper helper) {
        for (int x = 0; x < 3; x++) {
            for (int z = 0; z < 3; z++) {
                helper.setBlock(x, 1, z, SFMBlocks.CABLE_BLOCK.get());
            }
        }
        BlockPos managerPos = new BlockPos(0, 2, 2);
        BlockPos sourcePos = new BlockPos(2, 2, 0);
        BlockPos a1Pos = new BlockPos(0, 2, 0);
        BlockPos a2Pos = new BlockPos(0, 2, 1);
        BlockPos b1Pos = new BlockPos(1, 2, 2);
        BlockPos b2Pos = new BlockPos(2, 2, 2);

        // set up inventories
        helper.setBlock(sourcePos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(a1Pos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(a2Pos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(b1Pos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(b2Pos, SFMBlocks.TEST_BARREL_BLOCK.get());


        var sourceInv = getItemHandler(helper, sourcePos);

        var a1 = getItemHandler(helper, a1Pos);
        var a2 = getItemHandler(helper, a2Pos);
        var b1 = getItemHandler(helper, b1Pos);
        var b2 = getItemHandler(helper, b2Pos);

        for (int i = 0; i < sourceInv.getSlots(); i++) {
            sourceInv.insertItem(i, new ItemStack(Blocks.DIRT, 64), false);
        }

        // set up manager
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM source
                                           OUTPUT 128 dirt TO EACH a,b ROUND ROBIN BY BLOCK
                                       END
                                   """.stripTrailing().stripIndent());
        // set the labels
        LabelPositionHolder.empty()
                .add("source", helper.absolutePos(sourcePos))
                .add("a", helper.absolutePos(a1Pos))
                .add("a", helper.absolutePos(a2Pos))
                .add("b", helper.absolutePos(b1Pos))
                .add("b", helper.absolutePos(b2Pos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(count(sourceInv, Items.DIRT) == 64 * (27 - 2), "source count bad");
            int a1Count = count(a1, Items.DIRT);
            int a2Count = count(a2, Items.DIRT);
            int b1Count = count(b1, Items.DIRT);
            int b2Count = count(b2, Items.DIRT);
            // only one of a1, a2, b1, b2 must be 128, rest must be zero
            boolean good = (a1Count == 128 && a2Count == 0 && b1Count == 0 && b2Count == 0) ||
                           (a1Count == 0 && a2Count == 128 && b1Count == 0 && b2Count == 0) ||
                           (a1Count == 0 && a2Count == 0 && b1Count == 128 && b2Count == 0) ||
                           (a1Count == 0 && a2Count == 0 && b1Count == 0 && b2Count == 128);
            assertTrue(good, "first tick arrival count bad");

        });
    }

    @GameTest(template = "3x2x1")
    public static void round_robin_no_blocks_crash_regression(GameTestHelper helper) {
        BlockPos leftPos = new BlockPos(2, 2, 0);
        BlockPos managerPos = new BlockPos(1, 2, 0);
        BlockPos rightPos = new BlockPos(0, 2, 0);

        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var leftChest = getItemHandler(helper, leftPos);
        leftChest.insertItem(0, new ItemStack(Blocks.DIRT, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM d,e ROUND ROBIN BY BLOCK
                                           OUTPUT TO f,g,h ROUND ROBIN BY LABEL
                                       END
                                   """.stripTrailing().stripIndent());

        // set labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        // it should not crash
        succeedIfManagerDidThingWithoutLagging(helper, manager, helper::succeed);
    }

    @GameTest(template = "3x4x3")
    public static void round_robin_by_label(GameTestHelper helper) {
        for (int x = 0; x < 3; x++) {
            for (int z = 0; z < 3; z++) {
                helper.setBlock(x, 1, z, SFMBlocks.CABLE_BLOCK.get());
            }
        }
        BlockPos managerPos = new BlockPos(0, 2, 2);
        BlockPos sourcePos = new BlockPos(2, 2, 0);
        BlockPos a1Pos = new BlockPos(0, 2, 0);
        BlockPos a2Pos = new BlockPos(0, 2, 1);
        BlockPos b1Pos = new BlockPos(1, 2, 2);
        BlockPos b2Pos = new BlockPos(2, 2, 2);

        // set up inventories
        helper.setBlock(sourcePos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(a1Pos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(a2Pos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(b1Pos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(b2Pos, SFMBlocks.TEST_BARREL_BLOCK.get());


        var sourceInv = getItemHandler(helper, sourcePos);

        var a1 = getItemHandler(helper, a1Pos);
        var a2 = getItemHandler(helper, a2Pos);
        var b1 = getItemHandler(helper, b1Pos);
        var b2 = getItemHandler(helper, b2Pos);

        for (int i = 0; i < sourceInv.getSlots(); i++) {
            sourceInv.insertItem(i, new ItemStack(Blocks.DIRT, 64), false);
        }

        // set up manager
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM source
                                           OUTPUT 128 dirt TO EACH a,b ROUND ROBIN BY LABEL
                                       END
                                   """.stripTrailing().stripIndent());
        // set the labels
        LabelPositionHolder.empty()
                .add("source", helper.absolutePos(sourcePos))
                .add("a", helper.absolutePos(a1Pos))
                .add("a", helper.absolutePos(a2Pos))
                .add("b", helper.absolutePos(b1Pos))
                .add("b", helper.absolutePos(b2Pos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(count(sourceInv, Items.DIRT) == 64 * (27 - 4), "source count bad");
            // we make no guarantees about which one ticks first
            // we guarantee only one of a or b receives on the first tick
            boolean condition1 = count(a1, Items.DIRT) == 128 && count(a2, Items.DIRT) == 128
                                 && count(b1, Items.DIRT) == 0 && count(b2, Items.DIRT) == 0;
            boolean condition2 = count(b1, Items.DIRT) == 128 && count(b2, Items.DIRT) == 128
                                 && count(a1, Items.DIRT) == 0 && count(a2, Items.DIRT) == 0;
            assertTrue(condition1 || condition2, "Arrival counts bad");
        });
    }

    @GameTest(template = "3x2x1")
    public static void wireless_regression(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 1, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Blocks.DIRT, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM a
                                           OUTPUT TO b
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(!leftChest.getStackInSlot(0).isEmpty(), "Dirt should not move");
            assertTrue(rightChest.getStackInSlot(0).getCount() != 64, "Dirt should not move");

        });
    }

    @GameTest(template = "3x2x1")
    public static void multi_io_limits(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Blocks.DIRT, 64), false);
        leftChest.insertItem(1, new ItemStack(Blocks.DIRT, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT 64 FROM a
                                           OUTPUT RETAIN 63 TO b slots 0
                                           OUTPUT TO b slots 1-99
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftChest.getStackInSlot(0).isEmpty(), "Dirt slot 0 must move");
            assertTrue(leftChest.getStackInSlot(1).getCount() == 64, "Dirt slot 1 must not move");
            assertTrue(rightChest.getStackInSlot(0).getCount() == 63, "Dirt slot 0 must arrive");
            assertTrue(rightChest.getStackInSlot(1).getCount() == 1, "Dirt slot 1 must arrive");
            assertTrue(rightChest.getStackInSlot(2).isEmpty(), "Dirt slot 2 must not arrive");

        });
    }

    @GameTest(template = "3x4x3")
    public static void move_on_pulse(GameTestHelper helper) {
        var managerPos = new BlockPos(1, 2, 1);
        var buttonPos = managerPos.offset(Direction.NORTH.getNormal());
        var leftPos = new BlockPos(2, 2, 1);
        var rightPos = new BlockPos(0, 2, 1);

        // place and fill the chests
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        var left = (BarrelBlockEntity) helper.getBlockEntity(leftPos);
        var right = (BarrelBlockEntity) helper.getBlockEntity(rightPos);
        left.setItem(0, new ItemStack(Items.IRON_INGOT, 64));

        // create the manager block and add the disk
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // create the program
        var program = """
                    NAME "move on pulse"
                
                    EVERY REDSTONE PULSE DO
                        INPUT FROM left
                        OUTPUT TO right
                    END
                """.stripTrailing().stripIndent();

        // set the labels
        LabelPositionHolder.empty()
                .add("left", helper.absolutePos(leftPos))
                .add("right", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        // load the program
        manager.setProgram(program);
        manager.setLogLevel(Level.TRACE);
        assertTrue(manager.logger.getLogLevel() == Level.TRACE, "Log level should be trace");

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(left.getItem(0).isEmpty(), "Iron should depart");
            assertTrue(right.getItem(0).getCount() == 64, "Iron should arrive");
        });

        // create the button
        helper.setBlock(buttonPos, Blocks.STONE_BUTTON);
        // push the button
        helper.pressButton(buttonPos);
    }

    @GameTest(template = "3x2x1", batch = "linting")
    public static void count_execution_paths_1(GameTestHelper helper) {
        // place inventories
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        // place manager
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // set the labels
        LabelPositionHolder labelPositionHolder = LabelPositionHolder.empty()
                .add("left", helper.absolutePos(leftPos))
                .add("right", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        // load the program
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM left
                                           OUTPUT TO right
                                       END
                                   """.stripTrailing().stripIndent());
        assertManagerRunning(manager);
        var program = manager.getProgram();

        // ensure no warnings
        var warnings = DiskItem.getWarnings(Objects.requireNonNull(manager.getDisk()));
        assertTrue(warnings.isEmpty(), "expected 0 warning, got " + warnings.size());

        // count the execution paths
        GatherWarningsProgramBehaviour simulation = new GatherWarningsProgramBehaviour(warnings::addAll);
        program.tick(ProgramContext.createSimulationContext(
                program,
                labelPositionHolder,
                0,
                simulation
        ));
        assertTrue(simulation.getSeenPaths().size() == 1, "expected single execution path");
        assertTrue(simulation.getSeenPaths().get(0).history().size() == 2, "expected two elements in execution path");
        helper.succeed();
    }

    @GameTest(template = "3x2x1", batch = "linting")
    public static void count_execution_paths_2(GameTestHelper helper) {
        // place inventories
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        // place manager
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // set the labels
        LabelPositionHolder labelPositionHolder = LabelPositionHolder.empty()
                .add("left", helper.absolutePos(leftPos))
                .add("right", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        // load the program
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM left
                                           OUTPUT TO right
                                       END
                                       EVERY 20 TICKS DO
                                           INPUT FROM left
                                           OUTPUT TO right
                                           OUTPUT TO right
                                       END
                                   """.stripTrailing().stripIndent());
        assertManagerRunning(manager);
        var program = manager.getProgram();

        // ensure no warnings
        var warnings = DiskItem.getWarnings(Objects.requireNonNull(manager.getDisk()));
        assertTrue(warnings.isEmpty(), "expected 0 warning, got " + warnings.size());

        // count the execution paths
        GatherWarningsProgramBehaviour simulation = new GatherWarningsProgramBehaviour(warnings::addAll);
        program.tick(ProgramContext.createSimulationContext(
                program,
                labelPositionHolder,
                0,
                simulation
        ));
        assertTrue(simulation.getSeenPaths().size() == 2, "expected single execution path");
        assertTrue(simulation.getSeenPaths().get(0).history().size() == 2, "expected two elements in execution path");
        assertTrue(simulation.getSeenPaths().get(1).history().size() == 3, "expected two elements in execution path");
        helper.succeed();
    }

    @GameTest(template = "3x2x1", batch = "linting")
    public static void count_execution_paths_3(GameTestHelper helper) {
        // place inventories
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        // place manager
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // set the labels
        LabelPositionHolder labelPositionHolder = LabelPositionHolder.empty()
                .add("left", helper.absolutePos(leftPos))
                .add("right", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        // load the program
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM left
                                           OUTPUT TO right
                                       END
                                       EVERY 20 TICKS DO
                                           INPUT FROM left
                                           INPUT FROM left
                                           OUTPUT TO right
                                           OUTPUT TO right
                                       END
                                       EVERY 20 TICKS DO
                                           INPUT FROM left
                                           INPUT FROM left
                                           OUTPUT TO right
                                       END
                                   """.stripTrailing().stripIndent());
        assertManagerRunning(manager);
        var program = manager.getProgram();

        // ensure no warnings
        var warnings = DiskItem.getWarnings(Objects.requireNonNull(manager.getDisk()));
        assertTrue(warnings.isEmpty(), "expected 0 warning, got " + warnings.size());

        // count the execution paths
        GatherWarningsProgramBehaviour simulation = new GatherWarningsProgramBehaviour(warnings::addAll);
        program.tick(ProgramContext.createSimulationContext(
                program,
                labelPositionHolder,
                0,
                simulation
        ));
        assertTrue(simulation.getSeenPaths().size() == 3, "expected single execution path");
        assertTrue(simulation.getSeenPaths().get(0).history().size() == 2, "expected two elements in execution path");
        assertTrue(simulation.getSeenPaths().get(1).history().size() == 4, "expected two elements in execution path");
        assertTrue(simulation.getSeenPaths().get(2).history().size() == 3, "expected two elements in execution path");
        helper.succeed();
    }

    @GameTest(template = "3x2x1", batch = "linting")
    public static void unused_io_warning_output_label_not_presnet_in_input(GameTestHelper helper) {
        // place inventories
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        // place manager
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        LabelPositionHolder.empty()
                .add("bruh", helper.absolutePos(leftPos))
                .save(Objects.requireNonNull(manager.getDisk()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           OUTPUT TO bruh
                                       END
                                   """.stripTrailing().stripIndent());
        assertManagerRunning(manager);

        // assert expected warnings
        var warnings = DiskItem.getWarnings(Objects.requireNonNull(manager.getDisk()));
        assertTrue(warnings.size() == 1, "expected 1 warning, got " + warnings.size());
        assertTrue(warnings
                           .get(0)
                           .getKey()
                           .equals(LocalizationKeys.PROGRAM_WARNING_OUTPUT_RESOURCE_TYPE_NOT_FOUND_IN_INPUTS
                                           .key()
                                           .get()), "expected output without matching input warning");
        helper.succeed();
    }

    @GameTest(template = "3x2x1", batch = "linting")
    public static void unused_io_warning_input_label_not_present_in_output(GameTestHelper helper) {
        // place inventories
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        // place manager
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        LabelPositionHolder.empty()
                .add("left", helper.absolutePos(leftPos))
                .save(Objects.requireNonNull(manager.getDisk()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT FROM left
                                       END
                                   """.stripTrailing().stripIndent());
        assertManagerRunning(manager);

        // assert expected warnings
        var warnings = DiskItem.getWarnings(Objects.requireNonNull(manager.getDisk()));
        assertTrue(warnings.size() == 1, "expected 1 warning, got " + warnings.size());
        assertTrue(warnings
                           .get(0)
                           .getKey()
                           .equals(LocalizationKeys.PROGRAM_WARNING_UNUSED_INPUT_LABEL // should be unused input
                                           .key()
                                           .get()), "expected output without matching input warning");
        helper.succeed();
    }


    @GameTest(template = "7x3x3")
    public static void regression_input_retain_b_shared_shared(GameTestHelper helper) {
        BlockPos managerPos = new BlockPos(1, 2, 1);
        BlockPos aPos = new BlockPos(2, 2, 1);
        BlockPos b1Pos = new BlockPos(4, 2, 1);
        BlockPos b2Pos = new BlockPos(5, 2, 1);
        BlockPos b3Pos = new BlockPos(6, 2, 1);

        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        helper.setBlock(aPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(b1Pos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(b2Pos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(b3Pos, SFMBlocks.TEST_BARREL_BLOCK.get());

        for (int i = 0; i < 6; i++) {
            helper.setBlock(new BlockPos(1 + i, 2, 2), SFMBlocks.CABLE_BLOCK.get());
        }

        var a = getItemHandler(helper, aPos);
        var b1 = getItemHandler(helper, b1Pos);
        var b2 = getItemHandler(helper, b2Pos);
        var b3 = getItemHandler(helper, b3Pos);

        for (int i = 0; i < 5; i++) {
            b1.insertItem(i, new ItemStack(Items.DIRT, 64), false);
            b2.insertItem(i, new ItemStack(Items.DIRT, 64), false);
            b3.insertItem(i, new ItemStack(Items.DIRT, 64), false);
        }

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT RETAIN 5 FROM b
                                           OUTPUT TO a
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(aPos))
                .add("b", helper.absolutePos(b1Pos))
                .add("b", helper.absolutePos(b2Pos))
                .add("b", helper.absolutePos(b3Pos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // There should be exactly 5 dirt across all b
            // The rest should be in a
            assertTrue(count(a, Items.DIRT) == 64 * 3 * 5 - 5, "dirt should arrive in a");
            int bDirt = count(b1, Items.DIRT) + count(b2, Items.DIRT) + count(b3, Items.DIRT);
            assertTrue(bDirt == 5, "dirt should depart from b");
        });
    }

    @GameTest(template = "7x3x3")
    public static void regression_input_retain_b_shared_expanded(GameTestHelper helper) {
        BlockPos managerPos = new BlockPos(1, 2, 1);
        BlockPos aPos = new BlockPos(2, 2, 1);
        BlockPos b1Pos = new BlockPos(4, 2, 1);
        BlockPos b2Pos = new BlockPos(5, 2, 1);
        BlockPos b3Pos = new BlockPos(6, 2, 1);

        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        helper.setBlock(aPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(b1Pos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(b2Pos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(b3Pos, SFMBlocks.TEST_BARREL_BLOCK.get());

        for (int i = 0; i < 6; i++) {
            helper.setBlock(new BlockPos(1 + i, 2, 2), SFMBlocks.CABLE_BLOCK.get());
        }

        var a = getItemHandler(helper, aPos);
        var b1 = getItemHandler(helper, b1Pos);
        var b2 = getItemHandler(helper, b2Pos);
        var b3 = getItemHandler(helper, b3Pos);

        for (int i = 0; i < 5; i++) {
            b1.insertItem(i, new ItemStack(Items.DIRT, 64), false);
            b2.insertItem(i, new ItemStack(Items.DIRT, 64), false);
            b3.insertItem(i, new ItemStack(Items.DIRT, 64), false);
        }

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT RETAIN 5 EACH FROM b
                                           OUTPUT TO a
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(aPos))
                .add("b", helper.absolutePos(b1Pos))
                .add("b", helper.absolutePos(b2Pos))
                .add("b", helper.absolutePos(b3Pos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // There should be exactly 5 dirt across all b
            // The rest should be in a
            assertTrue(count(a, Items.DIRT) == 64 * 3 * 5 - 5, "dirt should arrive in a");
            int bDirt = count(b1, Items.DIRT) + count(b2, Items.DIRT) + count(b3, Items.DIRT);
            assertTrue(bDirt == 5, "dirt should depart from b");
        });
    }

    @GameTest(template = "7x3x3")
    public static void regression_input_retain_b_expanded_shared(GameTestHelper helper) {
        BlockPos managerPos = new BlockPos(1, 2, 1);
        BlockPos aPos = new BlockPos(2, 2, 1);
        BlockPos b1Pos = new BlockPos(4, 2, 1);
        BlockPos b2Pos = new BlockPos(5, 2, 1);
        BlockPos b3Pos = new BlockPos(6, 2, 1);

        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        helper.setBlock(aPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(b1Pos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(b2Pos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(b3Pos, SFMBlocks.TEST_BARREL_BLOCK.get());

        for (int i = 0; i < 6; i++) {
            helper.setBlock(new BlockPos(1 + i, 2, 2), SFMBlocks.CABLE_BLOCK.get());
        }

        var a = getItemHandler(helper, aPos);
        var b1 = getItemHandler(helper, b1Pos);
        var b2 = getItemHandler(helper, b2Pos);
        var b3 = getItemHandler(helper, b3Pos);

        for (int i = 0; i < 5; i++) {
            b1.insertItem(i, new ItemStack(Items.DIRT, 64), false);
            b2.insertItem(i, new ItemStack(Items.DIRT, 64), false);
            b3.insertItem(i, new ItemStack(Items.DIRT, 64), false);
        }

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT 9999 EACH RETAIN 5 FROM b
                                           OUTPUT TO a
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(aPos))
                .add("b", helper.absolutePos(b1Pos))
                .add("b", helper.absolutePos(b2Pos))
                .add("b", helper.absolutePos(b3Pos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // There should be exactly 5 dirt across all b
            // The rest should be in a
            assertTrue(count(a, Items.DIRT) == 64 * 3 * 5 - 5, "dirt should arrive in a");
            int bDirt = count(b1, Items.DIRT) + count(b2, Items.DIRT) + count(b3, Items.DIRT);
            assertTrue(bDirt == 5, "dirt should depart from b");
        });
    }

    @GameTest(template = "7x3x3")
    public static void regression_input_retain_b_expanded_expanded(GameTestHelper helper) {
        BlockPos managerPos = new BlockPos(1, 2, 1);
        BlockPos aPos = new BlockPos(2, 2, 1);
        BlockPos b1Pos = new BlockPos(4, 2, 1);
        BlockPos b2Pos = new BlockPos(5, 2, 1);
        BlockPos b3Pos = new BlockPos(6, 2, 1);

        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        helper.setBlock(aPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(b1Pos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(b2Pos, SFMBlocks.TEST_BARREL_BLOCK.get());
        helper.setBlock(b3Pos, SFMBlocks.TEST_BARREL_BLOCK.get());

        for (int i = 0; i < 6; i++) {
            helper.setBlock(new BlockPos(1 + i, 2, 2), SFMBlocks.CABLE_BLOCK.get());
        }

        var a = getItemHandler(helper, aPos);
        var b1 = getItemHandler(helper, b1Pos);
        var b2 = getItemHandler(helper, b2Pos);
        var b3 = getItemHandler(helper, b3Pos);

        for (int i = 0; i < 5; i++) {
            b1.insertItem(i, new ItemStack(Items.DIRT, 64), false);
            b2.insertItem(i, new ItemStack(Items.DIRT, 64), false);
            b3.insertItem(i, new ItemStack(Items.DIRT, 64), false);
        }

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT 9999 EACH RETAIN 5 EACH FROM b
                                           OUTPUT TO a
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(aPos))
                .add("b", helper.absolutePos(b1Pos))
                .add("b", helper.absolutePos(b2Pos))
                .add("b", helper.absolutePos(b3Pos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // There should be exactly 5 dirt across all b
            // The rest should be in a
            assertTrue(count(a, Items.DIRT) == 64 * 3 * 5 - 5, "dirt should arrive in a");
            int bDirt = count(b1, Items.DIRT) + count(b2, Items.DIRT) + count(b3, Items.DIRT);
            assertTrue(bDirt == 5, "dirt should depart from b");
        });
    }

    @GameTest(template = "3x2x1")
    public static void move_using_or(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Blocks.DIRT, 64), false);
        leftChest.insertItem(1, new ItemStack(Blocks.STONE, 64), false);
        leftChest.insertItem(2, new ItemStack(Blocks.COBBLESTONE, 64), false);
        leftChest.insertItem(3, new ItemStack(Blocks.COBBLESTONE, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT
                                               5 stone or dirt,
                                               cobblestone FROM a
                                           OUTPUT TO b
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // count of stone + dirt in left must be 64*2-5
            int leftStoneDirt = count(leftChest, Items.STONE) + count(leftChest, Items.DIRT);
            assertTrue(leftStoneDirt == 64 * 2 - 5, "stone and dirt should depart");
            // count of stone + dirt in right must be 5
            int rightStoneDirt = count(rightChest, Items.STONE) + count(rightChest, Items.DIRT);
            assertTrue(rightStoneDirt == 5, "stone and dirt should arrive");
            // left cobblestone count = 0
            assertTrue(count(leftChest, Items.COBBLESTONE) == 0, "no cobblestone should remain");
            // right cobblestone count = 64*2
            assertTrue(count(rightChest, Items.COBBLESTONE) == 64 * 2, "cobblestone should arrive");
        });
    }

    @GameTest(template = "3x2x1")
    public static void move_using_each_or(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Blocks.DIRT, 64), false);
        leftChest.insertItem(1, new ItemStack(Blocks.STONE, 64), false);
        leftChest.insertItem(2, new ItemStack(Blocks.COBBLESTONE, 64), false);
        leftChest.insertItem(3, new ItemStack(Blocks.COBBLESTONE, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT
                                               5 each stone or dirt,
                                               cobblestone FROM a
                                           OUTPUT TO b
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // left dirt count = 64-5
            assertTrue(count(leftChest, Items.DIRT) == 64 - 5, "dirt should depart");
            // left stone count = 64-5
            assertTrue(count(leftChest, Items.STONE) == 64 - 5, "stone should depart");
            // right dirt count = 5
            assertTrue(count(rightChest, Items.DIRT) == 5, "dirt should arrive");
            // right stone count = 5
            assertTrue(count(rightChest, Items.STONE) == 5, "stone should arrive");
            // left cobblestone count = 0
            assertTrue(count(leftChest, Items.COBBLESTONE) == 0, "no cobblestone should remain");
            // right cobblestone count = 64*2
            assertTrue(count(rightChest, Items.COBBLESTONE) == 64 * 2, "cobblestone should arrive");
        });
    }

//    @GameTest(template = "7x3x3")
//    public static void tunnel_furnace(GameTestHelper helper) {
//        BlockPos hopperPos = new BlockPos(0, 4, 0);
//        helper.setBlock(hopperPos, Blocks.HOPPER);
//        BlockPos managerPos = new BlockPos(0, 3, 0);
//        helper.setBlock(managerPos, SFMBlocks.TUNNELLED_MANAGER_BLOCK.get());
//        BlockPos barrelPos = new BlockPos(0, 2, 0);
//        helper.setBlock(barrelPos, SFMBlocks.TEST_BARREL_BLOCK.get());
//
//        var hopper = getItemHandler(helper, hopperPos);
//        var barrel = getItemHandler(helper, barrelPos);
//
//        hopper.insertItem(0, new ItemStack(Blocks.DIRT, 1), false);
//
//        helper.runAfterDelay(8, () -> {
//            assertTrue(hopper.getStackInSlot(0).isEmpty(), "Dirt did not move");
//            assertTrue(barrel.getStackInSlot(0).getCount() == 1, "Dirt did not move");
//        });
//
//
//        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
//        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
//        manager.setProgram("""
//                                       EVERY 20 TICKS DO
//                                           INPUT FROM barrel
//                                           OUTPUT TO hopper
//                                       END
//                                   """.stripTrailing().stripIndent());
//
//        // set the labels
//        LabelPositionHolder.empty()
//                .add("barrel", helper.absolutePos(barrelPos))
//                .add("hopper", helper.absolutePos(hopperPos))
//                .save(Objects.requireNonNull(manager.getDisk()));
//
//        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
//            assertTrue(hopper.getStackInSlot(0).getCount() == 1, "Dirt did not move");
//            assertTrue(barrel.getStackInSlot(0).isEmpty(), "Dirt did not move");
//        });
//    }

    @GameTest(template = "1x1x1")
    public static void inv_wrapper_investigation(GameTestHelper helper) {
        try {
            for (int stackSize : new int[]{200, 64}) {
                InvWrapper inv = new InvWrapper(new SimpleContainer(1));
                ItemStack insertParam = new ItemStack(Items.DIRT, stackSize);
                ItemStack insertParamCopy = insertParam.copy();
                ItemStack ignoredInsertResult = inv.insertItem(0, insertParam, false);
                assertTrue(
                        SFMItemUtils.isSameItemSameAmount(insertParam, insertParamCopy),
                        "stackSize="
                        + stackSize
                        + " insert param should not be modified after insertion, is now "
                        + insertParam
                );
                assertTrue(
                        inv.getStackInSlot(0) != insertParam,
                        "stackSize="
                        + stackSize
                        + " the inventory shouldn't take ownership of the reference after insertion"
                );
                ItemStack extractResult = inv.extractItem(0, stackSize, false);
                assertTrue(
                        SFMItemUtils.isSameItemSameAmount(insertParam, insertParamCopy),
                        "stackSize="
                        + stackSize
                        + " insert param should not be modified after extraction, is now "
                        + insertParam
                );
                assertTrue(
                        SFMItemUtils.isSameItemSameAmount(insertParam, extractResult),
                        "stackSize=" + stackSize + " extract result should match insertion param"
                );
            }
        } catch (GameTestAssertException e) {
            helper.succeed();
            // we expect this to fail because it is taking ownership on insertion when stack fits in slot
            // this isn't correct behaviour but we have to succeed the test when our expectations are met
        }
    }

    private static void falling_anvil_xp_shard_inner(
            GameTestHelper helper,
            int numBooks,
            LevelsToShards configToRestore,
            Vec3 pos,
            ItemStack enchBook,
            Iterator<Pair<LevelsToShards, Integer>> iter
    ) {
        if (!iter.hasNext()) {
            // restore config to value before the test
            SFMConfig.SERVER.levelsToShards.set(configToRestore);
            helper.succeed();
            return;
        }
        var c = iter.next();

        SFMConfig.SERVER.levelsToShards.set(c.first());
        // kill old item entities
        helper
                .getLevel()
                .getEntitiesOfClass(ItemEntity.class, new AABB(helper.absolutePos(new BlockPos(1, 4, 1))).inflate(3))
                .forEach(Entity::discard);

        for (int i = 0; i < numBooks; i++) {
            helper
                    .getLevel()
                    .addFreshEntity(new ItemEntity(
                            helper.getLevel(),
                            pos.x, pos.y, pos.z,
                            enchBook,
                            0, 0, 0
                    ));
        }

        helper.setBlock(new BlockPos(1, 3, 1), Blocks.AIR);
        helper.setBlock(new BlockPos(1, 4, 1), Blocks.ANVIL);

        helper.runAfterDelay(20, () -> {
            List<ItemEntity> found = helper
                    .getLevel()
                    .getEntitiesOfClass(
                            ItemEntity.class,
                            new AABB(helper.absolutePos(new BlockPos(1, 4, 1))).inflate(3)
                    );
            assertTrue(
                    found.stream().allMatch(e -> e.getItem().is(SFMItems.EXPERIENCE_SHARD_ITEM.get())),
                    "should only be xp shards"
            );

            var cnt = found.stream().mapToInt(e -> e.getItem().getCount()).sum();
            assertTrue(
                    cnt == c.second(),
                    "bad count for " + c.first().name() + ": expected " + c.second() + " but got " + cnt
            );

            falling_anvil_xp_shard_inner(helper, numBooks, configToRestore, pos, enchBook, iter);
        });
    }

    @GameTest(template = "3x2x1")
    public static void casing_rules(GameTestHelper helper) {
        helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
        BlockPos rightPos = new BlockPos(0, 2, 0);
        helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
        BlockPos leftPos = new BlockPos(2, 2, 0);
        helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());

        var rightChest = getItemHandler(helper, rightPos);
        var leftChest = getItemHandler(helper, leftPos);

        leftChest.insertItem(0, new ItemStack(Blocks.DIRT, 64), false);

        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0));
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                       EVERY 20 TICKS DO
                                           INPUT *DiRt* FROM a
                                           OUTPUT TO b
                                       END
                                   """.stripTrailing().stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(leftChest.getStackInSlot(0).isEmpty(), "Dirt did not move");
            assertTrue(rightChest.getStackInSlot(0).getCount() == 64, "Dirt did not move");
        });
    }
}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\declarative\TestBlockDef.java

package ca.teamdman.sfm.gametest.declarative;

import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.jetbrains.annotations.Nullable;

import java.util.function.Consumer;

public record TestBlockDef<T extends BlockEntity>(
        String label,
        BlockPos posRelativeToManager,
        Block block,
        @Nullable Consumer<T> blockEntityConfigurer
) {
    public static <U extends BlockEntity> TestBlockDef<U> of(
            String label,
            BlockPos posRelativeToManager,
            Block block,
            @Nullable Consumer<U> blockEntityConfigurer
    ) {
        return new TestBlockDef<>(label, posRelativeToManager, block, blockEntityConfigurer);
    }

    public static TestBlockDef<?> of(
            String label,
            BlockPos posRelativeToManager,
            Block block
    ) {
        return new TestBlockDef<>(label, posRelativeToManager, block, null);
    }
}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\compat\multiple\SFMThermalMekanismGameTests.java

package ca.teamdman.sfm.gametest.compat.multiple;

import ca.teamdman.sfm.SFM;
import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity;
import ca.teamdman.sfm.common.logging.TranslatableLogEvent;
import ca.teamdman.sfm.common.program.LabelPositionHolder;
import ca.teamdman.sfm.common.registry.SFMBlocks;
import ca.teamdman.sfm.common.registry.SFMItems;
import ca.teamdman.sfm.gametest.SFMGameTestBase;
import cofh.thermal.expansion.block.entity.machine.MachineInsolatorTile;
import mekanism.common.registries.MekanismBlocks;
import mekanism.common.tile.TileEntityBin;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Blocks;
import net.minecraftforge.gametest.GameTestHolder;
import net.minecraftforge.registries.ForgeRegistries;
import org.apache.logging.log4j.Level;

import java.util.ArrayDeque;
import java.util.Objects;

@GameTestHolder(SFM.MOD_ID)
public class SFMThermalMekanismGameTests extends SFMGameTestBase {
    @GameTest(template = "3x4x3")
    public static void resource_loss_regression(GameTestHelper helper) {
        var managerPos = new BlockPos(1, 2, 1);
        var buttonPos = managerPos.offset(Direction.NORTH.getNormal());
        var leftPos = new BlockPos(2, 2, 1);
        var rightPos = new BlockPos(0, 2, 1);

        // place and fill the chests
        helper.setBlock(leftPos, MekanismBlocks.CREATIVE_BIN.getBlock());
        TileEntityBin bin = (TileEntityBin) helper.getBlockEntity(leftPos);
        assert bin != null;
        bin.setStackInSlot(0, new ItemStack(Items.WHEAT_SEEDS, Integer.MAX_VALUE));

        var phytoBlock = ForgeRegistries.BLOCKS.getValue(new ResourceLocation("thermal", "machine_insolator"));
        assert phytoBlock != null;
        helper.setBlock(rightPos, phytoBlock);
        MachineInsolatorTile phyto = (MachineInsolatorTile) helper.getBlockEntity(rightPos);
        assert phyto != null;
        phyto.getItemInv().set(0, new ItemStack(Items.WHEAT_SEEDS, 63));

        // create the manager block and add the disk
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
        assert manager != null;
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));

        // create the program
        var program = """
                    NAME "move on pulse"
                
                    EVERY REDSTONE PULSE DO
                        INPUT FROM left BOTTOM SIDE
                        OUTPUT TO right
                    END
                """.stripTrailing().stripIndent();

        // set the labels
        LabelPositionHolder.empty()
                .add("left", helper.absolutePos(leftPos))
                .add("right", helper.absolutePos(rightPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        // load the program
        manager.setProgram(program);
        manager.setLogLevel(Level.ERROR);
        assertTrue(manager.logger.getLogLevel() == Level.ERROR, "Log level should be trace");

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            ItemStack phytoInputStack = phyto.getItemInv().get(0);
            assertTrue(phytoInputStack.getItem() == Items.WHEAT_SEEDS, "Item should be wheat seeds");
            assertTrue(phytoInputStack.getCount() == 64, "Item should be 64 wheat seeds");

            ArrayDeque<TranslatableLogEvent> logs = manager.logger.getLogs();
            int foundErrors = 0;
            for (TranslatableLogEvent log : logs) {
                if (log.level() == Level.ERROR) {
                    foundErrors++;
                    SFM.LOGGER.warn("Found error when should be none in resource_loss_regression test: {}", log.contents());
                }
            }
            assertTrue(foundErrors == 0, "No errors should be found in logs, found " + foundErrors);
        });

        // create the button
        helper.setBlock(buttonPos, Blocks.STONE_BUTTON);
        // push the button
        helper.pressButton(buttonPos);
    }

}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\compat\multiple\SFMIndustrialForegoingMekanismGameTests.java

package ca.teamdman.sfm.gametest.compat.multiple;

import ca.teamdman.sfm.SFM;
import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity;
import ca.teamdman.sfm.common.program.LabelPositionHolder;
import ca.teamdman.sfm.common.registry.SFMBlocks;
import ca.teamdman.sfm.common.registry.SFMItems;
import ca.teamdman.sfm.gametest.SFMGameTestBase;
import com.buuz135.industrial.block.resourceproduction.tile.WashingFactoryTile;
import com.buuz135.industrial.module.ModuleCore;
import com.buuz135.industrial.module.ModuleResourceProduction;
import com.hrznstudio.titanium.block.tile.ActiveTile;
import com.hrznstudio.titanium.client.screen.addon.FacingHandlerScreenAddon;
import com.hrznstudio.titanium.component.fluid.MultiTankComponent;
import com.hrznstudio.titanium.component.fluid.SidedFluidTankComponent;
import com.hrznstudio.titanium.component.sideness.IFacingComponent;
import com.hrznstudio.titanium.util.FacingUtil;
import mekanism.api.fluid.IExtendedFluidTank;
import mekanism.common.registries.MekanismBlocks;
import mekanism.common.tile.TileEntityFluidTank;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.gametest.GameTestHolder;

import java.lang.reflect.Field;
import java.util.Objects;

@SuppressWarnings("OptionalGetWithoutIsPresent")
@GameTestHolder(SFM.MOD_ID)
public class SFMIndustrialForegoingMekanismGameTests extends SFMGameTestBase {
    @SuppressWarnings("DanglingJavadoc")
    @GameTest(template = "3x4x3", timeoutTicks = 200)
    public static void meat_fluid(GameTestHelper helper) {
        BlockPos managerPos = new BlockPos(1, 2, 0);
        BlockPos leftTankPos = new BlockPos(2, 2, 0);
        BlockPos rightTankPos = new BlockPos(0, 2, 0);
        BlockPos washingFactoryPos = new BlockPos(2, 2, 1);

        // set up the tanks
        helper.setBlock(leftTankPos, MekanismBlocks.BASIC_FLUID_TANK.getBlock());
        helper.setBlock(rightTankPos, MekanismBlocks.BASIC_FLUID_TANK.getBlock());
        TileEntityFluidTank rightTank = (TileEntityFluidTank) helper.getBlockEntity(rightTankPos);
        assert rightTank != null;
        IExtendedFluidTank fluidTank = rightTank.getFluidTank(0, Direction.DOWN);
        assert fluidTank != null;

        // set up the washing factory
        helper.setBlock(washingFactoryPos, ModuleResourceProduction.WASHING_FACTORY.getLeft().get());
        WashingFactoryTile washingFactory = (WashingFactoryTile) helper.getBlockEntity(washingFactoryPos);
        assert washingFactory != null;

        // configure it to push front
        /**
         * {@link FacingHandlerScreenAddon#mouseClicked}
         * {@link ActiveTile#handleButtonMessage(int, Player, CompoundTag)}
         * {@link MultiTankComponent#handleFacingChange(String, FacingUtil.Sideness, int)}
         */
        CompoundTag click = new CompoundTag();
        click.putString("Name", "output");
        click.putString("Facing", FacingUtil.Sideness.FRONT.name());
//        click.putInt("Next", IFacingComponent.FaceMode.PUSH.getIndex());
        try {
            Field multiTankComponentField = ActiveTile.class.getDeclaredField("multiTankComponent");
            multiTankComponentField.setAccessible(true);
            MultiTankComponent<?> multiTankComponent = (MultiTankComponent<?>) multiTankComponentField.get(
                    washingFactory);
            SidedFluidTankComponent<?> outputTank = (SidedFluidTankComponent<?>) multiTankComponent
                    .getTanks()
                    .stream()
                    .filter(tank -> tank.getName().equals("output"))
                    .findFirst()
                    .get();
            outputTank.getFacingModes().put(FacingUtil.Sideness.BOTTOM, IFacingComponent.FaceMode.NONE);
            IFacingComponent.FaceMode[] validFacingModes = outputTank.getValidFacingModes();
            for (int i = 0; i < validFacingModes.length; i++) {
                if (validFacingModes[i] == IFacingComponent.FaceMode.PUSH) {
                    click.putInt("Next", i);
                    break;
                }
            }
            if (!click.contains("Next")) {
                throw new RuntimeException("No valid facing mode found");
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        //noinspection DataFlowIssue
        washingFactory.handleButtonMessage(-1, null, click);

        // add some power
        washingFactory
                .getCapability(ForgeCapabilities.ENERGY)
                .resolve()
                .get().receiveEnergy(Integer.MAX_VALUE, false);

        // add some meat
        washingFactory.getCapability(ForgeCapabilities.FLUID_HANDLER)
                .resolve().get().fill(
                        new FluidStack(ModuleCore.MEAT.getSourceFluid().get(), Integer.MAX_VALUE),
                        IFluidHandler.FluidAction.EXECUTE
                );

        // add some iron
        washingFactory
                .getCapability(ForgeCapabilities.ITEM_HANDLER, Direction.UP)
                .resolve()
                .get()
                .insertItem(0, new ItemStack(Items.RAW_IRON, 64), false);

        // place the manager
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        ManagerBlockEntity manager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));
        assert manager != null;
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   NAME "bruh"
                                   EVERY 20 TICKS DO
                                    INPUT fluid:: FROM a BOTTOM SIDE
                                    OUTPUT fluid:: TO b TOP SIDE
                                   END
                                   """.stripTrailing().stripIndent());
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftTankPos))
                .add("b", helper.absolutePos(rightTankPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        helper.succeedWhen(() -> {
            if (fluidTank.getFluidAmount() < 100) {
                helper.fail("Fluid tank did not receive fluid");
            }
        });
    }

    @GameTest(template = "3x4x3", timeoutTicks = 200)
    public static void meat_fluid_direct(GameTestHelper helper) {
        BlockPos managerPos = new BlockPos(1, 2, 1);
        BlockPos rightTankPos = new BlockPos(0, 2, 1);
        BlockPos washingFactoryPos = new BlockPos(2, 2, 1);

        // set up the tanks
        helper.setBlock(rightTankPos, MekanismBlocks.BASIC_FLUID_TANK.getBlock());
        TileEntityFluidTank rightTank = (TileEntityFluidTank) helper.getBlockEntity(rightTankPos);
        assert rightTank != null;
        IExtendedFluidTank fluidTank = rightTank.getFluidTank(0, Direction.DOWN);
        assert fluidTank != null;

        // set up the washing factory
        helper.setBlock(washingFactoryPos, ModuleResourceProduction.WASHING_FACTORY.getLeft().get());
        WashingFactoryTile washingFactory = (WashingFactoryTile) helper.getBlockEntity(washingFactoryPos);
        assert washingFactory != null;

        // add some power
        washingFactory
                .getCapability(ForgeCapabilities.ENERGY)
                .resolve()
                .get().receiveEnergy(Integer.MAX_VALUE, false);

        // add some meat
        washingFactory.getCapability(ForgeCapabilities.FLUID_HANDLER)
                .resolve().get().fill(
                        new FluidStack(ModuleCore.MEAT.getSourceFluid().get(), Integer.MAX_VALUE),
                        IFluidHandler.FluidAction.EXECUTE
                );

        // add some iron
        washingFactory
                .getCapability(ForgeCapabilities.ITEM_HANDLER, Direction.UP)
                .resolve()
                .get()
                .insertItem(0, new ItemStack(Items.RAW_IRON, 64), false);

        // place the manager
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        ManagerBlockEntity manager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));
        assert manager != null;
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   NAME "bruh"
                                   EVERY 20 TICKS DO
                                    INPUT fluid:: FROM a BOTTOM SIDE
                                    OUTPUT fluid:: TO b TOP SIDE
                                   END
                                   """.stripTrailing().stripIndent());
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(washingFactoryPos))
                .add("b", helper.absolutePos(rightTankPos))
                .save(Objects.requireNonNull(manager.getDisk()));

        helper.succeedWhen(() -> {
            if (fluidTank.getFluidAmount() < 100) {
                helper.fail("Fluid tank did not receive fluid");
            }
        });
    }
}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\compat\industrialforegoing\SFMIndustrialForegoingCompatGameTests.java

package ca.teamdman.sfm.gametest.compat.industrialforegoing;

import ca.teamdman.sfm.SFM;
import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity;
import ca.teamdman.sfm.common.program.LabelPositionHolder;
import ca.teamdman.sfm.common.registry.SFMBlocks;
import ca.teamdman.sfm.common.registry.SFMItems;
import ca.teamdman.sfm.gametest.SFMGameTestBase;
import com.buuz135.industrial.module.ModuleCore;
import com.buuz135.industrial.module.ModuleTransportStorage;
import com.buuz135.industrial.utils.BlockUtils;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.gametest.GameTestHolder;

@SuppressWarnings({"DuplicatedCode", "DataFlowIssue"})
@GameTestHolder(SFM.MOD_ID)
public class SFMIndustrialForegoingCompatGameTests extends SFMGameTestBase {
    @GameTest(template = "3x2x1")
    public static void industrialforegoing_blackhole_empty(GameTestHelper helper) {
        // designate positions
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);

        // set up the world
        helper.setBlock(leftPos, ModuleTransportStorage.BLACK_HOLE_UNIT_SUPREME.getLeft().get());
        var left = helper.getBlockEntity(leftPos).getCapability(ForgeCapabilities.ITEM_HANDLER).orElse(null);
        helper.setBlock(rightPos, ModuleTransportStorage.BLACK_HOLE_UNIT_SUPREME.getLeft().get());
        var right = helper.getBlockEntity(rightPos).getCapability(ForgeCapabilities.ITEM_HANDLER).orElse(null);
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var manager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));

        // set up the program
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   EVERY 20 TICKS DO
                                     INPUT FROM a NORTH SIDE
                                     OUTPUT TO b TOP SIDE
                                   END
                                   """.stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        int fullCount = BlockUtils.getStackAmountByRarity(ModuleCore.SUPREME_RARITY);
        assertTrue(fullCount > 0, "expected full count to be greater than 0");
        assertTrue(left.insertItem(0, new ItemStack(Items.COAL, 64), false).isEmpty(), "couldn't prep left");
        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(left.getStackInSlot(0).isEmpty(), "Contents did not depart properly");
            assertTrue(right.getStackInSlot(0).getCount() == 64, "Contents did not arrive");
        });
    }


    @GameTest(template = "3x2x1")
    public static void industrialforegoing_blackhole_some(GameTestHelper helper) {
        // designate positions
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);

        // set up the world
        helper.setBlock(leftPos, ModuleTransportStorage.BLACK_HOLE_UNIT_SUPREME.getLeft().get());
        var left = helper.getBlockEntity(leftPos).getCapability(ForgeCapabilities.ITEM_HANDLER).orElse(null);
        helper.setBlock(rightPos, ModuleTransportStorage.BLACK_HOLE_UNIT_SUPREME.getLeft().get());
        var right = helper.getBlockEntity(rightPos).getCapability(ForgeCapabilities.ITEM_HANDLER).orElse(null);
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var manager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));

        // set up the program
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   EVERY 20 TICKS DO
                                     INPUT FROM a NORTH SIDE
                                     OUTPUT TO b TOP SIDE
                                   END
                                   """.stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        // we need to insert a normal stack last for the rendering to work in IF
        assertTrue(left.insertItem(0, new ItemStack(Items.COAL, 5000 - 64), false).isEmpty(), "couldn't prep left");
        assertTrue(left.insertItem(0, new ItemStack(Items.COAL, 64), false).isEmpty(), "couldn't prep left");
        assertTrue(right.insertItem(0, new ItemStack(Items.COAL, 5000 - 64), false).isEmpty(), "couldn't prep left");
        assertTrue(right.insertItem(0, new ItemStack(Items.COAL, 64), false).isEmpty(), "couldn't prep right");

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            assertTrue(left.getStackInSlot(0).getCount() == 5_000 - 64, "Contents did not depart properly");
            assertTrue(right.getStackInSlot(0).getCount() == 5_000 + 64, "Contents did not arrive");
            
        });
    }


    @GameTest(template = "3x2x1")
    public static void industrialforegoing_blackhole_full(GameTestHelper helper) {
        // designate positions
        var leftPos = new BlockPos(2, 2, 0);
        var rightPos = new BlockPos(0, 2, 0);
        var managerPos = new BlockPos(1, 2, 0);

        // set up the world
        helper.setBlock(leftPos, ModuleTransportStorage.BLACK_HOLE_UNIT_SUPREME.getLeft().get());
        var left = helper.getBlockEntity(leftPos).getCapability(ForgeCapabilities.ITEM_HANDLER).orElse(null);
        helper.setBlock(rightPos, ModuleTransportStorage.BLACK_HOLE_UNIT_SUPREME.getLeft().get());
        var right = helper.getBlockEntity(rightPos).getCapability(ForgeCapabilities.ITEM_HANDLER).orElse(null);
        helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
        var manager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));

        // set up the program
        manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
        manager.setProgram("""
                                   EVERY 20 TICKS DO
                                     INPUT FROM a NORTH SIDE
                                     OUTPUT TO b TOP SIDE
                                   END
                                   """.stripIndent());

        // set the labels
        LabelPositionHolder.empty()
                .add("a", helper.absolutePos(leftPos))
                .add("b", helper.absolutePos(rightPos))
                .save(manager.getDisk());

        int fullCount = BlockUtils.getStackAmountByRarity(ModuleCore.SUPREME_RARITY);
        assertTrue(fullCount > 0, "expected full count to be greater than 0");
        assertTrue(left.insertItem(0, new ItemStack(Items.COAL, 64), false).isEmpty(), "couldn't prep left");
        assertTrue(left.insertItem(0, new ItemStack(Items.COAL, 1), false).isEmpty(), "couldn't prep left");
        assertTrue(
                right.insertItem(0, new ItemStack(Items.COAL, fullCount - 2), false).isEmpty(),
                "couldn't prep right"
        );
        assertTrue(right.insertItem(0, new ItemStack(Items.COAL, 1), false).isEmpty(), "couldn't prep right");

        succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
            // black hole units have voiding=true by default
            // the final insertion operation that causes the thing to fill will cause the items that don't fit to be voided
            // this test should cause a stack of 64 to be used to insert even though only 1 is needed to finish filling
            assertTrue(left.getStackInSlot(0).getCount() == 1, "Contents did not depart properly");
            assertTrue(right.getStackInSlot(0).getCount() == fullCount, "Contents did not arrive");
        });
    }

}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\SFMGameTestBase.java

package ca.teamdman.sfm.gametest;

import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity;
import ca.teamdman.sfm.common.item.DiskItem;
import ca.teamdman.sfm.common.program.ProgramContext;
import ca.teamdman.sfm.common.util.NotStored;
import ca.teamdman.sfml.ast.Block;
import ca.teamdman.sfml.ast.Program;
import ca.teamdman.sfml.ast.Trigger;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTestAssertException;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.Container;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.items.IItemHandler;
import org.jetbrains.annotations.Nullable;

import java.text.NumberFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.IntStream;
import java.util.stream.LongStream;

public abstract class SFMGameTestBase {

    public static ItemStack enchant(
            ItemStack stack,
            Enchantment enchantment,
            int level
    ) {
        EnchantmentHelper.setEnchantments(Map.of(enchantment, level), stack);
        return stack;
    }

    protected static void assertTrue(
            boolean condition,
            String message
    ) {
        if (!condition) {
            throw new GameTestAssertException(message);
        }
    }

    protected static Program compile(String code) {
        AtomicReference<Program> rtn = new AtomicReference<>();
        Program.compile(
                code,
                rtn::set,
                errors -> {
                    throw new GameTestAssertException("Failed to compile program: " + errors
                            .stream()
                            .map(Object::toString)
                            .reduce("", (a, b) -> a + "\n" + b));
                }
        );
        return rtn.get();
    }

    protected static void succeedIfManagerDidThingWithoutLagging(
            GameTestHelper helper,
            ManagerBlockEntity manager,
            Runnable assertion
    ) {
        // a nice thing about this method is that you can change a program from
        // EVERY 20 TICKS DO
        // to
        // EVERY REDSTONE PULSE DO
        // and it will patiently wait
        assertManagerDidThingWithoutLagging(
                helper,
                manager,
                assertion,
                helper::succeed
        );
    }

    protected static void assertManagerDidThingWithoutLagging(
            GameTestHelper helper,
            ManagerBlockEntity manager,
            Runnable assertion,
            Runnable onSuccess
    ) {
        SFMGameTestBase.assertManagerRunning(manager); // the program should already be compiled so we can monkey patch it
        manager.enableRebuildProgramLock();
        var hasExecuted = new AtomicBoolean(false);
        var startTime = new AtomicLong();
        var endTime = new AtomicLong();
        List<Trigger> triggers = Objects.requireNonNull(manager.getProgram()).triggers();
        var oldFirstTrigger = triggers.get(0);
        long timeoutTicks = 200;

        Trigger startTimerTrigger = new Trigger() {
            @Override
            public boolean shouldTick(ProgramContext context) {
                return oldFirstTrigger != null
                       ? oldFirstTrigger.shouldTick(context)
                       : context.getManager().getTick() % 20 == 0;
            }

            @Override
            public void tick(ProgramContext context) {
                startTime.set(System.nanoTime());
            }

            @Override
            public Block getBlock() {
                return new Block(Collections.emptyList());
            }
        };

        Trigger endTimerTrigger = new Trigger() {
            @Override
            public boolean shouldTick(ProgramContext context) {
                return oldFirstTrigger != null
                       ? oldFirstTrigger.shouldTick(context)
                       : context.getManager().getTick() % 20 == 0;
            }

            @Override
            public void tick(ProgramContext context) {
                if (!hasExecuted.get()) {
                    hasExecuted.set(true);
                    endTime.set(System.nanoTime());
                }
            }

            @Override
            public Block getBlock() {
                return new Block(Collections.emptyList());
            }
        };

        triggers.add(0, startTimerTrigger);
        triggers.add(endTimerTrigger);


        LongStream
                .range(helper.getTick() + 1, timeoutTicks - helper.getTick())
                .forEach(i -> helper.runAfterDelay(i, () -> {
                    if (hasExecuted.get()) {
                        triggers.remove(startTimerTrigger);
                        triggers.remove(endTimerTrigger);
                        assertion.run();
                        SFMGameTestBase.assertTrue(
                                endTime.get() - startTime.get() < 80_000_000,
                                "Program took too long to run: took " + NumberFormat
                                        .getInstance(Locale.getDefault())
                                        .format(endTime.get() - startTime.get()) + "ns"
                        );
                        hasExecuted.set(false); // prevent the assertion from running again
                        onSuccess.run();
                    }
                }));
    }

    protected static void assertManagerRunning(ManagerBlockEntity manager) {
        SFMGameTestBase.assertTrue(manager.getDisk() != null, "No disk in manager");
        SFMGameTestBase.assertTrue(
                manager.getState() == ManagerBlockEntity.State.RUNNING,
                "Program did not start running " + DiskItem.getErrors(manager.getDisk())
        );
    }

    protected static int count(
            Container chest,
            @Nullable Item item
    ) {
        return IntStream.range(0, chest.getContainerSize())
                .mapToObj(chest::getItem)
                .filter(stack -> item == null || stack.getItem() == item)
                .mapToInt(ItemStack::getCount)
                .sum();
    }

    protected static int count(
            IItemHandler chest,
            @Nullable Item item
    ) {
        return IntStream.range(0, chest.getSlots())
                .mapToObj(chest::getStackInSlot)
                .filter(stack -> item == null || stack.getItem() == item)
                .mapToInt(ItemStack::getCount)
                .sum();
    }

    protected static IItemHandler getItemHandler(
            GameTestHelper helper,
            @NotStored BlockPos pos
    ) {
        BlockEntity blockEntity = helper
                .getBlockEntity(pos);
        SFMGameTestBase.assertTrue(blockEntity != null, "No block entity found at " + pos);
        Optional<IItemHandler> found = blockEntity
                .getCapability(ForgeCapabilities.ITEM_HANDLER)
                .resolve();
        SFMGameTestBase.assertTrue(found.isPresent(), "No item handler found at " + pos);
        return found.get();
    }
}

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\gametest\java\ca\teamdman\sfm\gametest\compat\mekanism\package-info.java

@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
@FieldsAreNonnullByDefault
package ca.teamdman.sfm.gametest.compat.mekanism;

import net.minecraft.FieldsAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;

import javax.annotation.ParametersAreNonnullByDefault;

Summary

D:\Repos\Minecraft\SFM\repos\SuperFactoryManager 1.19.2\src\main\antlr\sfml\SFML.g4

grammar SFML;
@header {
package ca.teamdman.langs;
}
@lexer::members {
    public boolean INCLUDE_UNUSED = false; // we want syntax highlighting to not break on unexpected tokens
}

program : name? trigger* EOF;

name: NAME string ;

//
// TRIGGERS
//

trigger : EVERY interval DO block END           #TimerTrigger
        | EVERY REDSTONE PULSE DO block END     #PulseTrigger
        ;

interval: NUMBER? GLOBAL? (PLUS NUMBER)? (TICKS | TICK | SECONDS | SECOND)      # IntervalSpace
        | NUMBER_WITH_G_SUFFIX (PLUS NUMBER)? (TICKS | TICK | SECONDS | SECOND) # IntervalNoSpace;

//
// BLOCK STATEMENT
//

block           : statement* ;
statement       : inputStatement
                | outputStatement
                | ifStatement
                | forgetStatement
                ;

// IO STATEMENT
forgetStatement : FORGET label? (COMMA label)* COMMA?;
inputStatement  : INPUT inputResourceLimits? resourceExclusion? FROM EACH? labelAccess
                | FROM EACH? labelAccess INPUT inputResourceLimits? resourceExclusion?
                ;
outputStatement : OUTPUT outputResourceLimits? resourceExclusion? TO EACH? labelAccess
                | TO EACH? labelAccess OUTPUT outputResourceLimits? resourceExclusion?
                ;

inputResourceLimits   : resourceLimitList; // separate for different defaults
outputResourceLimits  : resourceLimitList; // separate for different defaults

resourceLimitList  : resourceLimit (COMMA resourceLimit)* COMMA?;
resourceLimit   : limit? resourceIdDisjunction with?
                | limit with?
                | with
                ;
limit           : quantity retention    #QuantityRetentionLimit
                | retention             #RetentionLimit
                | quantity              #QuantityLimit
                ;

quantity        : number EACH?;
retention       : RETAIN number EACH?;

resourceExclusion       : EXCEPT resourceIdList;

resourceId      : (identifier) (COLON (identifier)? (COLON (identifier)? (COLON (identifier)?)?)?)? # Resource
                | string                                                                            # StringResource
                ;

resourceIdList          : resourceId (COMMA resourceId)* COMMA?;
resourceIdDisjunction   : resourceId (OR resourceId)* OR?;


with        : WITH withClause
            | WITHOUT withClause
            ;
withClause  : LPAREN withClause RPAREN           # WithParen
            | NOT withClause                     # WithNegation
            | withClause AND withClause          # WithConjunction
            | withClause OR withClause           # WithDisjunction
            | (TAG HASHTAG?|HASHTAG) tagMatcher  # WithTag
            ;

tagMatcher  : identifier COLON identifier (SLASH identifier)*
            | identifier (SLASH identifier)*
            ;


sidequalifier   : EACH SIDE                 #EachSide
                | side(COMMA side)* SIDE    #ListedSides
                ;

side            : TOP
                | BOTTOM
                | NORTH
                | EAST
                | SOUTH
                | WEST
                ;
slotqualifier   : SLOTS rangeset;
rangeset        : range (COMMA range)*;
range           : number (DASH number)? ;


ifStatement     : IF boolexpr THEN block (ELSE IF boolexpr THEN block)* (ELSE block)? END;
boolexpr        : TRUE                              #BooleanTrue
                | FALSE                             #BooleanFalse
                | LPAREN boolexpr RPAREN            #BooleanParen
                | NOT boolexpr                      #BooleanNegation
                | boolexpr AND boolexpr             #BooleanConjunction
                | boolexpr OR boolexpr              #BooleanDisjunction
                | setOp? labelAccess HAS comparisonOp number resourceIdDisjunction? with? (EXCEPT resourceIdList)?  #BooleanHas
                | REDSTONE (comparisonOp number)?   #BooleanRedstone
                ;

comparisonOp    : GT
                | LT
                | EQ
                | LE
                | GE
                | GT_SYMBOL
                | LT_SYMBOL
                | EQ_SYMBOL
                | LE_SYMBOL
                | GE_SYMBOL
                ;
setOp           : OVERALL
                | SOME
                | EVERY
                | EACH
                | ONE
                | LONE
                ;






//
// IO HELPERS
//
labelAccess     : label (COMMA label)* roundrobin? sidequalifier? slotqualifier?;
roundrobin      : ROUND ROBIN BY (LABEL | BLOCK);
label           : (identifier)   #RawLabel
                | string                  #StringLabel
                ;

identifier : (IDENTIFIER | REDSTONE | GLOBAL | SECOND | SECONDS) ;

// GENERAL
string: STRING ;
number: NUMBER ;



//
// LEXER
//

// IF STATEMENT
IF      : I F ;
THEN    : T H E N ;
ELSE    : E L S E ;

HAS     : H A S ;
OVERALL : O V E R A L L ;
SOME    : S O M E ;
ONE     : O N E ;
LONE    : L O N E ;

// BOOLEAN LOGIC
TRUE    : T R U E ;
FALSE   : F A L S E ;
NOT     : N O T ;
AND     : A N D ;
OR      : O R ;

// QUANTITY LOGIC
GT        : G T ;
GT_SYMBOL : '>' ;
LT        : L T ;
LT_SYMBOL : '<' ;
EQ        : E Q ;
EQ_SYMBOL : '=' ;
LE        : L E ;
LE_SYMBOL : '<=' ;
GE        : G E ;
GE_SYMBOL : '>=' ;

// IO LOGIC
FROM    : F R O M ;
TO      : T O ;
INPUT   : I N P U T ;
OUTPUT  : O U T P U T ;
WHERE   : W H E R E ;
SLOTS   : S L O T S ;
RETAIN  : R E T A I N ;
EACH    : E A C H ;
EXCEPT  : E X C E P T ;
FORGET  : F O R G E T ;

// WITH LOGIC
WITHOUT : W I T H O U T;
WITH    : W I T H ;
TAG     : T A G ;
HASHTAG : '#' ;

// ROUND ROBIN
ROUND : R O U N D ;
ROBIN : R O B I N ;
BY    : B Y ;
LABEL : L A B E L ;
BLOCK : B L O C K ;

// SIDE LOGIC
TOP     : T O P ;
BOTTOM  : B O T T O M ;
NORTH   : N O R T H ;
EAST    : E A S T ;
SOUTH   : S O U T H ;
WEST    : W E S T ;
SIDE    : S I D E ;


// TIMER TRIGGERS
TICKS   : T I C K S ;
TICK    : T I C K ;
SECONDS : S E C O N D S ;
SECOND  : S E C O N D ;
GLOBAL  : (G L O B A L) | G;
PLUS    : '+' | P L U S;

// REDSTONE TRIGGER
REDSTONE        : R E D S T O N E ;
PULSE           : P U L S E;

// PROGRAM SYMBOLS
DO              : D O ;
END             : E N D ;
NAME            : N A M E ;

// GENERAL SYMBOLS
// used by triggers and as a set operator
EVERY           : E V E R Y ;

COMMA   : ',';
COLON   : ':';
SLASH   : '/';
DASH    : '-';
LPAREN  : '(';
RPAREN  : ')';


NUMBER_WITH_G_SUFFIX    : [0-9]+[gG] ;
NUMBER                  : [0-9]+ ;
IDENTIFIER              : [a-zA-Z_*][a-zA-Z0-9_*]* | '*'; // Note that the * in the square brackets is a literl

STRING : '"' (~'"'|'\\"')* '"' ;

LINE_COMMENT : '--' ~[\r\n]* -> channel(HIDDEN);
//LINE_COMMENT : '--' ~[\r\n]* (EOF|'\r'? '\n');

WS
        :   [ \r\t\n]+ -> channel(HIDDEN)
        ;

UNUSED
        :   {INCLUDE_UNUSED}? . -> channel(HIDDEN)
        ;

fragment A  :('a' | 'A') ;
fragment B  :('b' | 'B') ;
fragment C  :('c' | 'C') ;
fragment D  :('d' | 'D') ;
fragment E  :('e' | 'E') ;
fragment F  :('f' | 'F') ;
fragment G  :('g' | 'G') ;
fragment H  :('h' | 'H') ;
fragment I  :('i' | 'I') ;
fragment J  :('j' | 'J') ;
fragment K  :('k' | 'K') ;
fragment L  :('l' | 'L') ;
fragment M  :('m' | 'M') ;
fragment N  :('n' | 'N') ;
fragment O  :('o' | 'O') ;
fragment P  :('p' | 'P') ;
fragment Q  :('q' | 'Q') ;
fragment R  :('r' | 'R') ;
fragment S  :('s' | 'S') ;
fragment T  :('t' | 'T') ;
fragment U  :('u' | 'U') ;
fragment V  :('v' | 'V') ;
fragment W  :('w' | 'W') ;
fragment X  :('x' | 'X') ;
fragment Y  :('y' | 'Y') ;
fragment Z  :('z' | 'Z') ;

===

===

That is a summary of my Minecraft mod, Super Factory Manager. I've included the transcripts of some videos I've made, the example programs, and the test code.

Please demonstrate to me your comprehension of the mod.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment