The Workstation
Verifying the workstation
The first machine in The World is the workstation: the compiler, the container engine, and the directory where the server starts. Never trust an installed tool until it has run something and you have read the output.
An installer finishing without complaint proves the installer ran. It does not prove the compiler compiles or the container engine starts containers. Each tool you install today earns its place by doing its actual job once, in front of you, before anything depends on it.
The world is the reason for the strictness. It will run on a machine you control: ground with real soil chemistry, water that flows where the terrain says it must, plants that compete for light, creatures that eat the plants and each other, and a village of characters who remember what happened to them.
The client program is also yours. It lets you walk in, stand in that village, and be remembered. The world ticks while you sleep. It keeps its own history. It never resets.
None of that exists yet, and the book does not hand it over whole. You build the server that owns all of it, the simulation that advances it, the wire protocol that carries it, and the renderer that shows it.
The language is Go. The runtime home is a container. By the last page of this volume, the first server lives in a box that can keep counting time whether or not you are logged in.
Create the theworld module and its worldd command below. Run the formatter, vet and tests before adding simulation behavior.
Go and the first program
Download the archive for your operating system from
go.dev/dl and follow the three-line install on that page (on Linux:
unpack to /usr/local, add /usr/local/go/bin to your
PATH, open a new terminal). This volume targets Go 1.26; the reference
output below records Go 1.26.3 on Linux/amd64.
A newer compatible compiler can build the examples, but version strings and
diagnostics can differ. Exact transcript replay needs the reference toolchain, not
merely a compatible language version. Go's install page is the reference:
go.dev/doc/install.
go version
$ go version
go version go1.26.3 linux/amd64
Your line will differ in the last digit and probably in the tail. The patch number
climbs over time. linux/amd64 names this machine's operating system
and processor, so a Mac prints darwin/arm64 and Windows prints
windows/amd64.
Use Go 1.26 or a compatible newer release for learning. Keep 1.26.3 when checking this quoted version line.
If the command is not found at all, the PATH step above did not take.
Open a fresh terminal before debugging anything else, because the old one still has
the old PATH.
The version line proves the toolchain exists. Now make it do its job. Create a scratch
directory anywhere (it will not be part of the project; ~/scratch is fine)
and put one file in it.
// hello.go
package main
import "fmt"
func main() {
fmt.Println("the world is not running yet")
}
$ go run hello.go
the world is not running yet
Seven lines, and five of them carry the program. package main declares
that this file belongs to a program instead of a library. Go programs start in a
package with exactly that name.
import "fmt" pulls in the standard library's formatting package, the
one that knows how to print. func main() is the entry point: when the
program starts, this function runs, and when it returns, the program ends.
go run did two jobs in one command: it compiled the file into a real
machine-code binary in a temporary location, then executed it. There is no interpreter
here. Every Go program in this book is compiled first, every time, and the compile is
fast enough that you rarely see it happening.
Any text editor works. If you want one with Go support wired in, VS Code plus its Go extension gives you error underlines and formatting on save, free.
The book never depends on editor features. Everything is checked from the command line, so what your editor thinks is never the authority on whether the code is right.
The module and worldd
A scratch file proves the compiler works, but a project of this size needs a structure
the tools recognize. Go's unit of project is the module: a directory tree with
a go.mod file at its root.
That file names the module and records which Go version and which outside libraries it depends on. Everything you build across all twenty-five volumes lives in one module. Create its home and initialize it:
mkdir theworld
cd theworld
go mod init theworld
go mod edit -go=1.26
$ go mod init theworld
go: creating new go.mod: module theworld
go: to add module requirements and sums:
go mod tidy
$ cat go.mod
module theworld
go 1.26
The second suggested command, go mod tidy, matters later, when the
module gains dependencies. Today there are none to tidy.
The go mod edit line does one small thing, and it matters.
go mod init stamps the file with the exact toolchain that ran it,
1.26.3 on this machine and some other patch on yours.
The go directive declares a minimum Go version and selects the language
version. It can include a patch-level minimum, but it never locks the compiler to that
exact patch.
Setting it to go 1.26 permits Go 1.26 or newer, and it means your
go.mod now matches the one every later chapter quotes, byte for byte.
The file is two facts: this module is named theworld, and its code is
Go 1.26.
The name carries real weight. It becomes the root of every import path in the project, and that matters as soon as packages begin to refer to one another.
Inside the module, layout is convention. Go projects put each runnable program in its
own directory under cmd/, named after the binary it produces.
This one is cmd/worldd: the world daemon, the server that will own every
rock, river, creature, and villager. The trailing d is an old Unix habit
marking a program built to run continuously in the background. By the end of this volume
it has work to justify the letter.
go.mod tells the
toolchain where the module starts and what it is called; ./cmd/worldd is
how you name the package you want run; main.go is where execution
begins. Every command in this book that starts with go is run from the
directory holding go.mod.
// cmd/worldd/main.go
package main
import (
"fmt"
"runtime"
)
const version = "0.0.1"
func main() {
fmt.Println("worldd", version, "starting")
fmt.Println("go runtime:", runtime.Version())
fmt.Println("nothing exists yet: no ground, no water, no time")
}
$ go vet ./...
$ go run ./cmd/worldd
worldd 0.0.1 starting
go runtime: go1.26.3
nothing exists yet: no ground, no water, no time
Two new pieces of Go appeared. The import block now lists two packages
in parentheses, one per line. const version = "0.0.1" declares a named
constant: a value fixed at compile time, so nothing can change it while the program
runs.
The second output line comes from runtime.Version(), the standard
library reporting which Go built the running binary. Your line shows your own patch
release.
The command that printed nothing matters too. go vet ./... checks every
package in the module (./... means "here and everything below") for
mistakes the compiler technically permits. Silence is a pass. Run it before every run
in this book.
While writing stage 4 you might reasonably think ahead: the server will surely need
os, the package for talking to the operating system, so import it now
and save a trip. Add "os" to the import block without using it, and:
$ go run ./cmd/worldd
# theworld/cmd/worldd
cmd/worldd/main.go:5:2: "os" imported and not used
The program did not run. This is a compile error, not a warning: Go refuses to build a file that imports a package and never touches it.
Read the message the way you will read hundreds like it. The # line
names the package that failed to compile, and the next line gives file, line 5,
column 2: the exact position of the offending import.
The reasoning from symptom to cause is one step here. The policy behind it is the lesson: in a codebase that will grow for twenty-five volumes, an import is a claim that a dependency is real, and Go keeps every claim honest by force. Delete the line (or never add it) and the build comes back. Import when you use, not when you predict.
Podman containers
Podman runs the world's supporting services and reference checks in containers. Each container has a separate user-space view, but shares the host kernel on Linux or a helper VM's kernel on Windows and macOS.
Rootless Podman avoids a privileged daemon. Processor architecture still matters for replay.
On most Linux distributions it is one package: sudo apt install podman,
sudo dnf install podman, or your distribution's equivalent. On macOS
and Windows, install Podman Desktop and run podman machine init then
podman machine start once, which creates the small Linux virtual
machine containers need there. Full instructions: podman.io/docs/installation.
podman --version
podman run --rm docker.io/library/alpine:3.22 echo "a container said this"
$ podman --version
podman version 5.7.0
$ podman run --rm docker.io/library/alpine:3.22 echo "a container said this"
a container said this
On the first run, Podman may print image-download progress. run starts
the container. --rm removes it after exit. The arguments after the image
name select its command.
A later run can reuse the cached image. That speedup is useful, but the cached image is also one more reason exact replay has to record what image was used.
Replay conditions
A replay claim names its conditions: the same source and inputs, deterministic draw and update order, an exact compiler and dependencies, and a reference platform. A seed alone does not fix all of those.
Later simulation code uses floating-point arithmetic. Its final bits can depend on the architecture and compiler even when every operation appears in a fixed order. The book's byte comparisons test the recorded reference environment; they do not prove identical results on any machine.
Today's go.mod records a compatibility requirement. Likewise,
alpine:3.22 and golang:1.26 are mutable tags: a publisher can
move either to new content while your local cache still holds the old image.
Exact replay requires an image reference ending in @sha256:… and an
explicit platform such as --platform linux/amd64, plus the recorded Go
version. Inspect a pulled image with podman image inspect and record its
RepoDigests and platform before using the digest reference.
The early volume scripts retain tags. They do not record the original image digest, so resolving a tag today cannot recover that missing provenance. The contract compatibility note records this limit and the digest-pinned references the later volumes actually use.
Checkpoint
- Install a Go toolchain and prove it works with two commands: one that prints its version and one that compiles and runs a real program.
- Explain what
package main,import, andfunc main()each do in the seven-line program from this chapter. - Create a Go module, read its
go.mod, and say why the module name is the root of every import path to come. - Run
go vet ./...and interpret both of its answers: silence, and a file-line-column report. - Start a container from a version-tagged image, explain each part of the
podman runcommand, and say where the image came from and why the second run was faster. - Explain why a tag is weaker than a digest.
- Handed the error
"os" imported and not used, name the policy behind it and fix it in one line.
Exercise 1 — a second binary. The module can hold
a second program: worldc, the client path. Create
cmd/worldc/main.go with a main that prints
worldc: no world to connect to yet, then run it. What did you have to
change compared to stage 4, and what does go vet ./... now cover?
Almost nothing changes: same package main, same entry point, different
directory, and go run ./cmd/worldc picks it by path. Both programs
can be package main because packages are scoped by directory, one
package per directory. go vet ./... now checks both binaries in
one command; the ./... pattern grows with the module for free,
so the book standardizes on it.
Exercise 2 — break it on purpose, twice. From your home
directory (not the module), run go run ./cmd/worldd and read the
error. Then, back inside the module, change func main() to
func Main() and run it again. Which of Go's rules did each error
enforce?
The first prints go: go.mod file not found in current directory or any
parent directory: package paths like ./cmd/worldd are
resolved relative to a module, and Go searches upward from where you stand until
it finds a go.mod. The second fails because the entry point must be
exactly main, lowercase; Main is just an ordinary
function nobody calls, so the linker reports that main is
undeclared. Both errors name their rule in the message; Go usually does.
Exercise 3 — Go inside a container. Without installing
anything new, run podman run --rm docker.io/library/golang:1.26 go
version. Predict the output before you run it, including how it can differ
from your own go version.
After a pull (this image is far larger than Alpine; give it a minute) it prints
a go version go1.26.x linux/… line. The patch number can differ
from your host's because the image carries its own toolchain, and a later pull
of the same tag can update it. The platform reads
linux even on a Mac or Windows machine, because the container runs
Linux; the architecture follows the selected image platform. Record both before
comparing reference output.