Field report · Research & Development
Unit 7
Autonomous Extraction at the Face
Summary
Unit 7 levels a fifteen-by-fifteen pad to the elevation at which it is set down, and hauls what it removes to a chest at the edge of the work. It runs unattended. It has been cutting the north face since spring. This report covers what the unit does, what it refuses to do, how it takes a new version of itself, and how we satisfied ourselves that the difference holds.
Layers, not columns
The obvious way to level ground is column by column: find the high point, chase it down, move on. Unit 7 does not do this. It sweeps the entire area at its starting elevation, rises one block, sweeps it again, and repeats until nothing remains overhead.
This costs more walking. We accept the cost because a column scan walks underneath things — an overhang, a floating leaf canopy, anything whose support has already been removed. A column scan finds clear air above the column and calls it finished. The layer sweep does not have this failure mode, because it visits every cell at every elevation on the way up.
Within a layer the unit walks a serpentine path — down one column, up the next — so that consecutive cells are always neighbours and no move is wasted returning to a row start. Each layer reverses the direction of the one below it, so the unit begins the next layer where it finished the last rather than trekking back to the corner.
Knowing when to stop
This is the interesting part of the problem, and the part that took the longest.
An extraction unit can see exactly one block above itself. It cannot survey the site. It cannot know how tall the hill is. What it can do is notice, for free, whether the cell above the one it is standing in is occupied — and it stands in every cell of every layer. Finishing a layer therefore tells the unit whether the next layer has anything in it, which is how a tree keeps the unit climbing even when the leaf layers sit over open air.
That lookahead reaches one block. Debris floating with a wider gap beneath it would be missed, so the first four layers are swept unconditionally before the unit is permitted to trust an empty result. A ceiling of thirty-two layers above the starting elevation bounds the job in the event that something above the pad is taller than anyone expected.
An empty layer is only evidence if the unit actually reached all of it. A cell it could not enter — a unit stuck against a block it has failed to cut, out of the six attempts it allows itself — is not a cell known to be empty, and a layer holding one is no longer grounds for calling the job done. The unit carries on upward and reports the skipped cells rather than quietly finishing beneath them.
-- Stop once this layer came up empty AND nothing was spotted overhead,
-- but never before SCAN_MIN layers have actually been walked. A cell we
-- could not reach is not a cell we know to be empty, so a layer with
-- skipped cells is never grounds for calling the job done.
if z + 1 >= SCAN_MIN and layerDigs == 0 and not sawAbove and skipped == 0 then
print("Nothing left up here. Done.")
break
endturtles/flattener.lua. sawAbove is the free lookahead; SCAN_MIN is the four-layer floor beneath it; skipped is the count of cells the unit could not reach. All three conditions must hold, and none is reachable from an operator setting.What the unit will not do
The guarantees below are structural. With one exception, noted where it falls, they are not settings, and an operator cannot switch them off by accident.
It will not cut below the floor. The elevation at which the unit is placed is the floor. The unit never aims a dig at or beneath it. A crew can set the floor by hand, walk away, and find it intact.
It will not leave the pad. Every leg of every route is axis-aligned and every destination lies inside the work area, so there is no sequence of moves that carries the unit outside the fifteen-by-fifteen. This includes the chest, which sits one block behind the near edge and has never been mined.
It will not scatter the haul. A dig with nowhere to put the block drops it on the ground, so the unit refuses to begin a cell with a full inventory — it returns and empties first. The delivery instruction is worse than it looks: told to deposit into nothing, it throws the material on the floor and reports success. The unit therefore confirms a real container is present before it hands anything over, and holds its load rather than dumping it into the dirt if one is not.
It will not hoard its own fuel. Coal, charcoal and blaze rods are recognised as fuel. The unit keeps sixty-four of them aboard and posts the remainder to the chest with everything else it has cut.
Withdrawn at Rev. C. Rev. B of this report stated that fuel was never delivered to the chest. That was the defect, not the guarantee. Coal is ordinary spoil at the face, and a unit that treats every piece of it as reserve fills all sixteen slots with material it refuses to put down. Observed under test: a collection unit on a coal-littered yard seized after sixteen cells with 5,376 coal on the ground and 1,024 aboard, and halted there. An extraction unit driven through a coal seam abandoned 838 blocks mid-job and delivered nothing. Sixty-four is a working reserve; the rest is cargo. The figure is the exception noted above: it is an operator setting, and it is the only one in this section.
It will not spin. If the unit comes home full and leaves still full, it would bounce off the chest once per cell indefinitely. It stops instead, and says why.
It will not lose to gravel. Sand and gravel fall into the gap the instant it is cleared. Each block is re-cut up to eight times before the unit concedes the square.
Endurance
The unit refuels itself, topping up when its reserve falls below two hundred blocks of travel. It continuously computes the cost of returning home from wherever it currently stands, and keeps a margin of sixteen above that figure. When the margin is gone it comes home under its own power rather than stopping in the field.
When the inventory fills, the unit walks to the chest, empties its load down to the fuel reserve described in section 4, and resumes at the exact cell it left. This accounts for essentially all of Unit 7's downtime. The 94% uptime figure quoted elsewhere is not a reliability measure; it is the fraction of the shift the unit is not standing at a chest, waiting for someone to come and take the dirt away.
The second unit
A companion unit patrols a ten-by-ten area collecting loose material. It cruises one block above the litter rather than walking through it, because a unit cannot recover items sharing its own position; clearing each cell downward also makes coverage independent of which way the unit happens to be facing. Under a low roof it drops to ground level and sweeps sideways instead — coverage is then partial, and it reports this rather than implying a clean pass. It runs a full circuit, sleeps an hour, and runs another.
The sideways sweep is the one direction that can reach outside the pad, and at ground level it is the only one doing any work. It is therefore taken only when the cell in front belongs to the ten-by-ten — which excludes the row behind the near edge, where the chest stands. Rev. B described the unit as staying inside the area. At ground level that was an aspiration rather than a fact, and a marker placed one row outside the pad was collected during testing. It is now bounded, and the case is in the harness.
A single cell can hold more litter than the unit can carry. Rather than take what fits and move on, it returns to the chest and comes back to the same cell, up to eight times, until the ground is clear. Before this the unit resumed at the next cell along and counted the pass complete: 400 items of 2,000 were left behind, one per cell, against a report of a clean sweep.
Verification
Both units are exercised against a stubbed voxel world that implements the movement and inspection interface without requiring a live site. The update subsystem is exercised against a stubbed network and filesystem on the same principle. The three harnesses hold 58 cases between them. Their names are the guarantees restated, and they are reproduced here in full.
Extraction unit
- clears every block at and above the start layer inside the 15x15
- leaves the layer it started on intact
- never aims a dig below the start layer
- leaves blocks outside the 15x15 alone
- does not mine the chest
- clears an overhang floating above empty air
- delivers the dirt and the stone to the chest
- never scatters items on the ground
- comes home to the start cell facing the way it started
- empties into the chest partway through a big job
- halts instead of spinning when the chest fills up
- holds its haul rather than scattering it when no chest is there
- mines nothing on ground that is already flat
- posts mined fuel to the chest instead of seizing up on it
- runs perfectly well with no updater installed
- checks for a new version once, before it starts digging
- installs a new version and restarts before touching the ground
- takes an update without restarting when it cannot come back up
- gets on with the job when the update check cannot reach the network
- refuses a broken release and flattens with the version it has
test/flattener_test.lua
Collection unit
- picks up every loose stack inside the 10x10
- delivers the haul to the chest
- leaves litter outside the 10x10 alone
- never scatters items on the ground
- never digs anything
- empties into the chest partway through and resumes the pass
- halts instead of spinning when the chest fills up
- holds its haul rather than scattering it when no chest is there
- keeps its fuel instead of posting it into the chest
- comes home to the start cell facing the way it started
- warns and keeps sweeping when there is no headroom
- finishes a pass on a yard with nothing lying about
- posts swept fuel to the chest instead of seizing up on it
- leaves the row behind the start edge alone at ground level
- comes back for litter it had no room for
- runs perfectly well with no updater installed
- checks for a new version at startup and again between passes
- installs a new version published between passes and restarts
- leaves a startup file, but only when asked to
- writes nothing to the computer with autostart off, as it ships
- takes an update without restarting when it cannot come back up
- carries on sweeping when the update check cannot reach the network
- refuses a broken release and keeps sweeping with the version it has
test/sweeper_test.lua
Update subsystem
- installs a strictly newer version
- leaves an identical version alone
- refuses to install an older version
- compares version numbers numerically, not as text
- refuses a download that does not compile, and keeps the old file
- refuses a download with no VERSION line
- refuses a download that compiles but is a fraction of the size
- reports being offline rather than throwing
- copes with the HTTP API being switched off entirely
- does not know how to update a program it has no source for
- keeps a backup that restore puts back
- can update itself
- writes a startup file that relaunches the program
- rewrites its own startup file without complaint
- never clobbers a startup file somebody else wrote
test/updater_test.lua
We would rather find out on a workbench than on the north face.
Taking a new version
A fleet is only as current as its slowest visit. Units may now fetch their own replacement from the published repository and restart into it, which removes the walk to sixteen turtles and the transcription errors that come with it.
Rollout is driven by one line. Each program carries its own version number, and the copy on the server is compared against it component by component — numerically, so 1.10 follows 1.9 rather than preceding it. Publishing a change without raising that number rolls out nothing. Raising it rolls out to every unit that asks.
An update lands only where stopping is free. Neither unit writes its position to disk, so a restart partway through a job would bring the unit back up believing it stood at the corner and set it to work on the wrong ground. The extraction unit therefore checks once, at startup, before its first move. The collection unit checks at startup and again between passes, parked on its start cell with an empty hold — the state a fresh run expects to boot into anyway. There is no mid-job check, and adding one would be a defect.
A release is refused unless it is fit to install.The realistic failure is not a bad program but a truncated download, and a unit that writes half a file over itself is a unit somebody has to walk out to. Every download is therefore parsed before it is written, and one that will not compile is discarded. So is one carrying no version line, and one that arrives at less than half the size of the file it would replace — a cut that lands on a statement boundary can still compile, and the length is what gives it away. The previous version is kept alongside the new one. Rollback is deliberate and manual.
Nothing here is load-bearing. A unit with no network, no update library, or a server returning nonsense carries on running the program it already has, and the harness holds a case for each of those. A unit also restarts only when it knows it can come back up running; otherwise it installs the new version and defers to the next manual start. That guarantee ships switched off, because switching it on writes a startup file to the computer, and that is the operator's decision rather than ours.
Availability
Unit 7's routines are not a trade secret. Both programs, the update library and the test harnesses are published in full. Run them on your own fleet — we ask only that you tell us what we got wrong.
Appendix A — Specifications
Figures below are the shipped defaults. Each is set at the head of the program named beneath it and may be changed by the operator; the identifier column gives the name it is set under.
A.1 Extraction unit
| Parameter | Value | Identifier |
|---|---|---|
| Working area | 15 × 15 cells | WIDTH × LENGTH |
| Vertical ceiling | 32 layers above the floor | MAX_HEIGHT |
| Unconditional sweep depth | 4 layers | SCAN_MIN |
| Re-cuts per block | 8 | DIG_RETRY |
| Move attempts before a cell is abandoned | 6 | MAX_STUCK |
| Refuelling threshold | 200 blocks of travel | FUEL_MIN |
| Reserve held over the trip home | 16 blocks of travel | FUEL_MARGIN |
| Onboard capacity | 16 slots | — |
| Accepted fuel | Coal, charcoal, coal block, blaze rod | FUEL_ITEMS |
| Fuel kept aboard; the surplus is posted | 64 items | FUEL_KEEP |
| Program version | 1.0.0 | VERSION |
| Restarts itself after an update | false as shipped | AUTOSTART |
turtles/flattener.lua
A.2 Collection unit
| Parameter | Value | Identifier |
|---|---|---|
| Working area | 10 × 10 cells | WIDTH × LENGTH |
| Cruise altitude | 1 block above the litter layer | SWEEP_ALT |
| Interval between passes | 3,600 s (one hour) | PATROL_DELAY |
| Recovery attempts per cell | 64 | SUCK_LIMIT |
| Chest runs allowed for a single cell | 8 | CELL_RETRY |
| Climb allowance | 3 blocks | CLIMB_LIMIT |
| Move attempts before a cell is abandoned | 3 | MAX_STUCK |
| Refuelling threshold | 200 blocks of travel | FUEL_MIN |
| Reserve held over the trip home | 16 blocks of travel | FUEL_MARGIN |
| Fuel kept aboard; the surplus is posted | 64 items | FUEL_KEEP |
| Program version | 1.0.0 | VERSION |
| Restarts itself after an update | false as shipped | AUTOSTART |
turtles/sweeper.lua
A.3 Update subsystem
| Parameter | Value | Identifier |
|---|---|---|
| Library version | 1.0.0 | VERSION |
| Repository consulted | erinlkolp/computercraft-scripts | REPO |
| Branch consulted | main | BRANCH |
| Smallest accepted release, against the file it replaces | 0.5 | SHRINK_LIMIT |
| Previous version retained as | .bak alongside the program | BACKUP_SUFFIX |
| Rollback | Manual | — |
| Required of a release before it is written | Compiles, and carries a version line | — |
lib/updater.lua