Reference
2026-07-18 · 8 min read

Instruction set

AI notice: This text is created with the support of AI systems; it is reviewed editorially and taken responsibility for before publication.

Redcode has 16 instructions. This page describes each one: what it does, which modifiers matter for it, and where it appears in real warriors.

If you only want a quick reminder of what an instruction does, the overview is enough. The sections below go into detail.

Overview

Instruction In one sentence
DAT Kills the process that runs onto it.
MOV Copies A to B.
ADD Adds A onto B.
SUB Subtracts A from B.
MUL Multiplies B by A.
DIV Divides B by A.
MOD Remainder of B divided by A.
JMP Jumps to A.
JMZ Jumps to A if B is zero.
JMN Jumps to A if B is not zero.
DJN Subtracts one from B, then jumps if B is not zero.
SPL Creates a second process at A.
SEQ Skips the next line if A equals B.
SNE Skips the next line if A differs from B.
SLT Skips the next line if A is less than B.
NOP Does nothing.

LDP and STP exist in addition and access p-space. Under the standard '94nop rule set they are disabled; see Standards.

The instructions in detail

DAT

DAT.F   #0, #0

Does nothing — and thereby terminates the process that runs onto it. DAT is the only instruction that kills.

Two things worth knowing:

Empty core consists entirely of DAT.F $0, $0. Memory is not empty before a round starts but lethal throughout. A warrior therefore does not die from being "hit" but from running onto a DAT.

As a bomb, DAT is invisible. A scanner looks for deviations from empty core. A cell containing DAT 0, 0 looks to it like unused memory — the bomb is lethal and inconspicuous at once. That is why many warriors bomb with exactly this value.

DAT operands are never executed but are ordinary memory. Warriors therefore like to use DAT cells as pointers and counters.

MOV

MOV.I   $0, $1

Copies. The most important instruction in the language — and the one where the modifier makes the biggest difference.

Modifier What is copied
.A A-field to A-field
.B B-field to B-field
.AB A-field of source into B-field of target
.BA B-field of source into A-field of target
.F both fields, A→A and B→B
.X both fields crossed, A→B and B→A
.I the whole instruction, including opcode and addressing modes

Only .I transfers the opcode. Every other modifier moves numbers alone and leaves the target cell's opcode untouched.

This is not a detail. An imp using MOV.I survives three quarters of its fights; the same imp with any other modifier loses every single round, because its copies are only numbers and the opcode there stays DAT. The measurement is in Understanding Redcode.

ADD and SUB

ADD.AB  #4, $bomb

Arithmetic on fields. ADD adds, SUB subtracts — field by field, as the modifier prescribes.

The main purpose is advancing pointers. A bomber adds its step size to the target field on each pass and thereby travels through the core. ADD.AB #4, $bomb means: take the number 4 and add it to the B-field of cell bomb.

Note the .AB modifier — it is almost always the right one here, because the target value usually sits in the B-field while the addend is an immediate value in the A-field. ADD.F would change both fields at once, which is generally not what you want.

All arithmetic is modulo the core size. There is no overflow and there are no negative addresses; 8000 − 1 gives 7999, and 7999 + 1 gives 0 again.

MUL, DIV and MOD

MUL.B   $2, $3

Multiplication, division and remainder. Rare in practice — with one important exception.

DIV and MOD by zero kill the process. That is the only case besides DAT in which an instruction terminates a process, and a popular trap: if you build in a division, you must ensure the divisor never becomes zero.

MUL has one elegant special use in modern quickscanners. There, constants are chosen so that a single multiplication reconstructs the opponent's location exactly — instead of computing it in several steps. The gain is latency: fewer cycles between finding and the first attack.

JMP

JMP.A   $start

Jumps to A, unconditionally. The B-field is not evaluated and therefore often serves as storage for constants or pointers.

The usual modifier is .A or .B — with JMP it makes practically no difference, since only the address matters.

JMZ and JMN

JMZ.B   $loop, $counter

Conditional jumps. JMZ jumps if the tested field is zero, JMN if it is not.

Which field is tested is decided by the modifier: .A tests the A-field, .B the B-field, .F and .X test both (and jump only if both satisfy the condition).

Beware of side effects: if the B-operand carries a < or >, the pointer is altered even when no jump is taken. The side effect happens when the address is resolved, not when the jump executes.

DJN

DJN.F   $loop, $counter

Decrement and jump if not zero. Subtracts one from the tested field, then jumps if the result is not zero.

The classic loop counter — two operations in one instruction, and therefore one cycle saved per pass. In a core clear that overwrites tens of thousands of cells, that adds up considerably.

DJN also has a role as a weapon: a so-called DJN stream continuously decrements values in foreign cells, rendering them useless without overwriting them.

SPL

SPL.B   $1, $0

Creates a second process that continues at A while the first carries on after the SPL line. The basis of every replicator.

And here sits the most expensive beginner's misconception in the whole language:

Processes do not make you faster.

A warrior executes exactly one instruction per cycle, no matter how many processes it has. The processes share the time, they do not multiply it. A warrior with 100 processes advances a hundred times more slowly in each of them than with one.

Processes are robustness, not speed: a warrior with many processes is hard to kill, because a single hit only catches one of them. That is exactly why papers work — not because they are fast, but because they do not all die at once.

How pronounced this is shows in a real battle: one warrior grew to over 6000 processes and still lost against a scanner with a few hundred.

The number of concurrent processes is capped — at 8000 under the standard rule set. Once the limit is reached, a further SPL has no effect.

SEQ and SNE

SEQ.I   $ptr, $ptr+100

Compares two cells and skips the next instruction if the condition holds: SEQ on equality, SNE on inequality. SEQ is called CMP in older sources; both are the same.

This is the heart of every scanner. The usual construction:

        SEQ.I   $ptr, $ptr+dist    ; are both locations identical?
        JMP     $found             ; no -> somebody is here
        ADD     #step, $ptr        ; yes -> keep searching

Because empty core looks identical everywhere, a difference between two locations means somebody is there. The modifier decides the precision: .I compares the complete instruction, .F only the two number fields — quicker to write, but blind to differences in the opcode.

SLT

SLT.AB  #10, $ptr

Skip if less than. Skips the next instruction if A is less than B.

The comparison is unsigned: all values lie between 0 and core size − 1, and −1 is the same as 7999. Expecting a sign here builds you a bug that is hard to find.

SLT is used for range checks — for instance to determine whether a pointer has reached your own code.

NOP

NOP

Does nothing and costs one cycle.

Sounds useless, but has two genuine applications. First, as a placeholder to align code to a particular address. Second as a decoy — filler that keeps a scanner busy. Because NOP differs from empty core, a scanner takes the cell for a find and wastes its attack on an instruction whose destruction achieves nothing.

What the assembler fills in

If you write no modifier, the assembler inserts one — according to fixed rules that depend on the opcode and on the addressing mode of the A-operand:

Instructions A is # otherwise
DAT, NOP .F .F
MOV, SEQ, SNE .AB .I
ADD, SUB, MUL, DIV, MOD .AB .F
SLT .AB .B
JMP, JMZ, JMN, DJN, SPL .B .B

Examples:

Written Becomes
MOV $1, $2 MOV.I
MOV #1, $2 MOV.AB
ADD $1, $2 ADD.F
ADD #1, $2 ADD.AB
SLT $1, $2 SLT.B
JMP $1 JMP.B
DJN #1, $2 DJN.B
DAT #1, #2 DAT.F

Two places where it is easy to slip: SLT without # falls back to .B, not to .F like the other arithmetic instructions. And the jump instructions ignore the A-mode entirelyDJN #1, $2 becomes .B even though the A-operand holds an immediate value.

Recommendation: always write the modifier yourself. Not because the defaults are wrong, but because they are invisible. Relying on them means eventually hunting a long time for a bug that lives in a character which is not there.

  • Addressing modes — the eight modes and their side effects
  • Modifiers — complete semantics of all seven modifiers
  • Standards — differences between '88, '94 and '94nop

← Core War overview