Understanding Redcode
AI notice: This text is created with the support of AI systems; it is reviewed editorially and taken responsibility for before publication.
In step 1 you saw two warriors and roughly understood what they do. Now let us look at what a Redcode instruction actually consists of.
The language has few instructions. The depth is somewhere else — in a place that many introductions treat as a footnote.
The anatomy of an instruction
Every cell in the core holds exactly one instruction, and it always looks like this:
MOV.I $3, @7
│ │ │ │
│ │ │ └─ B-operand
│ │ └───── A-operand
│ └────────── modifier
└────────────── opcode
Four parts, always all present. If you leave out the modifier in the source, the assembler fills one in according to fixed rules — so it is never really absent, just invisible. That is the most common source of surprises for beginners.
Both operands have an addressing mode (the $ and the @ above) and a
value. Values are always relative to the current cell: $3 means "three cells
along", not "address 3".
The instructions
There are 16 opcodes, grouped by purpose:
Moving data and arithmetic
| Instruction | Meaning |
|---|---|
DAT |
Data. Kills the process that runs onto it. |
MOV |
Copies A to B. |
ADD SUB |
Adds or subtracts A onto B. |
MUL DIV MOD |
Multiplies, divides, remainder. |
NOP |
Does nothing, but costs a cycle. |
Jumping
| Instruction | Meaning |
|---|---|
JMP |
Jumps to A. |
JMZ JMN |
Jumps if B is zero, or not zero. |
DJN |
Subtracts one from B, then jumps if B is not zero. |
SPL |
Starts an additional process at A. |
Comparing
| Instruction | Meaning |
|---|---|
SEQ (CMP) |
Skips the next instruction if A equals B. |
SNE |
The same, if they differ. |
SLT |
The same, if A is less than B. |
Memory between rounds — LDP and STP access p-space, a small memory area
that survives a round. It lets a warrior learn from lost rounds. That is an
advanced topic; under the standard '94nop rule set used here, both are disabled.
That was the entire language. If it strikes you as thin: quite right. The art is not in the number of instructions.
The addressing modes
An operand says not only which cell is meant but also how to get there. There are eight modes. The four most important first:
| Mode | Name | Meaning |
|---|---|---|
# |
immediate | The value itself, not a cell. #5 is the number 5. |
$ |
direct | The cell that many steps away. $5 is five cells along. |
@ |
B-indirect | Go to $5, read the B-field there, go that far again. |
* |
A-indirect | The same, but via the A-field. |
The indirect modes are why Core War works at all: a warrior can advance a
pointer and work on the whole memory without growing itself. That is exactly
what the dwarf from step 1 does with @bomb.
Then there are four modes that alter the pointer in passing:
| Mode | Meaning |
|---|---|
< |
Subtract one from the B-field first, then go indirect through it. |
> |
Go indirect through B, then add one afterwards. |
{ |
Like <, but via the A-field. |
} |
Like >, but via the A-field. |
These four are extremely handy — you get loop counters for free — and at the same time the biggest source of errors in the language. More on that in a moment.
The modifier matters more than the instruction
Now the point this chapter is really about.
An instruction has two fields, A and B. The modifier determines which fields are touched at all:
| Modifier | Effect |
|---|---|
.A |
A to A only. |
.B |
B to B only. |
.AB |
A-field of the source into the B-field of the target. |
.BA |
B-field of the source into the A-field of the target. |
.F |
Both fields, A to A and B to B. |
.X |
Both, crossed over. |
.I |
The whole instruction, including opcode and modes. |
This sounds like a detail. It is the difference between a working warrior and a dead one.
Take the imp from step 1 and change nothing but the modifier. 2000 rounds against the dwarf in each case:
| Variant | Wins | Losses | Ties | Score |
|---|---|---|---|---|
MOV.I $0, $1 |
0 | 510 | 1490 | 74.5 |
MOV.A $0, $1 |
0 | 2000 | 0 | 0.0 |
MOV.B $0, $1 |
0 | 2000 | 0 | 0.0 |
MOV.F $0, $1 |
0 | 2000 | 0 | 0.0 |
MOV.X $0, $1 |
0 | 2000 | 0 | 0.0 |
MOV.AB $0, $1 |
0 | 2000 | 0 | 0.0 |
MOV.BA $0, $1 |
0 | 2000 | 0 | 0.0 |
Two characters changed, and a warrior that survives three quarters of its fights becomes one that loses every single one of 2000 rounds.
The reason is simple once you have seen it. The imp lives by copying itself —
and it must copy itself completely, including the MOV opcode. Only .I
carries the opcode across. With any other modifier, just a number travels into
the neighbouring cell. The opcode there remains what it was: DAT. So the imp
writes its own grave and walks into it on the next cycle.
Remember this: MOV.I and MOV.A are not two variants of the same
instruction. They are two different tools that happen to share a name.
Simulators reflect this in their construction, incidentally: opcode and modifier are fused internally into one value, because together they form a single instruction. Anyone who reads only the opcode has not read the instruction.
The trap with < and >
One more peculiarity, and it causes more warrior bugs than anything else.
The modes < > { } alter a pointer. They do so even when the operand is
not needed for the actual computation.
JMZ.B $2, <5
JMZ jumps if the B-field is zero. Whether it jumps or not — the < has
already subtracted one from the B-field of cell $5 in either case. The side
effect happens when the address is resolved, not when the instruction executes.
On top of that comes an ordering you need to know: the A-operand is resolved first, the B-operand afterwards. So if both point at the same cell and both modify it, the B-operand sees the A-operand's change — but not the other way around.
That is not harassment but a tool: experienced authors build counters with it
that cost no instruction of their own. For beginners the main lesson is: if a
warrior behaves inexplicably, first check whether there is a < or >
somewhere you did not think about.
An example to read
With that knowledge the dwarf can now be read line by line:
step EQU 4
start ADD.AB #step, $bomb
MOV.I $bomb, @bomb
JMP.A $start
bomb DAT.F #0, #0
ADD.AB #step, $bomb—#stepis the number 4 itself..ABmeans: A of the source onto B of the target. So it adds 4 to the B-field ofbomb.MOV.I $bomb, @bomb—.Icopies the whole instruction. The target is@bomb, that is, indirect through the B-field ofbomb— precisely the pointer the previous line just incremented.JMP.A $start— back to the beginning.bomb DAT.F #0, #0— the bomb. It is simultaneously ammunition and pointer storage: the B-field serves as an address counter while the rest of the cell is shipped through the core as a lethal payload.
This double duty — one cell being data and pointer and weapon — is typical of Redcode. Space is scarce, and every cell you save is one less cell the opponent can hit.
What you now know
- An instruction consists of opcode, modifier and two operands, each with an addressing mode.
- There are 16 instructions; the language is deliberately small.
#is a value,$is a cell,@and*are pointers.<>{}alter pointers — even when the operand goes unused.- The modifier decides the semantics.
MOV.IversusMOV.Ais the difference between 74.5 and 0.0 points.
In the next step we look at the strategies that grew out of these few instructions — and why they beat each other in a rock-paper-scissors pattern.
All figures on this page were computed with pMARS under the standard '94nop rule set over 2000 rounds each.