It took two days to build. It has been the thing I think about ever since. Fifty five commits on one file, and six months later it is still learning to walk.
A mob that does not chase you. Twice a second it scores seven possible things to do and picks whichever wins, and it will flatly refuse two or three of them.
Drag the marker in the simulator below and watch it change its mind in real time. Then read the seven ways it has embarrassed itself.
It commits to one of you and holds on. The one it holds is whoever is wearing the least armour, so putting a coat on hands its attention to your friend.
Most Minecraft mobs run on a short list of rules. See the player. Walk at the player. Hit the player.
The Architect doesn't do that. Roughly twice a second it stops and asks itself one question: given everything I know right now, what is the single best thing I could be doing?
It has seven possible answers. It gives every one of them a score, then does whichever scored highest. That is the entire idea. Everything else on this page is detail.
Those seven names are the real ones, straight out of the enum.
The scores themselves are boring arithmetic. The part that gives it a personality is the refusals.
It will not observe you from closer than sixteen blocks. It will not observe you at all once it drops below seventy percent health. It will not retreat while the heal is still cooling down. It will not set a trap if it has run out of ice.
Those flat refusals are what make it read as a thing with its own rules rather than a thing chasing you. You can feel the moments it decides you are not worth watching any more.
Sixty four pixels square. Nine colours, seven of which are barely distinguishable shades of black. Twelve pixels glow, and they are the only reason you ever see it coming.
This is the real texture out of the repo, rebuilt into the model the game actually draws. Drag it.
Everything below this line was sitting on an unmerged branch when I wrote it, and might not have survived in that shape. On 16 September both branches went into the mod, so all of it is in the build you can download now. I have left the section standing rather than quietly folding it into the rest of the page, because the honest part of a journal is the stretch between starting something and trusting it, and that stretch is only visible while you are still inside it.
One commit old, and it does something the Architect has never been able to do. It writes down what it watched you do, and it keeps that after you log out. A belief, a policy deciding which beliefs are worth keeping, an observation collector, and a class named MaeveDirector that I am not going to explain yet.
This is the real scoring code, running in your browser. Drag the blue marker anywhere in the box. Twice a second it rebuilds its picture of the situation, scores all seven actions, and does whichever one wins. Nothing here is on rails.
None of the above existed in one piece. This is the real commit history of the thing you were just playing with.
Forty one files and 6,499 lines in a single commit. Utility scoring, D* Lite pathfinding, wall breaching, scaffolding over obstacles, retreat and heal. It was never a walk-at-you mob, not even on day one. The commit title ends with walk oscillation fix, which means it was already pacing back and forth before anyone but me had ever seen it.
That is the entire commit message, verbatim. The next day brought architect polish pass 1 and architect polish pass 2. Fifty more commits followed over the next six months. I have never been more wrong in a commit message and I am leaving it in.
Everything above had been living inside one enormous entity class. In a single day it came apart: the decision engine, the brain state, the combat state, the approach state, and four separate controllers. One of those commit messages names the reason out loud, to avoid further god-class growth. The engine the simulation on this page runs on is the file that came out of that day.
Deciding turned out to be the easy half. Every fix that day is named after the exact way it had embarrassed itself: slab spin, scaffold-up retry loops, water lockups, chase stalls during corridor replans. A mind that cannot cross a half block is not much of a mind.
Six seconds, for testing. Then back to thirty to sixty seconds. Then a flat two hundred ticks. Three commits, two changed lines each, all in one afternoon. Nothing about that number was ever going to come out of reasoning. I had to stand in the snow and be watched by it until it felt right.
Traverse frozen atmosphere reliably. Breach enclosed targets. Bound healing retreats. The last one is the telling one: it had been allowed to back off and heal without limit, which is a perfectly rational thing to do and a miserable thing to fight.
The good parts of its behaviour are mostly accidents of scoring. The bad parts were all deliberate, and all mine.
It would take a step toward you, notice it had just reversed direction, and stop dead. Then do it again. The fix was to let it commit to a walk it had already started, or look past the oscillation zone entirely, rather than re-deciding every single step.
Fixed in the commit that created it · 14ab9eb9 Fixed the same day it was born.Slabs and snow layers read as walkable and not walkable at the same time depending on who was asking. So it would rotate in place on top of one, or decide to scaffold upward and retry forever.
fix(approach): stop slab spin and scaffold-up retry loops · cbb76e1fSubmerged, its committed walk kept churning against a plan made for dry land and it never surfaced. It needed a separate fallback chase mode and a bounded shove upward before it could get out of its own way.
fix(approach): escape water lockups without entity bloat · 47694759Retreating to heal had no ceiling on it. A hurt Architect could simply keep backing away and topping itself up, which is the correct decision and an awful fight. This one survived five months in the wild before I bounded it.
fix(architect): bound healing retreats · 14c9987bSeal yourself in a room with no opening and it had no answer. It understood breaching a wall it could see a way past, not committing to one it could not.
fix(architect): breach enclosed targets · 36af50feThe unstick planner offered the block underneath its destination as something it could break. That column is what holds the destination up, so on any walkable path it is always solid, and it won every time the real obstruction turned out to be air. A stall with a perfectly clear path ahead would send the Architect digging into the ground at its own feet, dropping in, and going one block deeper on the next try.
fix(entity): stop Architect mining its own walk surface · 95407a2dPorting the scoring code into the simulation above meant reading every number again. TRAP, the one that seals your door behind you, tops out at 0.455. APPROACH against a healthy player at range floors out around 0.48. Which means the trap behaviour can essentially never win unless you are already standing inside its base. Six months of play and I had never noticed, because when it does happen it looks completely deliberate.
Open. Not fixed yet.When a chase stalls, it goes still. Not paused: the planner underneath is running at full speed the entire time, searching for a route it cannot find yet. The stillness is the only part of that you can see, so I gave it something to do with the stillness.
Six ticksA third of a second of not moving and the head tilts. Thirty two ticksA second and a half and it might bring a hand up to its chin. Might, because the roll is one in three, and if it plays it will not play again for twelve seconds. A tell you see every time is an idle animation. A tell you see occasionally is a habit.
Vanilla Minecraft arms are a single rigid cube. There is no elbow in there to bend. Getting a hand anywhere near a face meant replacing the left arm with a two segment rig, an upper arm and a forearm on their own model layer, and reusing the right arm's existing texture rather than shipping new pixels for a gesture. The pose is suppressed entirely if it is holding something in the off hand or wearing a chestplate, because the armour layer copies shoulder rotation and you end up with a sleeve floating in the air next to the model.
The roll is tied to the individual Architect rather than to the clock, so two of them standing in the same room never bring a hand up on the same beat. And none of it touches the thinking. The pose is decoration laid over a mind that has not paused for a single instant.
The roll is not random in the usual sense. It hashes the entity's own id against the current tick, so it is deterministic per Architect and never touches the shared random stream the rest of the AI is drawing from. And the whole thing is cosmetic: the pose state rides over the network on one byte, and nothing in it is allowed to delay a decision by a single tick.
It has always been able to mine. What it never had was a record of why. A trace could tell me it broke the ceiling above its own head, which was useless, because there Watch it break the ceiling above its own head and you learn nothing, because there are four completely different reasons it might do that and they fail in four different ways.
Now every block it considers carries an intent from the moment it is proposed, and keeps that intent until the attempt ends.Every block it thinks about breaking now comes with a reason attached from the moment it is proposed, and it keeps that reason until the attempt is over. There are ten.
The useful part is not the list, it is that the selection now writes down what happened to every single candidate, including the ones it never got to.
| Candidate | Intent | Outcome |
|---|---|---|
| from.above() | HEAD_CLEARANCE | NOT_BREAKABLE |
| front | IMMEDIATE_CANDIDATE | BLACKLISTED |
| front.above() | HEAD_CLEARANCE | LAST_RESORT_DEFERRED |
| step | IMMEDIATE_CANDIDATE | SELECTED |
| step.above() | HEAD_CLEARANCE | NOT_EVALUATED_AFTER_SELECTION |
You cannot really unit test a decision. You can assert that a function returns 0.71, but the thing I actually want to know is whether it gets stuck, and there is no assertion for stuck. So the only honest option was to build somewhere awkward and put it there over and over.
There is a lab now. Its own dimension, a 21 by 16 by 21 arena that rebuilds itself around a saved origin, and a scenario loader. Setup freezes the entire server. Recording starts before the Architect is allowed to move or mine a single block, so nothing happens off camera.
Same arena rotated four ways, same scenario reseeded eight ways, because a bug that only exists when the wall runs north to south is still a bug and I would never have found it facing east.
The rule the whole suite runs on is the part I am most pleased with, and it is not technical. A failed case stays a failure with its trace attached. It does not get skipped, quietly relabelled, or handed a larger tick budget until it passes. The last run written down in the notes covers 396 of these cases: 378 passed, 18 failed, nothing missing. Those eighteen are still sitting there failing, which is the entire reason for having built the thing.
The arena in the last tab is a room. A fair room, rotated four ways and reseeded eight, but still a room I specified: flat floor, square walls, ceiling exactly where I said to put it. After a few hundred passes I stopped believing it. Nothing in Minecraft is that shape, and a mind that only works in a room I built for it is a mind that works in one room.
So the second lab builds nothing. It takes a 128 by 128 region in a dimension of its own, lets vanilla generate it the way it would generate any other patch of Overworld, and then lets Frozen Dawn do what Frozen Dawn does to a landscape: deep snow over the trails, Acheronite pushing up through the rock, frozen atmosphere settling into the low ground. A cave. A fenced passage. A shelter with doors. A ravine with one crossing over it. Preparation saves the sixty four native chunks before it touches them, puts them back afterwards, and fingerprints the result, so a run that fails can never be blamed on terrain that quietly drifted between attempts.
What it chases out there is not a marker or a moving point. It is a villager with a hundred hit points, walking on its own legs, with normal collision, normal gravity, normal jumping, normal pathfinding and normal damage. The harness only chooses where that villager should go. It never teleports it, never replaces it, never lifts it out of a hole it walked into. If the villager cannot get somewhere, that is the finding, not a bug in the test.
| Scenario | Runs for | What it has to show |
|---|---|---|
| surface | 3 min | unbroken pursuit around the frozen circuit |
| caves | 5 min | in through the cave branch, and back out |
| shelter_open | 5 min | nothing broken. breaking anything fails it |
| shelter_breach | 5 min | doors shut behind the target. break in, land a hit |
| changing | 8 min | a gate closes, snow rises, the crossing is taken away |
| construction | 15 min | climb six, bridge four, breach a wall, duck a ceiling |
| weather | 15 min | real phase five snowfall, checked every minute |
| endurance | 30 min | all of the above at once, with no reset in the middle |
| roam | 10 min | the villager runs its own brain. not graded |
| player | 30 min | it comes after me instead. not graded |
Every minute of a graded run has to contain at least twelve blocks of horizontal travel by the target and one melee hit confirmed by health actually coming off it. A good final minute does not rescue a bad third one.
That rule is most of the reason the wilderness lab exists. In a room, a stalled Architect still looks busy. Out here, if it spends a minute mining into a hillside while the villager walks away, that minute fails and stays failed, and the run carries it to the end instead of averaging it away. Seven outcomes are possible and only one of them is a pass.
The third one is there because of this, which is my favourite thing that has happened on this branch.
The surface run stalled at waypoint 43, near (22.5, 65, 40.3). Four decorative snow layers I had put there to make the trail look better had quietly made the step up taller than a villager can physically jump, while vanilla pathfinding went on cheerfully reporting the route as reachable. It bounced in place until it died, at tick 2419. The harness recorded TARGET_FAILED, which is the run saying the Architect never got a fair test, rather than the Architect being told it won.
The decoration moved to the shoulder of the trail, off the walking line. The more useful half: the stall timer stopped measuring time and started measuring ground. It now wants a quarter of a block of net horizontal displacement, so a target bouncing enthusiastically in one spot no longer reads as progress.
After sixty ticks without improvement the harness goes looking for a way round: up to twenty four candidate points within six blocks, each needing two separate paths to check out, target to side point and side point back to the original waypoint, both tested with an unspawned probe villager so the real one is never spent on a guess. A hundred and twenty ticks per attempt, and three failed attempts end the run honestly rather than nursing it to the finish.
The notes for this branch end with a line I am leaving exactly where it is, because it is most of what separates a journal from a sales page: this addition has received compilation and resource checks only. No simulations or visual runs were executed during implementation.
Everything up to here is me reading numbers after the fact. A trace tells you what it chose. It does not tell you what it was looking at while it chose, and by the time the file exists the moment is long gone and I am guessing at it.
So the newest thing on the bench draws the server's own working state into the world in front of you, while it is happening. Not a replay, and not an artist's impression of intent: the corridor it has actually committed to, the candidates it actually scored, the boxes it actually believes are solid.
Two trails run underneath all of that, one for each actor, which is the cheapest possible way to tell a stall from a circle. A thing stuck against a wall and a thing walking a perfect loop both look like failure in a log. They look nothing alike drawn on the ground.
It is an operator tool rather than a feature. It wants permission level two, it stops drawing past a hundred and twenty eight blocks, and it only ever holds a short window of the recent past.
Thirty seconds is not an arbitrary budget, it is the useful one. Nearly every Architect bug I have chased announces itself and resolves inside half a minute, and for the rest of the time I would rather the debugger cost the server nothing at all.
The bundle is the real point of it: a snapshot, a frame by frame history, an event table, the camera view it was all drawn from, and a summary carrying a SHA-256 for every attachment, so a recording can be checked against itself later instead of trusted.
Every honest thing in that overlay was a decision to draw less. Observing never forces a target, never resets the AI, never reseeds anything, because the moment a debugger changes the run it is no longer debugging it. Historical mode says outright that it is painting saved observations over the terrain as it is now, not replaying the terrain as it was.
Where it has no answer it says so rather than filling the gap. When the melee commitment check was not sampled on a frame it draws COMMIT_NOT_SAMPLED, rather than an empty space that would look like a decision. Options it never reached are labelled NOT_EVALUATED_AFTER_SELECTION instead of quietly sharing a colour with the ones it rejected. The range sphere uses the same 2.8 blocks as the real hit check, and the documentation states in plain words that being inside it does not prove the hit landed.
Drawing any of it was harder than it had any right to be. The shipping runtime's DebugPackets.sendPathFindingPacket is an empty method, and Path.writeToStream expects optional debug data that an ordinary navigation path does not carry, so there is nothing to subscribe to. The overlay rebuilds the route from the server's own committed corridor and hands it to Minecraft's real PathfindingRenderer through a render hook, which means the lines on your screen are drawn by the same code Mojang draws its own debug paths with, without a line of Mojang source being copied into this mod.
On 13 September the build passed, the unit tests passed, and the required gate passed all thirty seven GameTests, including the thirty two named Architect cases. The live overlay is still unverified, because the client would not start: Failed to locate a primary monitor. The one part of this I built specifically so that I could see it with my own eyes is the one part I have not seen yet.
Stand near a Hearth with a friend and the Architect assessing you never finishes. Not slowly. Never. Both of the controllers that run that scene re-picked the nearest player every single tick and reset the counter whenever the pick changed, and the counter needs sixty consecutive ticks to complete. Three seconds.It works out who is nearest twenty times a second, fresh each time, and its three second count starts over the instant that answer changes. It could not get three uninterrupted seconds with two people in the room, because the two of you only had to keep trading which one was marginally closer.
You can watch it happen, which is the part that gave it away. The head snaps back and forth between you. It starts walking toward whoever is momentarily nearer, then turns around. Three symptoms, one cause, and the cause is six lines of code that reset a counterone count being started over when it should have been left alone.
The fix is borrowed from Phasmophobia. The Banshee chooses one person at the start of a contract and keeps them for the whole thing. It lets go on events, when you die or when you leave, and never because somebody else wandered closer.
That is the whole property worth stealing, and it is worth being precise about why it works. Distance changes every tick. Events do not. Releasing on events rather than on proximity is what makes it thrash-proof, instead of merely slower to thrash.
So it commits. One target, held, and the one it holds is the weakest of you. The score is armour value and nothing else, which sounds crude until you look at the ladder it produces, where every piece of gear in the mod already sits in the right place and anything added later slots in without a line of code.
| Armour | Points | |
|---|---|---|
| Unarmoured | 0 | picked first, every time |
| Leather | 7 | |
| Insulated, tier 1 | 10 | |
| Chain | 12 | |
| Reinforced, tier 2 | 14 | |
| Iron | 15 | |
| EVA, tier 3 | 16 | |
| Diamond, Netherite | 20 | |
| Acheronite | 24 | picked last, and it has to be desperate |
Underneath that is a five step resolution that runs once a tick, in this order, and the order is the design.
Forgetting somebody and bookmarking them look like the same thing and are not, and keeping them apart is the whole reason the stall closes. Someone who logs out is forgotten. Someone who merely walked off is kept. Get that backwards and the Architect holds on to a person who is never coming back, while you stand directly in front of it being ignored.
Step one and step four are the same situation to a naive implementation and they are the entire reason the stall closes. A player id that is still on the server but out of range gets bookmarked. One that is gone gets forgotten. Without that split, a committed target who logs out locks the Architect onto an id that is never coming back, while somebody stands directly in front of it being ignored.
While you are the bookmark, nothing about the Architect looks any different. It assesses whoever else is there, properly, to completion, and patrols when nobody is. The bookmark has no behaviour of its own and there is no way to see it from outside. It matters for exactly one instant: the moment you walk back into range, when you take the commitment back and whoever it had picked up in your absence is dropped mid scan. Resuming restarts the three seconds rather than banking what it already had, and only one bookmark is ever kept, because the alternative is an unbounded queue of people who might come back.
An Architect being hit should fight, not scan. Both controllers now bail out the moment something has hurt them and hand the tick to the combat brain. That alone was not enough, and the reason is worth writing down: the target lookup underneath filtered on relationship, so a friendly player punching an Architect produced no valid target and therefore no retaliation at all. It did not. The lookup underneath only ever handed back people it had already decided were a threat, and somebody who had not been marked as one did not count, even while they were hitting it. You could stand there hitting it and it would keep politely assessing you. Now whoever actually hit it is returned first, ahead of every other rule, and it does not dogpile. Each Architect answers only the thing that hit it.
Two of these controllers watch at different distances, twenty eight blocks and thirty two, through two different constants that happen to share a name. I left them different. Unifying them is a change to how far two separate creatures can see, which is not a thing to do quietly while fixing a counter.
The last honest part: I did not write the test to match the fix. HearthTargetArbitrationGameTest already existed and already failed, on purpose, and it is the reason the build gate sat red for over a week. One of the commits on this branch is fail the game test gate when tests never run, because the gate had been reporting a pass when zero tests ran, which is the most dangerous kind of green there is.