The First Mob: Analyzing Minecraft rd-132328
Six hours after inventing the voxel world, Notch populated it.
On May 13, 2009, Markus “Notch” Persson released rd-132211 — the earliest known build of what would become Minecraft. Thirteen Java files. A flat world of blocks you could place and destroy. No mobs, no entities, no life. Just a silent landscape of stone and grass under a featureless sky.
Sometime later that same day — the version timestamp suggests just a few hours — he released rd-132328. Six new files, bringing the total to 19. Three hundred and six new lines of code, pushing the codebase from 1,562 lines to 1,868. And with them, the first creatures to walk a Minecraft world.
One hundred zombies, spawned at the center of the map, stumbling in random directions with a beautifully simple AI that amounts to “pick a direction, drift slowly, occasionally jump.” They cannot hurt you. They do not notice you. They have no purpose except to exist, to move, to prove that this voxel engine could host living things.
This is the version where Minecraft stopped being a tech demo and started becoming a game.
Same Day, Different World
The version identifiers tell the story. rd-132211 — likely a timestamp meaning roughly 1:22 PM on the 13th. rd-132328 — perhaps 3:23 PM, or thereabouts. (The exact encoding of these version numbers is uncertain, but Mojang’s version manifest lists both with the same release date: May 13, 2009.) In the span of an afternoon, Notch went from “a world made of blocks” to “a world made of blocks with things living in it.”
The speed of iteration is striking. Six new Java files, a new texture asset, a refactor of the player class, and modifications to the main game loop — all in what appears to be a single working session. This was not a carefully planned feature branch merged after code review. This was a programmer in flow state, seeing the next logical step and taking it immediately.
And the next logical step was entities.
The Big Refactor: Extracting Entity
In rd-132211, the Player class was a monolith. It contained the player’s position, velocity, bounding box, collision detection, physics simulation, movement code, mouse look, keyboard input, and camera height — everything that made the player a physical presence in the world. This worked fine when the player was the only moving thing.
But if you want zombies, you need a second moving thing. And if two classes need the same physics, you extract the shared code. This is textbook object-oriented refactoring, and Notch executed it cleanly.
Entity.java becomes the new base class. It takes ownership of everything that makes a thing exist physically in the world:
- Position:
x,y,zand their previous-tick counterpartsxo,yo,zo(used for render interpolation) - Velocity:
xd,yd,zd - Rotation:
yRot(yaw),xRot(pitch) - Collision:
bb(the AABB bounding box),onGroundflag - Physics methods:
move()(collision resolution),moveRelative()(input-to-world-space transformation),setPos(),resetPos()
And one new field that did not exist in rd-132211: heightOffset. In the previous version, the camera height of 1.62 units above the bottom of the bounding box was hardcoded in the render method of the main class. Now it is a property of the entity itself. Player sets heightOffset = 1.62F in its constructor. The Entity base class defaults it to 0.0F. This is a small detail, but it tells you that Notch was already thinking about entities as things with different physical properties — not just identical physics puppets with different AI scripts.
The refactored Player class is dramatically simpler. All that remains is the constructor (which sets heightOffset) and the tick() method (which reads keyboard input). The actual physics — gravity, friction, collision resolution, the entire sweep-and-prune axis-separated collision system — now lives in Entity, inherited by both Player and Zombie.
Here is the complete Player.tick() after the refactor:
public void tick() {
this.xo = this.x;
this.yo = this.y;
this.zo = this.z;
float xa = 0.0F;
float ya = 0.0F;
if (Keyboard.isKeyDown(200) || Keyboard.isKeyDown(17)) { ya--; }
if (Keyboard.isKeyDown(208) || Keyboard.isKeyDown(31)) { ya++; }
if (Keyboard.isKeyDown(203) || Keyboard.isKeyDown(30)) { xa--; }
if (Keyboard.isKeyDown(205) || Keyboard.isKeyDown(32)) { xa++; }
if ((Keyboard.isKeyDown(57) || Keyboard.isKeyDown(219)) && this.onGround) {
this.yd = 0.12F;
}
this.moveRelative(xa, ya, this.onGround ? 0.02F : 0.005F);
this.yd = (float)(this.yd - 0.005);
this.move(this.xd, this.yd, this.zd);
this.xd *= 0.91F;
this.yd *= 0.98F;
this.zd *= 0.91F;
if (this.onGround) {
this.xd *= 0.8F;
this.zd *= 0.8F;
}
}
If you compare this line-for-line with rd-132211’s Player.tick(), it is identical. The behavior has not changed at all. The only difference is that move() and moveRelative() are now inherited from Entity rather than defined in Player itself. This is a pure structural refactor: same inputs, same outputs, different organization.
Zombie AI: The Beauty of Simplicity
The Zombie.tick() method is where things get interesting. Notch needed to make these creatures move, and he solved it with what might be the simplest possible AI that still looks organic:
this.rot += this.speed;
this.speed *= 0.99F;
this.speed += (float)(Math.random() - Math.random()) * Math.random() * Math.random() * 0.01F;
float xa = (float)Math.sin(this.rot);
float ya = (float)Math.cos(this.rot);
Two state variables drive the entire behavior: rot (the current facing angle) and speed (how fast that angle is changing). Each tick:
- The rotation angle advances by
speed. speeddecays by 1% (multiplied by 0.99), slowly damping out.- A small random perturbation is added to
speed. - Movement direction is
(sin(rot), cos(rot))— a unit vector pointing in the current facing direction.
The result is a random walk with momentum. The zombie does not jitter randomly. It does not snap to a new direction each tick. Instead, it smoothly curves through the world, its path a series of lazy arcs as the rotation angle drifts. Sometimes it walks in roughly straight lines (when speed is near zero and the perturbations are small). Sometimes it turns in broad circles (when a lucky sequence of perturbations pushes speed higher). The 0.99 damping ensures that any burst of turning eventually fades back to near-straight motion.
This is a remarkably elegant algorithm for producing natural-looking movement. Random walks in velocity space (rather than position space) are a well-known technique in procedural animation, but seeing it implemented in seven lines of Java in a day-old game prototype is still impressive.
On top of this smooth drift, there is one more behavior: a 1% chance to jump each tick. if (Math.random() < 0.01F) { this.yd = 0.12F; } — the same jump velocity as the player. At 60 ticks per second, this means each zombie jumps roughly 36 times per minute on average, or about once every 1.7 seconds. They hop their way across the landscape with cheerful abandon.
The physics after the AI input are identical to the player: same gravity (0.005 per tick), same ground speed (0.02), same air speed (0.005), same friction coefficients (0.91 horizontal in air, 0.98 vertical, additional 0.8 horizontal on ground). Zombies move exactly like players — they just choose their inputs differently.
One safety valve: if (this.y > 100.0F) { this.resetPos(); }. If a zombie somehow gets above Y=100 (the world surface is at Y~42, and the world height is 64), it is teleported to a random position and dropped back down. This prevents zombies from escaping the playable area, though it is hard to imagine how they would get that high with normal jumping.
The Character Model: A Skeletal System in 273 Lines
Zombies need bodies. To give them bodies, Notch created an entire character model system from scratch — four new classes in a new character/ package.
The architecture is bottom-up:
Vec3 is a 3D vector. Three floats (x, y, z) with an interpolation method and subtraction. Nothing surprising, but necessary as a foundation.
Vertex pairs a Vec3 position with texture UV coordinates (u, v). It has a remap method that creates a new vertex with different UVs at the same position — useful when you want to reuse geometry but map different parts of the texture onto it.
Polygon is a quad face built from four vertices. Its render() method emits the vertices as an OpenGL triangle fan with texture coordinates and surface normals (computed from the cross product of two edge vectors). This is the fundamental renderable primitive.
Cube is where things come together. A Cube is a textured box defined by an origin offset, dimensions (width, height, depth), and a UV origin point on the texture atlas. The constructor generates eight corner vertices, maps six quad faces with proper UVs from a 64×32 texture (the classic Minecraft skin layout), and stores them as six Polygon instances.
But a Cube is not just a static box. It has rotation: xRot, yRot, zRot fields and a setPos method that defines the rotation pivot point. At render time, the cube translates to its pivot, applies rotations, and draws its six faces. This is the foundation of skeletal animation: each body part is a rigid box that rotates around a joint point.
The zombie’s body is six cubes:
| Part | Dimensions | Texture Region |
|---|---|---|
| Head | 8 x 8 x 8 | Top-left of char.png |
| Body | 8 x 12 x 4 | Center of char.png |
| Right arm | 4 x 12 x 4 | Right side of char.png |
| Left arm | 4 x 12 x 4 | Right side (mirrored) |
| Right leg | 4 x 12 x 4 | Bottom-left of char.png |
| Left leg | 4 x 12 x 4 | Bottom-left (mirrored) |
All six cubes read their textures from char.png, a 64×32 pixel image that follows the same UV layout that would eventually become the standard Minecraft player skin format. The UV mapping in Cube‘s constructor carefully tiles each face of the box to the correct region of this atlas. The arms share a texture region. The legs share a texture region. The head and body each have their own.
Animation is driven by a time value: float time = (float)System.nanoTime() / 1.0E9F * 10.0F + this.timeOffs. Each zombie has a random timeOffs (assigned at construction from Math.random() * 1239813.0F) so they are not all synchronized. The head nods up and down at sin(time) * 0.8 radians. The arms swing in opposition: right arm forward when left arm is back, and vice versa. The legs do the same. This produces a recognizable walking animation with zero keyframes — just sine waves applied to joint rotations.
The rendering setup includes a glScalef(1.0F, -1.0F, 1.0F) call that flips the Y axis. The character model system uses Y-down coordinates (the head is at negative Y relative to the body), while the world uses Y-up. The scale flip reconciles these two conventions. It is the kind of pragmatic hack that you write when you have built a model system from scratch in an afternoon and the coordinate conventions do not quite line up.
What Did NOT Change
Here is something remarkable: the entire block and world system is byte-identical between rd-132211 and rd-132328. Not “similar.” Not “functionally equivalent.” Identical. Every file in the level/ package and the phys/ package is unchanged:
Level.java— block storage, world generation, lighting, save/loadLevelRenderer.java— chunk management, dirty tracking, rebuild schedulingChunk.java— 16x16x16 display list sectionsTile.java— block type definitions and per-face mesh generationFrustum.java— view frustum extraction and cullingTesselator.java— vertex array batchingLevelListener.java— observer interfaceAABB.java— axis-aligned bounding box collision primitivesHitResult.java— raycast result structTimer.java— fixed-timestep timerTextures.java— texture loading utility
Eleven of thirteen original files are completely untouched. The world still generates the same way. Blocks still store the same way. Lighting still works the same way. Collision still resolves the same way. Save/load still serializes the same way. The foundation that Notch laid in the morning held firm while he built an entity system on top of it in the afternoon.
This speaks to the quality of the original architecture. The level system was clean enough that adding entities required zero changes to it. The physics code in Player was factored well enough that it could be extracted to a base class without modification. The game loop in RubyDung absorbed entity ticking and rendering with only a handful of new lines.
The Game Loop: Entity Integration
The changes to RubyDung.java itself are surgical. In the tick method, a loop over all zombies calls zombie.tick() before the player ticks. In the render method, zombies are drawn between the two terrain rendering passes:
render layer 0 (bright terrain faces)
render all zombies
render layer 1 (shadowed terrain faces)
This ordering means zombies appear in front of lit surfaces but behind shadowed surfaces. It is not a proper depth-sorting solution — for that, you would need per-pixel depth testing against the terrain, and that is exactly what the depth buffer provides. But the two-pass terrain system from rd-132211 was not designed with entities in mind, and slotting the zombies between the passes was probably the fastest way to get something that looked roughly correct.
The 100 zombies are created at position (128, 0, 128) — the center of the 256×256 world map. They spawn at Y=0, which is below the terrain surface (which sits at Y~42, two-thirds of the 64-block depth). Gravity immediately pulls them toward the surface. Or rather, they spawn above Y=0 and fall, landing on the flat terrain. With 100 of them all starting from the same point, the initial moments after spawning must be a chaotic cluster of zombies stumbling outward in all directions from the world center, slowly dispersing as their random walks carry them apart.
The Texture: char.png
The new char.png asset is a 64×32 pixel image — the same dimensions that would become the standard Minecraft player skin format for years afterward. The UV layout conventions established here (head at top-left, body in the center, limbs arranged around the edges) would persist through the entire evolution of Minecraft’s character rendering system.
It is worth noting that this texture is called char.png, not zombie.png or player.png. The zombie model system was clearly intended to be general-purpose from the start. The Cube class does not know or care what kind of entity it belongs to. It is a textured box that can rotate around a pivot point. Stack six of them together with sine-wave joint rotations and you have a walking humanoid. Change the texture and the animation parameters and you have a different kind of walking humanoid.
This is the embryonic form of what would become Minecraft’s entire entity rendering system. Player skins, mob models, armor overlays — they all descend from this simple idea: boxes with textures, rotating around joints.
Implications for the Rust Port
For the port, rd-132328 adds two categories of new work.
Game logic (pure computation, no rendering):
The Entity base class and Zombie AI are pure game logic. The physics code is already ported from rd-132211’s Player — it just needs to be restructured. In Rust, the natural pattern is composition rather than inheritance: a shared EntityPhysics struct containing position, velocity, bounding box, and collision state, with Player and Zombie structs each owning an EntityPhysics instance. The move() and moveRelative() methods become functions on EntityPhysics.
The zombie AI is trivially portable: two floating-point state variables (rot, speed), a handful of trig calls, and a random number check. No dependencies on anything outside standard math.
Rendering (wgpu replacement):
The character model system needs a wgpu equivalent. The Java code uses OpenGL immediate mode (glBegin/glVertex/glEnd) which has no analogue in modern graphics APIs. For wgpu, we need to:
- Generate the box geometry (8 vertices, 6 quads, 12 triangles per Cube) as a vertex buffer.
- Compute UV coordinates the same way the Java
Cubeconstructor does (from the 64×32 atlas). - Apply joint rotations via transform matrices rather than GL state calls.
- Load
char.pngas a texture and bind it during the entity render pass.
The sine-wave animation system is just math — sin(time) * 0.8 applied to rotation matrices. The key rendering decision is whether to rebuild the vertex buffer each frame (simple but wasteful) or use instance transforms (efficient but more complex). With only 100 zombies, either approach is viable.
What carries forward unchanged:
The entire level/block system port from rd-132211 requires zero modifications. The AABB collision code, the timer, the save/load system, the block storage — all of it carries forward directly. This validates the architecture of the Rust port: if the level system is cleanly separated from entities, adding entities should require no changes to existing code, just as it required no changes in the Java original.
Reflection: The Speed of Creation
There is something deeply instructive about this pair of versions. In the morning (or whenever rd-132211 was built), there was a world. By the afternoon, that world had inhabitants. The entire entity system — physics extraction, base class design, mob AI, character model rendering, texture creation, game loop integration — was conceived and implemented in what appears to be a single session.
Notch was not optimizing. The zombie AI is not sophisticated. The character model system does not handle edge cases. The render ordering between entities and terrain is a hack. But none of that mattered. What mattered was that you could stand in a voxel world and watch things move around you. The zombies proved that this engine was not just a voxel editor — it was a space where things could happen.
The hundred zombies of rd-132328 are the ancestors of every Creeper, Enderman, Iron Golem, and Warden that would follow. They cannot attack. They cannot pathfind. They cannot interact with blocks. But they can walk, jump, and exist as physical presences in a shared world with the player. That was enough.
Three hundred and six lines of code. One afternoon. The first mob in the history of Minecraft.
Sometimes the most important features are the ones that prove what is possible, not the ones that get it right.