Reference
2026-07-18 · 4 min read

Addressing modes

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

Every operand of an instruction carries a mode. It says not only which cell is meant but also how to get there — and for four of the eight modes, the computation alters memory along the way.

Those four are the most common source of errors in the language.

Overview

Symbol Name The address is …
# immediate no address — the value itself
$ direct the cell that many steps away
@ B-indirect forwarded through the target cell's B-field
* A-indirect forwarded through the target cell's A-field
< B-indirect, pre-decrement like @, but the B-field is decremented first
> B-indirect, post-increment like @, but the B-field is incremented afterwards
{ A-indirect, pre-decrement like *, but the A-field is decremented first
} A-indirect, post-increment like *, but the A-field is incremented afterwards

All values are relative to the executing cell and computed modulo the core size. $1 means "one cell along", not "address 1". There are no absolute addresses in Redcode.

The modes in detail

# — immediate

ADD.AB  #4, $ptr

The operand is not a pointer but a number. #4 means the four itself.

A subtlety that rarely matters but occasionally confuses: an immediate operand formally has an address too — namely that of the executing cell. This only becomes relevant for .I operations and in edge cases.

$ — direct

MOV.I   $0, $1

The value is the distance to the target cell. $0 is the cell itself, $1 the next one, $-1 the previous one.

The $ may be omitted — MOV.I 0, 1 means the same thing. This reference writes it everywhere anyway, because explicit code is easier to read.

@ and * — indirect

MOV.I   $bomb, @bomb

Two steps instead of one. First the cell the operand points at is determined; then a further jump is taken from that cell's field. @ reads the B-field, * the A-field.

This is the mechanism by which a small warrior works on a large memory: it advances a pointer and accesses memory indirectly rather than growing itself. The dwarf does this with @bomb — the bomb is simultaneously ammunition and pointer.

< > { } — indirect with side effect

MOV.I   }src, >dst

These four work like @ and * but alter the field they jump through along the way:

  • < and { decrement before the access
  • > and } increment after the access

That gives you a loop counter for free, without spending an instruction on it. A replicator copying itself cell by cell therefore needs only a MOV line instead of a MOV plus an ADD — and in Core War, individual cycles decide.

The trap: side effects always happen

The point where most warrior bugs originate:

The side effect happens when the address is resolved, not when the instruction executes. It therefore occurs even when the operand plays no part in the result.

JMZ.B   $loop, <counter

JMZ only jumps if the tested cell is zero. But the < has already decremented the counter in either case — whether the jump is taken or not.

Does the same apply to skipped instructions? No — an instruction skipped because of a SEQ is not executed at all, so nothing happens. The trap only concerns operands within an executed instruction that are not needed for its result.

The order: A before B

When both operands have side effects and touch the same cell, the order becomes important:

The A-operand is resolved first, the B-operand afterwards.

That means the B-operand sees the change caused by the A-operand — but not the other way around.

This is not a footnote from the standard but observable behaviour. The proof fits in eight lines:

        ORG     go
p       DAT.F   #0, #5
        DAT.F   #0, #0
        DAT.F   #0, #0
        DAT.F   #0, #0
        MOV.I   $0, $1      ; an imp — runs at any position
target  DAT.F   #0, #0      ; lethal unless overwritten
go      MOV.I   <p, @p
        JMP.A   $target

The pointer in p holds 5, that is, it points at target. The decisive line is MOV.I <p, @p:

  • If A is resolved first, <p decrements the pointer to 4. @p then reads the same, now decremented pointer — both operands point at the same cell. The imp is copied onto itself, target stays a DAT, and jumping there kills the process.
  • If B were resolved first, @p would still point at 5, that is, at target. The imp would be copied there, the jump would land on an imp, and the warrior would keep running.

Measured over 200 rounds: 200 losses, not a single surviving round. The warrior dies reliably — A is resolved first.

Common patterns

A few combinations you will meet in almost every warrior:

Pattern Purpose
MOV.I $bomb, @ptr throw a bomb at an advancing address
MOV.I }src, >dst copy a block; both pointers advance automatically
ADD.AB #step, $ptr move a pointer by a fixed step size
DJN.F $loop, <ptr count a loop and move a pointer in one go
SEQ.I $ptr, $ptr+dist compare two locations — the basic form of scanning

← Core War overview