Chapter 22, Proving Grounds
Two launch commands, one mandatory server flag, and the short list of mistakes that compile fine and explode at runtime.
You've built the whole mod. This last chapter is about running it, really running it, both halves, and reading what the game tells you. There's exactly one non-obvious setup step (the offline-mode flag) and a short checklist of sided-code traps. Learn both and you can test anything in this book.
runClient, singleplayer, where almost everything lives
The everyday command:
./gradlew runClientThis launches Beta 1.7.3 with your mod loaded. Because beta singleplayer runs the world entirely inside the client (Chapter 2), everything is testable here except custom packets and dedicated-server-only paths. Blocks, items, recipes, the mob, achievements, the dimension, every one of the twelve mixins, all of it works in runClient.
And you don't have to go hunting for your content. The showcase's ExamplePlayerSetup hands you a starter kit on your first tick in the world, every block, the Jump Stick, water buckets and coal for the freezer, the join achievement, and a spawned example mob at your feet. Walk in and it's all in your hotbar.
The game writes its world into run/. Your dev world, options, screenshots, and logs all land in the run/ folder beside your build files. Delete run/saves/ to start fresh; delete run/ entirely to reset everything. It's git-ignored by the template, it's scratch space.
runServer, the dedicated server, and the trap
To exercise the parts that only exist on a real server, the welcome packet, id sync, mob spawn packets, you launch a dedicated server:
./gradlew runServerIt starts, prepares the spawn region, and prints Done. Then you connect from a runClient instance via Multiplayer → Direct Connect → localhost. And the first time you try, you get kicked at the door:
The trap every beta modder hits once. A dev client isn't logged into minecraft.net's session servers, so a fresh dev server can't verify it and kicks the join immediately. The log looks exactly like this:
[INFO] Disconnecting ExampleProbe [/127.0.0.1:56768]: Failed to verify username!
[internal error java.lang.NullPointerException: Cannot invoke
"String.equals(Object)" because "string6" is null]This is not a bug in your mod. A dev (offline-session) client cannot pass online-mode verification, there's no session to check. The fix is mandatory for dev, and it's one line.
The server writes run/server.properties after its first start. Open it and flip one property:
online-mode=falseRestart the server. It now prints the offline-mode warnings on boot (SERVER IS RUNNING IN OFFLINE/INSECURE MODE!, expected and fine for dev), and your client connects cleanly:
[INFO] ExampleProbe [/127.0.0.1:52262] logged in with entity id 2781 at (113.5, 78.6, -236.5)This is not optional and not a workaround, it's the required setup for a local dev server. Leave online-mode on and no dev client will ever get in.
The test matrix
Where each feature can actually be tested, singleplayer is enough for most of it, a dedicated server is required for anything that crosses the wire:
| Feature | Singleplayer (runClient) | Dedicated (runServer) |
|---|---|---|
| Blocks, items, recipes | ✓ fully | ✓ |
| Achievements | ✓ fully | ✓ |
| The dimension & portal | ✓ fully | ✓ |
| The twelve mixins | ✓ fully | ✓ (sided ones on their side) |
| Welcome / ping packets | ✗ channels stay quiet | ✓ only here |
id_sync block id sync | n/a (one side) | ✓ only here |
| Mob spawn packets | n/a (one side) | ✓ only here |
open_gui container opening | local, no packet | ✓ exercised over the wire |
| The freezer's synced properties | visible (bars animate) | only here are they sent over the wire |
That last row is the subtle one: the freezer's progress bars display in singleplayer because the block entity and the GUI share memory, but the synchronization code that ships those properties to a remote client is only exercised when there's a wire to ship them over. To prove the sync works, you need the server.
Sided-code gotchas
These all compile cleanly and then explode at runtime, because the compiler sees one merged classpath while the running game has two separate sides. The checklist:
- Referencing
net.minecraft.client.*from a server-loaded class.NoClassDefFoundErroron the dedicated server, those classes simply aren't there. This is why renderers and screens live inExampleModClient(Chapter 2). - Referencing
ServerPlayerEntityfrom client-loaded code. The mirror image, it doesn't exist on the client. Keep server-only types inExampleModServerand the"server"mixin list. - Mixins in the wrong list. A client-only mixin in the common
"mixins"list crashes a dedicated server at load; a server-only one in the common list crashes the client. Match the list to the target class's side (Chapter 13). - Sending packets before
ServerPlayNetworking.isPlayReady. Fire too early and the client isn't listening on your channel yet, so the packet vanishes. Gate every send on the handshake (Chapter 12). - Forgetting
world.isRemoteguards. Game logic, spawning, damage, inventory changes, granting achievements, belongs behind!world.isRemote. Beware the beta surprise: in singleplayerworld.isRemoteisfalse(the local world is authoritative), so the guard reads as "only run where the world is real," which is exactly where your logic belongs.
Reading the log
The console is your best testing tool. A few things to look for:
- A bad mixin target is a loud crash at class-load. With
defaultRequire: 1(Chapter 13), a typo'd method or field name stops the game at launch with a clear message instead of failing silently. When you see it, you have a name to fix, that's the system working. - RetroAPI logs every registration at startup. Blocks, items, entities, dimensions, achievement ids, each prints as it registers. Skim those lines to confirm your
init()actually ran and your content reached the registry. The showcase even logs its dimension's serial id and "gave <player> the example starter kit", concrete proof the path executed. - The StationAPI
FlattenedWorldStoragemixin warning is NORMAL. When StationAPI isn't present, RetroAPI logs a warning that its optional StationAPI-compat mixin couldn't apply. This is expected, it's an optional-compatibility mixin, and its absence means nothing is wrong. Don't panic, and don't go hunting for a missing dependency.
The send-off
That's the whole book. Thirteen chapters ago you had an empty folder; now you've pointed modern tooling at 2011 and built a mod with custom blocks, items, machines with synced GUIs, a mob, an achievement page, a walk-in dimension, crafting and smelting recipes, a custom packet round-trip, and a twelve-step mixin course, all of it persisting across saves, surviving a vanilla client, and syncing over multiplayer because RetroAPI quietly handled the dangerous parts.
You have the full toolbox now: the bare template to start clean, the feature showcase to steal from, and these thirteen chapters to come back to whenever a detail slips. The grass is still that particular green, the world is still quiet, go make something for it.