Blog ·

A datapack that installs perfectly and does nothing

The pack wrote fourteen files, loaded into a running server, enabled by name and reported success. Every one of its nine rules did nothing at all, forever, and nothing anywhere said so.

Cog can be asked for a game rather than a building — a soccer pitch where the goals count, a race with a clock, a hill worth standing on. Cog lays the arena and then writes the rules, and the rules go into the server as a datapack: a directory of .mcfunction files, dropped in, /reload confirm, /datapack enable, live. Nobody is kicked, nothing restarts, and no one has to install anything.

The first soccer game did all of that in 1.5 seconds and then sat there being a field.

The bug

A rule that watches a region compiles to a Minecraft selector. A goal mouth is a box, so the region contributes x=…,y=…,z=…,dx=…,dy=…,dz=…, and the thing being watched for contributes type=…,tag=…. The code that built the box did not know whether the caller already had arguments, so it appended its own group:

@e[type=minecraft:snowball,tag=soccer][x=195,y=64,z=182,dx=11,dy=5,dz=4]

Two bracket groups. That is not valid selector syntax, and Minecraft's response is the interesting part: it does not complain about the selector. It refuses to register the entire function containing it.

So the pack loaded. The pack enabled. /function soccer:init ran, created the scoreboard, laid the pitch, and printed a cheerful message in chat. And /function soccer:tick — the function every single rule hangs off — answered Unknown function. Nothing in the install path asks the tick function whether it exists, because why would it. It was written thirty seconds ago.

Why "it installed" is not worth saying

This is the same lesson as every other one on this blog, in a new costume. A build that reports the blocks it sent is not a build that stands up. A datapack that reports the files it wrote is not a game that plays. The only thing worth reporting is what the server does when you ask it to do the thing.

So now every rule counts its own firings:

scoreboard players add #rule_00_fired soccer 1

One line at the top of every rule, before its actions. It costs a scoreboard add per firing and it turns every rule into something that can be asked a direct question. Before it, only rules that happened to move a score could be checked at all — the ones that just say something, a title or a whistle or a message across the bottom of the screen, installed and were never tested. Four of nine.

Then each rule gets set off on purpose. A snowball is dropped into the goal mouth. A score is driven to its winning value. The clock is wound to the tick a timer is waiting for. The counter is read before and after. Six of nine.

The last three

Which left the rules that wait for a person to stand somewhere — the start pads, the finish lines, the team pads that hand you snowballs. Those were reported as untested, honestly, because a player cannot be conjured and saying "verified" of a region nobody entered would be a lie.

Except Cog is a player. Cog joins the world like one; they have feet and a position. So they go and stand in them: to the middle of the region, wait for the tick to come round, read the counter, go back to where they were. Nine of nine.

Across five games written in one evening — soccer, a shooting range, king of the hill, a sprint race, a parkour course — that is 53 rules. Every one of the 53 was triggered on purpose. Every one fired.

A second thing, while we were in there

The obvious way to check a pack is enabled is to ask the server and read the answer. Do not. The reply is truncated by the server itself, at around 137 characters:

There are 6 data pack(s) enabled: [vanilla (built-in)], [file/bukkit (world)], [paper (built-in)], [file/ferris (world)], [file/soccer...

The pack that is running reads as absent, because its name is cut in half. One more pack and it would vanish from the list entirely. Ask the server to run the pack's own tick function instead — Running function or Unknown function, no room for ambiguity, and nothing to truncate. Running one extra tick of a function that already runs twenty times a second costs nothing.

A postscript: do not ask for the whole thing back

Once games could be written, the next thing anyone wants is to change one. "Make it first to three instead of five." The obvious way to do that is to hand the model the game and ask for it back with one thing different.

It does not work, and the way it fails is worth knowing. Asked exactly that, Opus 5 returned a perfectly valid soccer game with the right winning score — which had also quietly lost both team pads, one of its rules, a region, and fourteen of the thirty-seven steps that lay the pitch. Three rounds running, three different sets of losses. Every rule in the result fired, so nothing downstream had any way to notice.

It is not carelessness. Reproducing a long document verbatim is simply not the thing a language model is good at, and the longer the game the worse it gets. So it does not reproduce anything any more. It writes edits — replace rule 4, change the title, add a region — and the edits are applied to the original by code. Everything not mentioned survives by construction rather than by diligence.

[{"op": "set_rule", "index": 5, "rule": {"when": {"type": "score_at_least", "score": "red_goals", "value": 3}, ... }}]

Same request, after the change: four edits, first attempt, twenty-two seconds, all nine rules kept, all five regions kept, all thirty-seven pitch steps kept, and every rule re-tested afterwards because the rule nobody asked about is the one that breaks.

What the model is allowed to write

Worth saying plainly, because it is the reason this class of bug is findable at all: the model does not write .mcfunction files. It writes a game — teams, scores, regions, and rules of the form "when this, do that" — and deterministic code turns that into commands. Every command that reaches the disk is built here, out of fields that have already been checked against a list.

Which means the doubled bracket was our bug, in one function, fixed once, with a test that now scans every file a game emits for ][ and fails if it finds one. A model writing raw commands would have made that mistake in a different place every time, and the fix would have been a hope.

Take a shot at the goal →

Be in the first worlds.

We are opening a small number to begin with. Put your name down and we will tell you when yours is ready.

Early access

Be in the first worlds.

We are opening a small number of worlds to begin with. Tell us which part matters most to you and it moves up the list.

No spam. One email when your world is ready.