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/R tokens separated by newlines.
  • Convert each token into a signed direction (-1 for L, +1 for R) 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.