Advent of Code 2025
Posted on Sat 27 December 2025 in update
I am not committing to the Advent of Code 2025 this year. I'm working on too many side projects checksmix, snipren, emomtimer. In fact I don't even have time to write this blog. On the other hand, I used some holiday downtime to work through Day 1 Part 1 in MMIX.
Day 1 is about a safe dial with 100 increments. The dial starts at 50, then processes a stream of rotations such as L68 or R48. Each instruction moves the dial left or right by the given amount, wrapping modulo 100. The idea is that directional, left or right, rotations are applied to the dial pointer using mod math. The trick is that, while mod addition is no big deal, it will be obviously positive, modullo subtraction is a bit more complicated because the pointer could take a negative value. In that case the pointer needs to be reset from the back (end - pointer) to determine the pointed value.
Consider the following:
RemEuclid DIV $2, $0, $1 % q = trunc(a/m)
MUL $3, $2, $1 % q*m
SUB $0, $0, $3 % r = a - q*m (can be negative)
BNN $0, RemDone % if r >= 0, we're done
ADD $0, $0, $1 % r += m
RemDone POP 1,0
Note that after the branch, BNN, instruction a correction is applied to add the remainder to the max number of increments, in this case 100 to determine the correct offset, aka, Euclidean remainder.
Here is my implementation of safe dial pointer in MMIX - I got my ⭐️.
Approach
- Parse the input as a null-terminated string containing
L/Rtokens separated by newlines. - Convert each token into a signed direction (
-1forL,+1forR) and a magnitude. - Update the dial position by adding the signed delta and applying Euclidean remainder with divisor 100 to keep the value in
[0, 99]. - Record a hit whenever the dial value becomes zero.
InputPtr is a global register used as a moving pointer into the input string. ParseRotations reads one token and returns the direction in $2 and the magnitude in $3. HandleRotation computes the signed move, updates the dial in $10, wraps it with RemEuclid, and increments the hit counter $12 when the dial reaches zero. The loop continues until a null terminator is encountered.
RemEuclid is a small helper that mirrors Rust's rem_euclid to avoid negative results after subtraction. The helper keeps the remainder non-negative by adding the divisor when needed; it uses $2 and $3 as scratch registers.
Complexity is linear in the number of tokens. The program keeps all state in registers and a single input buffer in the data segment; no heap or I/O is involved beyond the initial static input.
Input and code are embedded together below. The input matches the sample I used to validate the counting logic: the dial hits zero whenever a rotation lands exactly on that position after wrapping.
DIAL_INCREMENTS IS 100
LOC #100
% Entry point
GREG @
InputPtr GREG 0 % global register for input pointer
Main LDA InputPtr,MyInput % initialize pointer
SETI $10,50 % dial setting - current dial pointer (starts at 50)
SETI $11,0 % count number of operations handled
SETI $12,0 % hit count - how many times the dial got set to 0
TokenLoop
LDBI $1,InputPtr,0 % check if at end BEFORE parsing
BZ $1,Done % null terminator, done
PUSHJ $0,ParseRotations
PUSHJ $0,HandleRotation
ADDUI $11,$11,1 % increment operations processed
JMP TokenLoop
% ----------------------------------------------------
% HandleRotation
% ----------------------------------------------------
HandleRotation
% operation = direction * magnitude
MUL $5,$2,$3 % apply direction to value
% temp = dial + operation
ADD $0,$10,$5 % move dial in direction of vector
% temp = temp.mod_euclid(DIAL_INCREMENTS)
SETI $1,DIAL_INCREMENTS
PUSHJ $4,RemEuclid
% dial = temp
SET $10,$0
% if dial == 0?
BNZ $10,SkipCount
% if dial is zero increment the number of hits
ADDI $12,$12,1
SkipCount
POP 0,0
% ------------------------------------------------------------
% RemEuclid: $0 := ($0 rem_euclid $1), with $1 > 0
% Input: $0 = dividend, $1 = divisor (must be > 0)
% Output: $0 = remainder in range [0, $1)
% Uses: $2, $3
% ------------------------------------------------------------
RemEuclid DIV $2, $0, $1 % q = trunc(a/m)
MUL $3, $2, $1 % q*m
SUB $0, $0, $3 % r = a - q*m (can be negative)
BNN $0, RemDone % if r >= 0, we're done
ADD $0, $0, $1 % r += m
RemDone POP 1,0
% ----------------------------------------------------
% ParseRotations - Parse ONE rotation from input string
% Input: InputPtr = pointer to current position in string (global)
% Returns: $2 = direction (-1 or +1), $3 = value, InputPtr = updated pointer
% Uses: $1 = current char, $5/$6 = digit scratch
ParseRotations
LDBI $1,InputPtr,0 % load first char
BZ $1,ParseEnd % null terminator
CMPI $2,$1,'L' % check if 'L'
BZ $2,ParseL
CMPI $2,$1,'R'
BZ $2,ParseR
ADDUI InputPtr,InputPtr,1 % skip unknown char
POP 0,0 % return early
ParseL SETI $2,-1 % direction = -1 for left
JMP ParseNumber
ParseR SETI $2,1 % direction = +1 for right
ParseNumber
ADDUI InputPtr,InputPtr,1 % skip L/R char
SETI $3,0 % value accumulator
DigitLoop
LDBI $1,InputPtr,0 % load next char
SUBI $5,$1,'0' % convert to digit
BN $5,EndNumber % < '0'
CMPI $6,$5,10
BNN $6,EndNumber % >= 10
MULI $3,$3,10 % value *= 10
ADD $3,$3,$5 % value += digit
ADDUI InputPtr,InputPtr,1
JMP DigitLoop
EndNumber
ADDUI InputPtr,InputPtr,1 % skip newline/delimiter
ParseEnd
POP 0,0
Done TRAP 0,Halt,0
LOC Data_Segment
MyInput BYTE "L68", '\n', "L30", '\n', "R48", '\n'
BYTE "L5", '\n', "R60", '\n'
BYTE "L55", '\n', "L1", '\n', "L99", '\n'
BYTE "R14", '\n', "L82", '\n', 0
The this example input the program processes 10 operations, finishes at dial position 32, and records 3 zero hits in $12.