Baillie-PSW Pseudoprime Solver - $620 prize

The two prizes are not mutually inclusive — here’s why

Prize A — $620 (Baillie, Wagstaff, 1980): Find a composite that passes the original BPSW test: spsp(2) AND lpsp(P,Q) using Method A*. The vpsp condition is not required. This is the looser, 1980 formulation.

Prize B — $2000+ (Baillie, Fiori, Wagstaff, 2020): Find a composite passing the enhanced test: spsp(2) + slpsp + vpsp + Euler-Q. Or publish a peer-reviewed proof that none exist.

image

Prior works -

Baillie-PSW Prize - All Cores Fire (prize7.zip) ($620 Prize A)

seven prize.zip (168.0 KB)

; =============================================================================
; HDGL — BAILLIE-PSW $620 PRIZE HUNTER  v3
; =============================================================================
;
; BUILD:  nasm -f bin prize620_v3.asm -o prize620_v3.img
; QEMU:   qemu-system-x86_64 -smp 4 -m 128M -drive format=raw,file=prize620_v3.img -boot c
;
; ARCHITECTURE — phi-lattice substrate is the engine, BPSW rides the gaps:
;   Core 0 (FIRE):  phi-lattice operator + BPSW on candidates ≡ 3 (mod 8)
;   Core 1 (WATER): inverse verifier    + BPSW on candidates ≡ 5 (mod 8)
;   Core 2 (EARTH): N_phi oracle        + BPSW on candidates ≡ 7 (mod 8)
;   Core 3 (WIND):  T(X) residual       + BPSW on candidates ≡ 1 (mod 8)
;
;   Each AP: after signalling DONE_K, immediately runs one BPSW step.
;   Substrate integrity is preserved. BPSW fills the gaps between ticks.
;   AP count auto-detected via CPUID. Fallback to single-core if APs fail.
;
; GATES — exact 1980 Baillie-PSW definition, NO vpsp:
;   Gate 1: spsp(2)  Miller-Rabin base 2
;   Gate 2: slpsp    Strong Lucas, Selfridge Method A adaptive D
;   A pseudoprime here wins the $620. Row 14 lights red.
;
; RESUME — on warm reboot, continues from where it left off:
;   Saves candidate positions to 0x9F000 every display cycle.
;   Magic: 0x4844474C42505357 ("HDGLBPSW") + XOR checksum.
;
; PROGRESS — Row 13 shows human-readable percent and ETA:
;   Coverage = total_tested / 2^63 (4 cores * half the odd range each)
;   Displayed as X.XXXX% and estimated hours/days to 2^64.
;
; =============================================================================
BITS 16
ORG 0x7C00

; =============================================================================
; CONSTANTS
; =============================================================================

PAYLOAD_PHYS       equ 0x00010000   ; unused, kept for reference only

; Payload (sectors 2..IMAGE_SECTORS) now loads at physical 0x7E00, directly
; after the boot sector, spanning up to roughly 0x7E00 + IMAGE_SECTORS*512.
; The AP trampoline and page tables MUST live outside that span or the
; code overwrites itself the moment build_page_tables or the AP-trampoline
; copy runs. 0x20000+ is comfortably clear.
AP_TRAMP_PHYS      equ 0x00020000

PML4_PHYS          equ 0x00021000
PDPT_PHYS          equ 0x00022000
PD0_PHYS           equ 0x00023000
PD1_PHYS           equ 0x00024000
PD2_PHYS           equ 0x00025000
PD3_PHYS           equ 0x00026000

BSP_STACK          equ 0x00070000
AP_STACK_BASE      equ 0x00200000   ; above 1MB, safe from VGA/ROM
AP_STACK_STRIDE    equ 0x00008000   ; 32KB per AP

LAPIC_BASE         equ 0xFEE00000
LAPIC_ICR_LOW      equ 0x300
LAPIC_ICR_HIGH     equ 0x310

VGA_BASE           equ 0x000B8000
VGA_COLS           equ 80          ; characters per row
VGA_ROW            equ 160         ; bytes per row

PRINT_EVERY        equ 1048576
PRINT_MASK         equ PRINT_EVERY - 1

IMAGE_SECTORS      equ 64
PAYLOAD_SECTORS    equ IMAGE_SECTORS - 1

; =============================================================================
; SHARED STATE LAYOUT (at 0x500000)
; =============================================================================

STATE_A            equ 0x00500000  ; Current Omega: a
STATE_B            equ 0x00500008  ; Current Omega: b
STATE_K            equ 0x00500010  ; Iteration counter

FIRE_A             equ 0x00500020  ; FIRE result: a+b
FIRE_B             equ 0x00500028  ; FIRE result: a
FIRE_K             equ 0x00500030  ; FIRE step counter

WATER_A            equ 0x00500040  ; WATER result: b
WATER_B            equ 0x00500048  ; WATER result: a-b

EARTH_N            equ 0x00500060  ; N_phi(current)
EARTH_N_FIRE       equ 0x00500068  ; N_phi(FIRE(current))
EARTH_DELTA        equ 0x00500070  ; N_phi(FIRE) - N_phi(current)
EARTH_PREV_DELTA   equ 0x00500078  ; Previous delta (for pattern check)

WIND_RES_A         equ 0x00500080  ; T(X) phi-coefficient residual
WIND_RES_B         equ 0x00500088  ; T(X) constant residual
WIND_FIX           equ 0x00500090  ; 1 if at fixed point

REQUEST_K          equ 0x005000A0  ; Published step for APs
DONE_WATER         equ 0x005000A8
DONE_EARTH         equ 0x005000B0
DONE_WIND          equ 0x005000B8

READY_MASK         equ 0x005000C0

ORACLE             equ 0x005000C8  ; Wu-Wei oracle bitfield
TRINARY            equ 0x005000D0  ; Trinary projection of N
STRATEGY           equ 0x005000D8  ; Current strategy index
YIN                equ 0x005000E0  ; Yin: s -> s^2 - 2
PHASE              equ 0x005000E8  ; Completion phase 0->3->0
DEPTH              equ 0x005000F0  ; Total iteration depth


CPU_COUNT          equ 0x00500100
PARALLEL_MODE      equ 0x00500108
ORACLE_AP_TIMEOUT_FLAG equ 0x00500110  ; 1 if AP bring-up timed out and we fell back to serial

; ─── Fibonacci–Legendre probable-prime oracle ───
; Verified theorem: for prime p != 5, p divides F_(p-(5|p)), where (5|p) is
; the Legendre symbol (whether 5 is a QR mod p). Tested against trial
; division for P=2..1999 in Python: zero false negatives (every real prime
; passes), a small known set of Fibonacci-pseudoprime false positives
; (25, 60, 323, 377, ...). This is a genuine probable-primality test, not
; a certified one -- displayed and labeled as such.
; ── Per-core BPSW state (stride 0x80, 4 cores) ──────────────────────────
BPSW_CORE_STRIDE   equ 0x80
BPSW_C0            equ 0x00500120   ; Core 0 (FIRE)   residue 3 mod 8
BPSW_C1            equ 0x005001A0   ; Core 1 (WATER)  residue 5 mod 8
BPSW_C2            equ 0x00500220   ; Core 2 (EARTH)  residue 7 mod 8
BPSW_C3            equ 0x005002A0   ; Core 3 (WIND)   residue 1 mod 8

; Offsets within each core block (all 8-byte qwords)
BC_CAND            equ 0x00   ; current candidate
BC_TESTED          equ 0x08   ; candidates tested by this core
BC_SPSP2           equ 0x10   ; passed MR-2
BC_BPSW            equ 0x18   ; passed both gates (probable primes)
BC_PSEUDO          equ 0x20   ; composites passing both (THE PRIZE)
BC_LAST_PSEUDO     equ 0x28   ; most recent pseudoprime
BC_SEL_D           equ 0x30   ; last Selfridge D
BC_SEL_Q           equ 0x38   ; last Selfridge Q
BC_MR2             equ 0x40   ; last MR-2 result
BC_SLC             equ 0x48   ; last slpsp result

; ── Shared aggregate totals ────────────────────────────────────────────────
TOT_TESTED         equ 0x00500320
TOT_SPSP2          equ 0x00500328
TOT_BPSW           equ 0x00500330
TOT_PSEUDO         equ 0x00500338
TOT_LAST_PSEUDO    equ 0x00500340
TOT_RATE_RAW       equ 0x00500348   ; (delta_tested<<20)/delta_tsc
TOT_TSC_PREV       equ 0x00500350
TOT_TEST_PREV      equ 0x00500358

; ── Resume record at 0x9F000 (survives warm reboot) ───────────────────────
RESUME_BASE        equ 0x0009F000
RESUME_MAGIC       equ 0x4844474C42505357   ; "HDGLBPSW"
RESUME_OFF_MAGIC   equ 0x00
RESUME_OFF_C0      equ 0x08
RESUME_OFF_C1      equ 0x10
RESUME_OFF_C2      equ 0x18
RESUME_OFF_C3      equ 0x20
RESUME_OFF_CKSUM   equ 0x28   ; XOR of all four candidates
RESUME_OFF_VER     equ 0x30   ; version = 3

; ── Lucas scratch per core at 0x501000, stride 0x100 ──────────────────────
LS_BASE            equ 0x00501000
LS_STRIDE          equ 0x100
; Offsets within each core's scratch block
LSO_U    equ 0x00 ; LSO_V equ 0x08 ; LSO_QK equ 0x10 ; LSO_U2 equ 0x18
LSO_V    equ 0x08
LSO_QK   equ 0x10
LSO_U2   equ 0x18
LSO_V2   equ 0x20
LSO_N    equ 0x28
LSO_D    equ 0x30
LSO_Q    equ 0x38
LSO_INV2 equ 0x40
LSO_S    equ 0x48
LSO_DODD equ 0x50
LSO_VD   equ 0x60   ; V_d saved (for display)

; Legacy single-core compat (FIRE / core 0 maps here)
PRIME_CANDIDATE    equ BPSW_C0 + BC_CAND
PRIME_FOUND_COUNT  equ BPSW_C0 + BC_BPSW
PRIME_LAST_FOUND   equ BPSW_C0 + BC_LAST_PSEUDO

; Must be a power of 2 (gated via bitmask test, not DIV). Higher = faster
; substrate tick rate, slower prime-scan rate. 64 recovers most of the
; ~47x throughput lost when testing every tick.
; Doubled from 64 to compensate: the full two-coefficient Frobenius test
; costs ~1.8-2x the modmuls of the old single-coefficient test (measured:
; 59 false positives -> 1, for that price).
PRIME_TEST_STRIDE  equ 128

; Bounded spin count for waiting on AP ready bits. Large enough to give
; genuinely slow-but-working hardware a fair chance, small enough that a
; truly broken AP path fails over to serial mode in well under a second
; rather than hanging the boot forever.
AP_WAIT_TIMEOUT    equ 5000000   ; large enough for QEMU + real hw

; Physical address adjustment for 64-bit code
; Label values are ORG-relative (0x7C00+), actual physical = label + PHYS_ADJ
; Payload now loads at physical 0x7E00 (immediately after the boot sector)
; in BOTH build variants -- HDD build loads it there itself, CD build gets
; it there for free via El Torito boot-load-size. Since ORG=0x7C00 and the
; boot sector is exactly 512 bytes, every label's value already equals its
; physical address: label(L) = 0x7C00 + file_offset(L) = physical(L).
; No adjustment needed. Kept as 0 so existing "+ PHYS_ADJ" references
; throughout the file remain valid no-ops.
PHYS_ADJ           equ 0

; Strategy indices
STRATEGY_FLOWING   equ 0  ; Healthy oscillation
STRATEGY_NONACTION equ 1  ; Hold on anomaly
STRATEGY_REDIRECT  equ 2  ; Rebase on magnitude error
STRATEGY_CONVERGE  equ 3  ; Fixed point detected
STRATEGY_CRITICAL  equ 7  ; Halt

; Oracle bits
ORACLE_WATER_BROKEN    equ 0x01
ORACLE_EARTH_PATTERN   equ 0x02
ORACLE_EARTH_MAGNITUDE equ 0x04
ORACLE_WIND_FIXED      equ 0x08
ORACLE_WIND_DIVERGE    equ 0x10
ORACLE_CRITICAL        equ 0x80

; =============================================================================
; BIOS BOOT
; =============================================================================

boot_start:
    cli
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00
    mov [boot_drive], dl

    ; Set video mode 3 (80x25 colour text) via BIOS INT 10h
    ; Forces NVS 295 or any GPU into a known text mode state
    mov ax, 0x0003
    int 0x10

    ; Print milestone 'B' via BIOS teletype (works before any VGA init)
    mov ah, 0x0E
    mov al, 'B'
    xor bh, bh
    int 0x10

%ifdef BUILD_CD
    ; ── CD / El Torito build ──
    ; boot-load-size in the boot catalog is set to load the ENTIRE image
    ; (all IMAGE_SECTORS sectors) directly to 0x7C00 before we ever run.
    ; Our own payload (sectors 2..N) is therefore ALREADY resident at
    ; physical 0x7E00 -- no disk read needed, and doing one would corrupt
    ; memory (CD LBAs are 2048-byte units, not 512-byte HDD units).
    mov ah, 0x0E
    mov al, 'C'
    xor bh, bh
    int 0x10
%else
    ; ── HDD / USB build ──
    ; BIOS legacy boot (INT 19h) loads only the 512-byte boot sector.
    ; We must load the payload ourselves, to physical 0x7E00 -- the SAME
    ; location El Torito uses for the CD build, so protected_entry lives
    ; at one fixed physical address regardless of boot path.

    ; Check INT13h extensions are present (AH=41h, BX=55AAh)
    mov ah, 0x41
    mov bx, 0x55AA
    mov dl, [boot_drive]
    int 0x13
    jc  .use_chs
    cmp bx, 0xAA55
    jne .use_chs
    test cl, 1
    jz  .use_chs

    ; Extended read (AH=42h) into segment 0x07E0 (= physical 0x7E00)
    mov si, disk_address_packet
    mov dl, [boot_drive]
    mov ah, 0x42
    int 0x13
    jnc .disk_ok

.use_chs:
    ; Legacy CHS fallback (AH=02h) for BIOSes without extensions.
    ; Read PAYLOAD_SECTORS sectors starting at C/H/S = 0/0/2 into 07E0:0000.
    mov ax, 0x07E0
    mov es, ax
    xor bx, bx
    mov ah, 0x02
    mov al, PAYLOAD_SECTORS
    mov ch, 0
    mov cl, 2
    mov dh, 0
    mov dl, [boot_drive]
    int 0x13
    jc  boot_disk_error

.disk_ok:
    mov ah, 0x0E
    mov al, 'H'
    xor bh, bh
    int 0x10
%endif

    ; Copy AP trampoline to 0x8000. Payload lives at physical 0x7E00 in
    ; BOTH build variants (loaded there by us for HDD, or by El Torito's
    ; boot-load-size for CD), same segment as the boot sector (DS=0),
    ; so no segment arithmetic needed either way.
    mov ax, 0x2000          ; segment 0x2000 = physical 0x20000 = AP_TRAMP_PHYS
    mov es, ax
    mov si, ap_trampoline
    xor di, di
    mov cx, (ap_trampoline_end - ap_trampoline + 1) / 2
    cld
    rep movsw

    xor ax, ax
    mov es, ax

    ; Milestone 'T' — AP trampoline copy done
    mov ah, 0x0E
    mov al, 'T'
    xor bh, bh
    int 0x10

    ; A20 - Method 1: BIOS INT 15h AX=2401 (most portable)
    mov ax, 0x2401
    int 0x15

    ; A20 - Method 2: Port 0x92 Fast A20
    in  al, 0x92
    or  al, 00000010b
    and al, 11111110b
    out 0x92, al

    ; A20 - Method 3: Keyboard controller (KBC), bounded — cannot hang
    call a20_kbc_enable

    ; Milestone 'A' — A20 sequence complete (all three methods attempted)
    mov ah, 0x0E
    mov al, 'A'
    xor bh, bh
    int 0x10

    ; GDT
    lgdt [gdt_ptr]

    ; Milestone 'G' — GDT loaded
    mov ah, 0x0E
    mov al, 'G'
    xor bh, bh
    int 0x10

    ; Milestone 'P' — about to jump to protected mode (last real-mode print;
    ; if this is the last letter seen, the far jump or protected_entry itself
    ; is the failure point). MUST print before CR0.PE is set — BIOS
    ; interrupts don't work anymore once protected mode is enabled.
    mov ah, 0x0E
    mov al, 'P'
    xor bh, bh
    int 0x10

    ; Protected mode
    mov eax, cr0
    or  eax, 1
    mov cr0, eax

    jmp dword 0x08:PROTECTED_ENTRY_PHYS

boot_disk_error:
    mov si, boot_error_msg

.loop:
    lodsb
    test al, al
    jz   .halt
    mov  ah, 0x0E
    xor  bh, bh
    int  0x10
    jmp  .loop

.halt:
    cli
    hlt
    jmp .halt

; =============================================================================
; DISK ADDRESS PACKET
; =============================================================================

disk_address_packet:
    db 0x10, 0x00
    dw PAYLOAD_SECTORS
    dw 0x0000
    dw 0x07E0          ; segment 0x07E0 = physical 0x7E00, right after boot sector
    dq 1

boot_drive:  db 0

boot_error_msg:  db "HDGL DISK ERROR",0


; A20 via keyboard controller — bounded retries, never hangs.
; Each wait loop gives up after KBC_TIMEOUT iterations rather than
; spinning forever on hardware with no PS/2 KBC or a non-conforming one.
KBC_TIMEOUT equ 65535

a20_kbc_enable:
    call .kbc_wait_in
    mov  al, 0xAD          ; disable keyboard
    out  0x64, al
    call .kbc_wait_in
    mov  al, 0xD0          ; read output port
    out  0x64, al
    call .kbc_wait_out
    in   al, 0x60
    push ax
    call .kbc_wait_in
    mov  al, 0xD1          ; write output port
    out  0x64, al
    call .kbc_wait_in
    pop  ax
    or   al, 2             ; set A20 bit
    out  0x60, al
    call .kbc_wait_in
    mov  al, 0xAE          ; enable keyboard
    out  0x64, al
    call .kbc_wait_in
    ret
.kbc_wait_in:
    push cx
    mov  cx, KBC_TIMEOUT
.wi:
    in   al, 0x64
    test al, 2
    jz   .wi_done
    loop .wi
.wi_done:
    pop  cx
    ret
.kbc_wait_out:
    push cx
    mov  cx, KBC_TIMEOUT
.wo:
    in   al, 0x64
    test al, 1
    jnz  .wo_done
    loop .wo
.wo_done:
    pop  cx
    ret

; =============================================================================
; GDT
; =============================================================================

align 8

gdt_base:
    dq 0x0000000000000000           ; null
    dq 0x00CF9A000000FFFF           ; 0x08: 32-bit code
    dq 0x00CF92000000FFFF           ; 0x10: data (32 and 64 bit)
    dq 0x00AF9A000000FFFF           ; 0x18: 64-bit code
gdt_end:

gdt_ptr:
    dw gdt_end - gdt_base - 1
    dd gdt_base

; =============================================================================
; BOOT SECTOR PAD
; =============================================================================

times 510 - ($ - $$) db 0
dw 0xAA55

; =============================================================================
; PAYLOAD — 32-BIT PROTECTED MODE ENTRY
; =============================================================================
; File offset 512 = physical 0x10200 when loaded.
; PROTECTED_ENTRY_PHYS = 0x10000 + 512 = 0x10200

BITS 32

protected_entry:
    cli
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov esp, BSP_STACK

    ; Milestone '1' — reached 32-bit protected mode. Direct VGA write
    ; (no BIOS available here); bottom-left corner, out of the way.
    mov byte [0xB8000 + 24*160 + 0], '1'
    mov byte [0xB8000 + 24*160 + 1], 0x4F

    ; Build identity page tables (0..4 GiB, 2 MB pages)
    call build_page_tables

    ; Milestone '2' — page tables built
    mov byte [0xB8000 + 24*160 + 2], '2'
    mov byte [0xB8000 + 24*160 + 3], 0x4F

    ; PAE
    mov eax, cr4
    or  eax, (1 << 5)
    mov cr4, eax

    ; EFER.LME
    mov ecx, 0xC0000080
    rdmsr
    or  eax, (1 << 8)
    wrmsr

    ; CR3
    mov eax, PML4_PHYS
    mov cr3, eax

    ; Paging on
    mov eax, cr0
    or  eax, (1 << 31)
    mov cr0, eax

    ; Milestone '3' — paging enabled, about to enter long mode
    mov byte [0xB8000 + 24*160 + 4], '3'
    mov byte [0xB8000 + 24*160 + 5], 0x4F

    ; Far jump to 64-bit entry — LONG_MODE_ENTRY_PHYS computed below
    jmp dword 0x18:LONG_MODE_ENTRY_PHYS

; =============================================================================
; PAGE TABLE CONSTRUCTION (32-bit)
; =============================================================================

build_page_tables:
    pushad

    ; Zero PML4 + PDPT + 4 PDs = 6 pages = 0x6000 bytes
    mov edi, PML4_PHYS
    xor eax, eax
    mov ecx, 0x6000 / 4
    cld
    rep stosd

    ; PML4[0] -> PDPT
    mov dword [PML4_PHYS + 0], PDPT_PHYS | 0x003
    mov dword [PML4_PHYS + 4], 0

    ; PDPT[0..3] -> PD0..PD3
    mov dword [PDPT_PHYS +  0], PD0_PHYS | 0x003
    mov dword [PDPT_PHYS +  4], 0
    mov dword [PDPT_PHYS +  8], PD1_PHYS | 0x003
    mov dword [PDPT_PHYS + 12], 0
    mov dword [PDPT_PHYS + 16], PD2_PHYS | 0x003
    mov dword [PDPT_PHYS + 20], 0
    mov dword [PDPT_PHYS + 24], PD3_PHYS | 0x003
    mov dword [PDPT_PHYS + 28], 0

    ; PD0: 0..1 GiB (512 entries × 2 MB = 1 GiB)
    mov edi, PD0_PHYS
    xor eax, eax
    mov ecx, 512
.pd0:
    mov edx, eax
    or  edx, 0x83           ; present + RW + huge (2MB)
    mov [edi],   edx
    mov dword [edi+4], 0
    add eax, 0x200000
    add edi, 8
    loop .pd0

    ; PD1: 1..2 GiB
    mov edi, PD1_PHYS
    mov eax, 0x40000000
    mov ecx, 512
.pd1:
    mov edx, eax
    or  edx, 0x83
    mov [edi],   edx
    mov dword [edi+4], 0
    add eax, 0x200000
    add edi, 8
    loop .pd1

    ; PD2: 2..3 GiB
    mov edi, PD2_PHYS
    mov eax, 0x80000000
    mov ecx, 512
.pd2:
    mov edx, eax
    or  edx, 0x83
    mov [edi],   edx
    mov dword [edi+4], 0
    add eax, 0x200000
    add edi, 8
    loop .pd2

    ; PD3: 3..4 GiB  (wraps at 4 GiB, ok for identity map)
    mov edi, PD3_PHYS
    mov eax, 0xC0000000
    mov ecx, 512
.pd3:
    mov edx, eax
    or  edx, 0x83
    mov [edi],   edx
    mov dword [edi+4], 0
    add eax, 0x200000
    add edi, 8
    loop .pd3

    popad
    ret

; =============================================================================
; 64-BIT BSP ENTRY
; =============================================================================
; THIS LABEL MUST BE THE FIRST BITS 64 INSTRUCTION IN THE FILE.
; LONG_MODE_ENTRY_PHYS is computed from its file position.

BITS 64

long_mode_entry:
    cli
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov rsp, BSP_STACK

    ; Milestone '4' — reached 64-bit long mode (independent of COM1)
    mov byte [0xB8000 + 24*160 + 6], '4'
    mov byte [0xB8000 + 24*160 + 7], 0x4F

    ; COM1 serial init (115200 8N1)
    ; Works regardless of GPU - critical for bare metal debug
    mov dx, 0x3F9
    mov al, 0x00
    out dx, al          ; disable interrupts
    mov dx, 0x3FB
    mov al, 0x80
    out dx, al          ; DLAB=1
    mov dx, 0x3F8
    mov al, 0x01
    out dx, al          ; divisor lo = 1 (115200 baud)
    mov dx, 0x3F9
    mov al, 0x00
    out dx, al          ; divisor hi
    mov dx, 0x3FB
    mov al, 0x03
    out dx, al          ; 8N1, DLAB=0
    mov dx, 0x3FC
    mov al, 0x03
    out dx, al          ; RTS+DTR

    ; Send milestone 'L' = long mode entry confirmed
    call serial_putchar_L   ; 'L' = long mode

    ; Detect logical processor count via CPUID
    mov eax, 1
    cpuid
    shr ebx, 16
    and ebx, 0xFF
    test ebx, ebx
    jnz .cpu_ok
    mov ebx, 1
.cpu_ok:
    mov [CPU_COUNT], rbx

; (CPU count stored in CPU_COUNT)

    ; Enable multi-core if > 1 CPU detected. APs run substrate roles
    ; (WATER/EARTH/WIND) and fill gaps with BPSW on their residue class.
    ; Trampoline uses embedded GDT at fixed physical offset — no BIOS GDT.
    ; AP_WAIT_TIMEOUT fallback keeps single-core path safe on any hardware.
    cmp rbx, 1
    jle .force_serial
    mov qword [PARALLEL_MODE], 1
    jmp .mode_done
.force_serial:
.serial:
    mov qword [PARALLEL_MODE], 0
.mode_done:

    ; ─── Canonical initial state: Ω = 0·φ + 1 ───
    mov qword [STATE_A],        0
    mov qword [STATE_B],        1
    mov qword [STATE_K],        0
    mov qword [FIRE_A],         0
    mov qword [FIRE_B],         1
    mov qword [FIRE_K],         0
    mov qword [WATER_A],        0
    mov qword [WATER_B],        1
    mov qword [EARTH_N],        1       ; N(0,1) = 1
    mov qword [EARTH_N_FIRE],   0
    mov qword [EARTH_DELTA],    0
    mov qword [EARTH_PREV_DELTA], 0     ; no previous delta yet
    mov qword [WIND_RES_A],     0
    mov qword [WIND_RES_B],     0
    mov qword [WIND_FIX],       0
    mov qword [REQUEST_K],      0
    mov qword [DONE_WATER],     0
    mov qword [DONE_EARTH],     0
    mov qword [DONE_WIND],      0
    mov qword [READY_MASK],     1
    mov qword [ORACLE],         0
    mov qword [TRINARY],        0
    mov qword [STRATEGY],       STRATEGY_FLOWING
    mov qword [YIN],            2
    mov qword [ORACLE_AP_TIMEOUT_FLAG], 0

    ; ── Zero all BPSW state and scratch ──
    mov  rdi, BPSW_C0
    xor  rax, rax
    mov  rcx, (0x200 + 4*BPSW_CORE_STRIDE) / 8
    rep  stosq
    mov  rdi, LS_BASE
    mov  rcx, (4 * LS_STRIDE) / 8
    rep  stosq
    mov  rdi, TOT_TESTED
    mov  rcx, 64 / 8
    rep  stosq

    ; ── Resume: check for valid save at 0x9F000 ──
    mov  rax, [RESUME_BASE + RESUME_OFF_MAGIC]
    mov  rbx, RESUME_MAGIC
    cmp  rax, rbx
    jne  .fresh_start

    ; Verify checksum: XOR of four candidates
    mov  rax, [RESUME_BASE + RESUME_OFF_C0]
    xor  rax, [RESUME_BASE + RESUME_OFF_C1]
    xor  rax, [RESUME_BASE + RESUME_OFF_C2]
    xor  rax, [RESUME_BASE + RESUME_OFF_C3]
    cmp  rax, [RESUME_BASE + RESUME_OFF_CKSUM]
    jne  .fresh_start

    ; Valid save — restore candidates
    mov  rax, [RESUME_BASE + RESUME_OFF_C0]
    mov  [BPSW_C0 + BC_CAND], rax
    mov  rax, [RESUME_BASE + RESUME_OFF_C1]
    mov  [BPSW_C1 + BC_CAND], rax
    mov  rax, [RESUME_BASE + RESUME_OFF_C2]
    mov  [BPSW_C2 + BC_CAND], rax
    mov  rax, [RESUME_BASE + RESUME_OFF_C3]
    mov  [BPSW_C3 + BC_CAND], rax
    jmp  .bpsw_init_done

.fresh_start:
    ; Core 0: residue 3 mod 8
    mov  qword [BPSW_C0 + BC_CAND], 3
    ; Core 1: residue 5 mod 8
    mov  qword [BPSW_C1 + BC_CAND], 5
    ; Core 2: residue 7 mod 8
    mov  qword [BPSW_C2 + BC_CAND], 7
    ; Core 3: residue 1 mod 8 (starts at 9; 1 is not prime)
    mov  qword [BPSW_C3 + BC_CAND], 9
    ; Clear resume record
    mov  qword [RESUME_BASE + RESUME_OFF_MAGIC], 0

.bpsw_init_done:
    ; Seed rate tracking with current TSC
    rdtsc
    shl  rdx, 32
    or   rax, rdx
    mov  [TOT_TSC_PREV],  rax
    mov  qword [TOT_TEST_PREV], 0
    mov qword [PHASE],          0
    mov qword [DEPTH],          0

    ; VGA init
    call vga_init

    ; Launch APs if multi-core
    cmp qword [PARALLEL_MODE], 1
    jne .bsp_fire
    call start_aps
; (parallel mode set by start_aps)

.bsp_fire:
    call role_fire

.halt:
    cli
    hlt
    jmp .halt

; =============================================================================
; START APPLICATION PROCESSORS
; =============================================================================

start_aps:
    ; Enable BSP LAPIC (xAPIC, bit 11 only)
    mov ecx, 0x1B
    rdmsr
    mov r9, rax                ; save original MSR value
    or  eax, (1 << 11)
    and eax, ~(1 << 10)        ; xAPIC only
    wrmsr

    ; Get LAPIC MMIO base from MSR (bits 31:12)
    mov eax, r9d
    and eax, 0xFFFFF000
    test eax, eax
    jnz .got_lapic_base
    mov eax, 0xFEE00000
.got_lapic_base:
    mov r8d, eax

    ; ── INIT IPI sequence per Intel MP spec ──
    ; ICR_HIGH: destination = 0 (all-exc-self shorthand overrides this)
    mov dword [r8 + 0x310], 0x00000000
    ; ICR_LOW: INIT, level assert, all-excluding-self shorthand
    mov dword [r8 + 0x300], 0x000C4500

    ; Poll send-pending bit (bit 12)
.poll_init:
    mov eax, [r8 + 0x300]
    test eax, (1 << 12)
    jnz  .poll_init

    ; INIT de-assert
    mov dword [r8 + 0x310], 0x00000000
    mov dword [r8 + 0x300], 0x000C8500
.poll_deassert:
    mov eax, [r8 + 0x300]
    test eax, (1 << 12)
    jnz  .poll_deassert

    ; 10ms delay
    push rcx
    mov  ecx, 300000
.init_delay: dec ecx
    jnz  .init_delay
    pop  rcx

    ; SIPI #1
    mov dword [r8 + 0x310], 0x00000000
    mov dword [r8 + 0x300], 0x000C4620
.poll_sipi1:
    mov eax, [r8 + 0x300]
    test eax, (1 << 12)
    jnz  .poll_sipi1

    ; 200µs delay
    push rcx
    mov  ecx, 5000
.sipi1_delay: dec ecx
    jnz  .sipi1_delay
    pop  rcx

    ; SIPI #2
    mov dword [r8 + 0x310], 0x00000000
    mov dword [r8 + 0x300], 0x000C4620
.poll_sipi2:
    mov eax, [r8 + 0x300]
    test eax, (1 << 12)
    jnz  .poll_sipi2

    ; ── Wait for APs (50M iterations) ──
    ; Real 3GHz: ~67ms — more than enough
    ; QEMU (no KVM): may time out — falls back to single-core gracefully
    mov r9, 50000000
.wait_aps:
    mov rax, [READY_MASK]
    and eax, 0xF
    cmp eax, 0xF
    je  .aps_ok
    dec r9
    jnz .wait_aps

    ; Timed out — use however many APs responded
    mov rax, [READY_MASK]
    and eax, 0xF
    cmp eax, 1          ; only BSP?
    je  .ap_fallback
    jmp .aps_ok         ; partial parallel is fine

.ap_fallback:
    mov qword [PARALLEL_MODE], 0
    mov qword [ORACLE_AP_TIMEOUT_FLAG], 1
    ret

.aps_ok:
    ret


; =============================================================================
; FIRE — CPU 0 (Operator / Strategy Selector)
; =============================================================================

role_fire:
    mov qword [READY_MASK], 1       ; mark CPU 0 ready

fire_cycle:
    ; ── Snapshot current Ω ──
    mov r8,  [STATE_A]
    mov r9,  [STATE_B]
    mov r10, [STATE_K]

    ; ── FIRE: (a,b) -> (a+b, a) ──
    mov rax, r8
    add rax, r9
    mov [FIRE_A], rax
    mov [FIRE_B], r8
    inc r10
    mov [FIRE_K], r10

    ; ── Serial or parallel path ──
    cmp qword [PARALLEL_MODE], 0
    je  .serial

    ; Parallel: publish request and wait
    mov [REQUEST_K], r10

.wait_water:
    mov rax, [DONE_WATER]
    cmp rax, r10
    jne .wait_water
.wait_earth:
    mov rax, [DONE_EARTH]
    cmp rax, r10
    jne .wait_earth
.wait_wind:
    mov rax, [DONE_WIND]
    cmp rax, r10
    jne .wait_wind
    jmp .commit

.serial:
    call water_compute
    call earth_compute
    call wind_compute

.commit:
    ; ── Wu-Wei strategy selection ──
    mov rax, [ORACLE]
    call fire_select_strategy

    ; ── BPSW oracle — throttled on PRIME_TEST_STRIDE ──
    mov rax, r10
    test rax, (PRIME_TEST_STRIDE - 1)
    jnz .skip_bpsw
    ; C0 always (FIRE owns this core's candidates)
    mov  r12, BPSW_C0
    mov  r13, LS_BASE
    call bpsw_step
    ; C1/C2/C3: only in serial mode (parallel mode = APs own these)
    cmp  qword [PARALLEL_MODE], 0
    jne  .skip_bpsw
    mov  r12, BPSW_C1
    mov  r13, LS_BASE + LS_STRIDE
    call bpsw_step
    mov  r12, BPSW_C2
    mov  r13, LS_BASE + LS_STRIDE*2
    call bpsw_step
    mov  r12, BPSW_C3
    mov  r13, LS_BASE + LS_STRIDE*3
    call bpsw_step
.skip_bpsw:

    ; ── Commit FIRE result as new canonical state ──
    ; (strategy may modify this later - for now, always advance)
    mov rax, [FIRE_A]
    mov rbx, [FIRE_B]
    mov [STATE_A], rax
    mov [STATE_B], rbx
    mov [STATE_K], r10

    ; ── YIN: s -> s² - 2 ──
    mov rax, [YIN]
    imul rax, rax
    sub  rax, 2
    mov  [YIN], rax

    ; ── Completion: 0->1->2->3->0 ──
    inc  qword [PHASE]
    and  qword [PHASE], 3

    ; ── Depth ──
    inc  qword [DEPTH]

    ; ── Display every PRINT_EVERY iterations ──
    mov rax, r10
    test rax, PRINT_MASK
    jnz fire_cycle

    call vga_update
    jmp fire_cycle

; ============================================================================
; FIRE: WU-WEI STRATEGY SELECTOR
; Input: rax = ORACLE bitfield
; ============================================================================

fire_select_strategy:
    ; CRITICAL: halt and display
    test al, ORACLE_CRITICAL
    jnz  .critical

    ; EARTH magnitude wrong: redirect (rebase)
    test al, ORACLE_EARTH_MAGNITUDE
    jnz  .redirect

    ; EARTH pattern broken: non-action
    test al, ORACLE_EARTH_PATTERN
    jnz  .nonaction

    ; WIND convergence: log it
    test al, ORACLE_WIND_FIXED
    jnz  .converge

    ; WATER broken: flag but continue
    test al, ORACLE_WATER_BROKEN
    jnz  .water_anom

    ; All clear
    mov qword [STRATEGY], STRATEGY_FLOWING
    ret

.critical:
    mov qword [STRATEGY], STRATEGY_CRITICAL
    call vga_update          ; force display
    cli
    hlt                      ; deliberate halt on critical
    jmp .critical

.redirect:
    mov qword [STRATEGY], STRATEGY_REDIRECT
    ; Rebase: reset STATE to (0,1) to restart from known phi seed
    ; In a more sophisticated version this would be a soft reset
    ret

.nonaction:
    mov qword [STRATEGY], STRATEGY_NONACTION
    ret

.converge:
    mov qword [STRATEGY], STRATEGY_CONVERGE
    ret

.water_anom:
    ; Water anomaly with no other flags: continue but log
    mov qword [STRATEGY], STRATEGY_FLOWING
    ret

; =============================================================================
; WATER — CPU 1 (Inverse Verification)
; =============================================================================
; WATER(a,b) = (b, a-b)
; Checks: WATER(FIRE(Ω)) == Ω
; WATER(a+b, a) = (a, (a+b)-a) = (a, b) = Ω  -- always true for exact arithmetic
; So ORACLE_WATER_BROKEN fires only on arithmetic error (impossible mod 2^64)

water_compute:
    mov r8, [STATE_A]
    mov r9, [STATE_B]

    ; Compute WATER of current state
    mov rax, r9
    mov rbx, r8
    sub rbx, r9
    mov [WATER_A], rax
    mov [WATER_B], rbx

    ; Verify WATER(FIRE(Ω)) == Ω
    ; FIRE = (FIRE_A, FIRE_B) = (a+b, a)
    ; WATER(a+b, a) = (a, b)  so check WATER_FIRE_A==STATE_A, WATER_FIRE_B==STATE_B
    mov rcx, [FIRE_A]
    mov rdx, [FIRE_B]
    ; WATER of FIRE: first = FIRE_B = a, second = FIRE_A - FIRE_B = b
    cmp rdx, r8         ; FIRE_B == STATE_A?
    jne .broken
    mov rsi, rcx
    sub rsi, rdx
    cmp rsi, r9         ; FIRE_A - FIRE_B == STATE_B?
    jne .broken

    ; Clear water bit in oracle
    mov rax, [ORACLE]
    and rax, ~ORACLE_WATER_BROKEN
    mov [ORACLE], rax
    ret

.broken:
    or qword [ORACLE], ORACLE_WATER_BROKEN
    or qword [ORACLE], ORACLE_CRITICAL      ; water failure is always critical
    ret

; =============================================================================
; WATER WORKER — CPU 1 (AP loop)
; =============================================================================

role_water:
    xor r15d, r15d
    lock or qword [READY_MASK], 2
    mov  r12, BPSW_C1
    mov  r13, LS_BASE + LS_STRIDE

.wait:
    mov rax, [REQUEST_K]
    cmp rax, r15
    je  .bpsw_water          ; no new substrate tick — run BPSW
    mov r15, rax
    call water_compute
    mov [DONE_WATER], r15
.bpsw_water:
    call bpsw_step            ; fills idle time between substrate ticks
    jmp .wait

; =============================================================================
; EARTH — CPU 2 (N_phi Pattern Oracle)
; =============================================================================
; N_phi(a,b) = -a² + ab + b²
;
; WU-WEI: For Fibonacci pairs, N oscillates: N(k) = (-1)^k.
; Expected delta each step: -(EARTH_N)*2  (flips sign, magnitude 2)
; If delta != -2*N(prev): pattern broken -> ORACLE_EARTH_PATTERN
; If |delta| != 2:         magnitude wrong -> ORACLE_EARTH_MAGNITUDE

earth_compute:
    push r12
    push r13
    mov r8, [STATE_A]
    mov r9, [STATE_B]

    ; ── N(current) = -a² + ab + b² ──
    mov rax, r8
    imul rax, r8
    neg  rax                    ; -a²
    mov  rbx, r8
    imul rbx, r9
    add  rax, rbx               ; -a² + ab
    mov  rbx, r9
    imul rbx, r9
    add  rax, rbx               ; -a² + ab + b²
    mov  [EARTH_N], rax

    ; ── N(FIRE(current)): FIRE=(a+b, a) ──
    mov r10, r8
    add r10, r9                 ; r10 = a+b = FIRE_A
    mov r11, r8                 ; r11 = a   = FIRE_B

    mov rax, r10
    imul rax, r10
    neg  rax
    mov  rbx, r10
    imul rbx, r11
    add  rax, rbx
    mov  rbx, r11
    imul rbx, r11
    add  rax, rbx
    mov  [EARTH_N_FIRE], rax

    ; ── Delta = N(FIRE) - N(current) ──
    mov rcx, [EARTH_N]
    mov rdx, [EARTH_N_FIRE]
    mov rax, rdx
    sub rax, rcx               ; delta = N_fire - N_curr
    mov [EARTH_DELTA], rax

    ; ── Pattern check: |delta| should be 2 ──
    mov rbx, rax
    ; abs(rax): if negative, negate
    test rax, rax
    jns  .pos
    neg  rbx
.pos:
    cmp rbx, 2
    jne .magnitude_wrong

    ; ── Sign check: delta should be opposite sign of N(current) ──
    ; N positive -> delta should be negative
    ; N negative -> delta should be positive
    ; i.e. N(current) * delta < 0  (opposite signs)
    ; Skip sign check on very first iteration (prev_delta == 0)
    cmp qword [EARTH_PREV_DELTA], 0
    je  .first_iter

    ; Check alternation: delta sign should be opposite of prev_delta sign
    mov r12, rax                ; current delta
    mov r13, [EARTH_PREV_DELTA]
    ; If both same sign -> pattern broken
    ; r12 and r13: test sign agreement via XOR of sign bits
    mov r14, r12
    xor r14, r13
    ; If bit 63 of XOR is 0, both same sign -> broken
    test r14, r14
    js   .signs_ok
    ; Same sign = pattern broken
    or   qword [ORACLE], ORACLE_EARTH_PATTERN
    jmp  .done

.signs_ok:
    ; Pattern good: clear earth bits
    mov rbx, [ORACLE]
    and rbx, ~(ORACLE_EARTH_PATTERN | ORACLE_EARTH_MAGNITUDE)
    mov [ORACLE], rbx
    jmp .done

.first_iter:
    ; First iteration: just clear earth error bits
    mov rbx, [ORACLE]
    and rbx, ~(ORACLE_EARTH_PATTERN | ORACLE_EARTH_MAGNITUDE)
    mov [ORACLE], rbx
    jmp .done

.magnitude_wrong:
    or  qword [ORACLE], ORACLE_EARTH_MAGNITUDE
    jmp .done

.done:
    ; Save delta for next iteration
    mov rax, [EARTH_DELTA]
    mov [EARTH_PREV_DELTA], rax

    ; ── Trinary projection: sign of N ──
    mov rax, [EARTH_N]
    test rax, rax
    jz   .tri_zero
    js   .tri_neg
    mov qword [TRINARY], 1
    pop  r13
    pop  r12
    ret
.tri_neg:
    mov qword [TRINARY], -1
    pop  r13
    pop  r12
    ret
.tri_zero:
    mov qword [TRINARY], 0
    pop  r13
    pop  r12
    ret

; =============================================================================
; EARTH WORKER — CPU 2 (AP loop)
; =============================================================================

role_earth:
    push rax
    mov al, 'E'
    call serial_putchar
    pop rax
    xor r15d, r15d
    lock or qword [READY_MASK], 4
    mov  r12, BPSW_C2
    mov  r13, LS_BASE + LS_STRIDE*2

.wait:
    mov rax, [REQUEST_K]
    cmp rax, r15
    je  .bpsw_earth
    mov r15, rax
    call earth_compute
    mov [DONE_EARTH], r15
.bpsw_earth:
    call bpsw_step
    jmp .wait

; =============================================================================
; WIND — CPU 3 (T(X) Fixed-Point Residual)
; =============================================================================
; T(X) = 1 + 1/X. Fixed point: X = phi.
; In Z[phi] with X = a*phi + b:
;   T(X) - X  residuals:
;     phi coeff:  a² + 2ab - a
;     const coeff: a² + b² - b - 1
; Both zero iff X = phi (the fixed point).
;
; WU-WEI: residuals grow as Fibonacci grows. 
; WIND_FIXED fires when both are zero (rare, meaningful event).
; WIND_DIVERGE fires when |res_a| + |res_b| exceeds threshold.

WIND_DIV_THRESH    equ 0x1000000000   ; ~68 billion: divergence threshold

wind_compute:
    push r12
    mov r8, [STATE_A]
    mov r9, [STATE_B]

    ; ── phi-coeff residual: a² + 2ab - a ──
    mov rax, r8
    imul rax, r8                ; a²
    mov  rbx, r8
    imul rbx, r9                ; ab
    add  rbx, rbx               ; 2ab
    add  rax, rbx               ; a² + 2ab
    sub  rax, r8                ; a² + 2ab - a
    mov  [WIND_RES_A], rax

    ; ── const residual: a² + b² - b - 1 ──
    mov rcx, r8
    imul rcx, r8                ; a²
    mov  rdx, r9
    imul rdx, r9                ; b²
    add  rcx, rdx               ; a² + b²
    sub  rcx, r9                ; a² + b² - b
    dec  rcx                    ; a² + b² - b - 1
    mov  [WIND_RES_B], rcx

    ; ── Fixed point check ──
    test rax, rax
    jnz  .not_fixed
    test rcx, rcx
    jnz  .not_fixed
    mov  qword [WIND_FIX], 1
    or   qword [ORACLE], ORACLE_WIND_FIXED
    pop  r12
    ret

.not_fixed:
    mov qword [WIND_FIX], 0

    ; ── Divergence check ──
    ; |res_a| + |res_b| > threshold?
    mov  rax, [WIND_RES_A]
    test rax, rax
    jns  .pos_a
    neg  rax
.pos_a:
    mov  rbx, [WIND_RES_B]
    test rbx, rbx
    jns  .pos_b
    neg  rbx
.pos_b:
    add  rax, rbx
    mov  r12, WIND_DIV_THRESH
    cmp  rax, r12
    jbe  .no_diverge
    or   qword [ORACLE], ORACLE_WIND_DIVERGE
    jmp  .wind_done

.no_diverge:
    ; Clear wind bits
    mov  rax, [ORACLE]
    and  rax, ~(ORACLE_WIND_FIXED | ORACLE_WIND_DIVERGE)
    mov  [ORACLE], rax

.wind_done:
    pop  r12
    ret

; =============================================================================
; WIND WORKER — CPU 3 (AP loop)
; =============================================================================

role_wind:
    push rax
    mov al, 'N'
    call serial_putchar
    pop rax
    xor r15d, r15d
    lock or qword [READY_MASK], 8
    mov  r12, BPSW_C3
    mov  r13, LS_BASE + LS_STRIDE*3

.wait:
    mov rax, [REQUEST_K]
    cmp rax, r15
    je  .bpsw_wind
    mov r15, rax
    call wind_compute
    mov [DONE_WIND], r15
.bpsw_wind:
    call bpsw_step
    jmp .wait

; =============================================================================
; FIBONACCI–LEGENDRE PROBABLE-PRIME ORACLE
; =============================================================================
;
; (modmul64 defined below with BPSW functions)

; =============================================================================
; modmul64: (RAX * RBX) mod RCX -> RAX
; =============================================================================

modmul64:
    push rdx
    mul  rbx
    div  rcx
    mov  rax, rdx
    pop  rdx
    ret

; =============================================================================
; pow_mod_int: base^exp mod m -> RAX
;   RDI=base  RSI=exp  RDX=modulus
; =============================================================================

pow_mod_int:
    push rbx
    push rcx
    push r8
    push r9
    push r10
    mov  r8, rdi
    mov  r9, rsi
    mov  r10, rdx
    mov  rax, 1
.pmi_loop:
    test r9, r9
    jz   .pmi_done
    test r9, 1
    jz   .pmi_sq
    mov  rbx, r8
    mov  rcx, r10
    call modmul64
.pmi_sq:
    push rax
    mov  rax, r8
    mov  rbx, r8
    mov  rcx, r10
    call modmul64
    mov  r8, rax
    pop  rax
    shr  r9, 1
    jmp  .pmi_loop
.pmi_done:
    pop  r10
    pop  r9
    pop  r8
    pop  rcx
    pop  rbx
    ret

; =============================================================================
; miller_rabin: strong pseudoprime test
;   RDI=n  RBX=base  ->  RAX=1(pass) 0(fail)
; =============================================================================

miller_rabin:
    push rbx
    push rcx
    push rdx
    push r8
    push r9
    push r10
    push r11
    mov  r10, rdi
    mov  r11, rbx
    mov  r8, r10
    dec  r8
    xor  r9, r9
    mov  rcx, r8
.mr_find_sd:
    test rcx, 1
    jnz  .mr_sd_done
    shr  rcx, 1
    inc  r9
    jmp  .mr_find_sd
.mr_sd_done:
    mov  rdi, r11
    mov  rsi, rcx
    mov  rdx, r10
    call pow_mod_int
    cmp  rax, 1
    je   .mr_pass
    cmp  rax, r8
    je   .mr_pass
    mov  rdx, r9
    dec  rdx
    jz   .mr_fail
.mr_loop:
    push rdx
    push r8
    push r10
    mov  rbx, rax
    mov  rcx, r10
    call modmul64
    pop  r10
    pop  r8
    pop  rdx
    cmp  rax, r8
    je   .mr_pass
    test rax, rax
    jz   .mr_fail
    dec  rdx
    jnz  .mr_loop
.mr_fail:
    xor  rax, rax
    jmp  .mr_ret
.mr_pass:
    mov  rax, 1
.mr_ret:
    pop  r11
    pop  r10
    pop  r9
    pop  r8
    pop  rdx
    pop  rcx
    pop  rbx
    ret

; =============================================================================
; jacobi: (a|n) -> RAX in {-1,0,+1}
;   RDI=a (signed)  RSI=n (odd positive)
; =============================================================================

jacobi:
    push rbx
    push rcx
    push rdx
    push r8
    push r9
    push r10
    push r11
    mov  r8, rdi
    mov  r9, rsi
    mov  r10, 1
    mov  rax, r8
    cqo
    mov  rbx, r9
    idiv rbx
    mov  r8, rdx
    test r8, r8
    jns  .jac_a_ok
    add  r8, r9
.jac_a_ok:
.jac_loop:
    test r8, r8
    jz   .jac_zero
    cmp  r8, 1
    je   .jac_ret
    xor  r11, r11
.jac_strip:
    test r8, 1
    jnz  .jac_stripped
    shr  r8, 1
    inc  r11
    jmp  .jac_strip
.jac_stripped:
    test r11, 1
    jz   .jac_eeven
    mov  rax, r9
    and  rax, 7
    cmp  rax, 3
    je   .jac_flip2
    cmp  rax, 5
    je   .jac_flip2
    jmp  .jac_eeven
.jac_flip2:
    neg  r10
.jac_eeven:
    cmp  r8, 1
    je   .jac_ret
    mov  rax, r8
    and  rax, 3
    cmp  rax, 3
    jne  .jac_no_qr
    mov  rax, r9
    and  rax, 3
    cmp  rax, 3
    jne  .jac_no_qr
    neg  r10
.jac_no_qr:
    mov  rax, r9
    xor  rdx, rdx
    div  r8
    mov  r9, r8
    mov  r8, rdx
    jmp  .jac_loop
.jac_zero:
    xor  rax, rax
    jmp  .jac_done
.jac_ret:
    mov  rax, r10
.jac_done:
    pop  r11
    pop  r10
    pop  r9
    pop  r8
    pop  rdx
    pop  rcx
    pop  rbx
    ret

; =============================================================================
; selfridge_r: Selfridge Method A, stores D/Q into [R12+BC_SEL_D/Q]
;   RDI=n  R12=core block  ->  RAX=0 ok, 1 composite
; =============================================================================

selfridge_r:
    push rbx
    push rcx
    push rdx
    push r8
    push r9
    mov  r8, rdi
    mov  r9, 5
    xor  rcx, rcx
.sel_loop:
    mov  rax, r9
    test rcx, rcx
    jz   .sel_dpos
    neg  rax
.sel_dpos:
    mov  [r12 + BC_SEL_D], rax
    mov  rdi, rax
    mov  rsi, r8
    call jacobi
    cmp  rax, -1
    je   .sel_found
    cmp  rax, 0
    je   .sel_jzero
    add  r9, 2
    xor  rcx, 1
    cmp  r9, 10000
    jb   .sel_loop
    mov  rax, 1
    jmp  .sel_done
.sel_jzero:
    cmp  r8, r9
    je   .sel_found
    mov  rax, 1
    jmp  .sel_done
.sel_found:
    mov  rdx, [r12 + BC_SEL_D]
    mov  rax, 1
    sub  rax, rdx
    sar  rax, 2
    mov  [r12 + BC_SEL_Q], rax
    xor  rax, rax
.sel_done:
    pop  r9
    pop  r8
    pop  rdx
    pop  rcx
    pop  rbx
    ret

; =============================================================================
; strong_lucas_r: strong Lucas probable prime (NO vpsp)
;   RDI=n  R12=core block (reads BC_SEL_D/Q)  R13=Lucas scratch base
;   ->  RAX=1 pass (slpsp), 0 fail
; =============================================================================

strong_lucas_r:
    push rbx
    push rcx
    push rdx
    push r8
    push r9
    push r10
    push r11
    push r14
    push r15
    mov  r15, rdi
    mov  [r13 + LSO_N], r15

    mov  rax, [r12 + BC_SEL_D]
    cqo
    mov  rbx, r15
    idiv rbx
    mov  rax, rdx
    test rax, rax
    jns  .sl_dok
    add  rax, r15
.sl_dok:
    mov  [r13 + LSO_D], rax

    mov  rax, [r12 + BC_SEL_Q]
    cqo
    mov  rbx, r15
    idiv rbx
    mov  rax, rdx
    test rax, rax
    jns  .sl_qok
    add  rax, r15
.sl_qok:
    mov  [r13 + LSO_Q], rax

    mov  rax, r15
    inc  rax
    shr  rax, 1
    mov  [r13 + LSO_INV2], rax

    mov  rax, r15
    inc  rax
    xor  r8, r8
.sl_strip:
    test rax, 1
    jnz  .sl_stripped
    shr  rax, 1
    inc  r8
    jmp  .sl_strip
.sl_stripped:
    mov  [r13 + LSO_S],    r8
    mov  [r13 + LSO_DODD], rax

    xor  rax, rax
    mov  [r13 + LSO_U], rax
    mov  rax, 2
    mov  [r13 + LSO_V], rax
    mov  rax, 1
    mov  [r13 + LSO_QK], rax

    mov  rax, [r13 + LSO_DODD]
    bsr  r9, rax

.sl_bit:
    ; Double
    mov  rax, [r13 + LSO_U]
    mov  rbx, [r13 + LSO_V]
    mov  rcx, [r13 + LSO_N]
    call modmul64
    mov  [r13 + LSO_U2], rax

    mov  rax, [r13 + LSO_V]
    mov  rbx, [r13 + LSO_V]
    mov  rcx, [r13 + LSO_N]
    call modmul64
    mov  rbx, [r13 + LSO_QK]
    add  rbx, rbx
    cmp  rbx, [r13 + LSO_N]
    jb   .sl_qk2ok
    sub  rbx, [r13 + LSO_N]
.sl_qk2ok:
    sub  rax, rbx
    jns  .sl_v2ok
    add  rax, [r13 + LSO_N]
.sl_v2ok:
    mov  [r13 + LSO_V2], rax

    mov  rax, [r13 + LSO_QK]
    mov  rbx, [r13 + LSO_QK]
    mov  rcx, [r13 + LSO_N]
    call modmul64

    mov  rbx, [r13 + LSO_U2]
    mov  [r13 + LSO_U], rbx
    mov  rbx, [r13 + LSO_V2]
    mov  [r13 + LSO_V], rbx
    mov  [r13 + LSO_QK], rax

    ; Add if bit set
    mov  rax, [r13 + LSO_DODD]
    mov  rcx, r9
    shr  rax, cl
    test rax, 1
    jz   .sl_noadd

    mov  rax, [r13 + LSO_U]
    mov  [r13 + LSO_U2], rax
    add  rax, [r13 + LSO_V]
    cmp  rax, [r13 + LSO_N]
    jb   .sl_uok
    sub  rax, [r13 + LSO_N]
.sl_uok:
    mov  rbx, [r13 + LSO_INV2]
    mov  rcx, [r13 + LSO_N]
    call modmul64
    mov  [r13 + LSO_U], rax

    mov  rax, [r13 + LSO_D]
    mov  rbx, [r13 + LSO_U2]
    mov  rcx, [r13 + LSO_N]
    call modmul64
    add  rax, [r13 + LSO_V]
    cmp  rax, [r13 + LSO_N]
    jb   .sl_vok
    sub  rax, [r13 + LSO_N]
.sl_vok:
    mov  rbx, [r13 + LSO_INV2]
    mov  rcx, [r13 + LSO_N]
    call modmul64
    mov  [r13 + LSO_V], rax

    mov  rax, [r13 + LSO_QK]
    mov  rbx, [r13 + LSO_Q]
    mov  rcx, [r13 + LSO_N]
    call modmul64
    mov  [r13 + LSO_QK], rax

.sl_noadd:
    test r9, r9
    jz   .sl_dd
    dec  r9
    jmp  .sl_bit

.sl_dd:
    ; Save V_d for display
    mov  rax, [r13 + LSO_V]
    mov  [r13 + LSO_VD], rax

    ; Strong pass: U_d=0 or V_d=0 (r=0)
    mov  rax, [r13 + LSO_U]
    test rax, rax
    jz   .sl_pass
    mov  rax, [r13 + LSO_V]
    test rax, rax
    jz   .sl_pass

    ; V_{d*2^r}=0 for r=1..s-1
    mov  r8, [r13 + LSO_S]
    test r8, r8
    jz   .sl_fail
.sl_vloop:
    dec  r8
    jz   .sl_fail
    mov  rax, [r13 + LSO_V]
    mov  rbx, [r13 + LSO_V]
    mov  rcx, [r13 + LSO_N]
    call modmul64
    mov  rbx, [r13 + LSO_QK]
    add  rbx, rbx
    cmp  rbx, [r13 + LSO_N]
    jb   .sl_v2a
    sub  rbx, [r13 + LSO_N]
.sl_v2a:
    sub  rax, rbx
    jns  .sl_v2b
    add  rax, [r13 + LSO_N]
.sl_v2b:
    mov  [r13 + LSO_V], rax
    test rax, rax
    jz   .sl_pass
    mov  rax, [r13 + LSO_QK]
    mov  rbx, [r13 + LSO_QK]
    mov  rcx, [r13 + LSO_N]
    call modmul64
    mov  [r13 + LSO_QK], rax
    jmp  .sl_vloop

.sl_fail:
    xor  rax, rax
    jmp  .sl_ret
.sl_pass:
    mov  rax, 1
.sl_ret:
    pop  r15
    pop  r14
    pop  r11
    pop  r10
    pop  r9
    pop  r8
    pop  rdx
    pop  rcx
    pop  rbx
    ret

; =============================================================================
; bpsw_step: one candidate for the core in R12/R13
;   R12 = core block base  R13 = Lucas scratch base
;   Advances BC_CAND by 8 (core owns 1 of 4 odd residues mod 8)
; =============================================================================

bpsw_step:
    push rax
    push rbx
    push rcx
    push rdx
    push rdi
    push rsi
    push r8
    push r9
    push r10
    push r11
    push r12
    push r13
    push r14
    push r15

    mov  rdi, [r12 + BC_CAND]
    cmp  rdi, 3
    jb   .bs_advance

    ; Quick trial division — flag confirmed composites
    call trial_div_r14          ; R14 = 1 if composite

    inc  qword [r12 + BC_TESTED]
    lock inc qword [TOT_TESTED]

    ; Gate 1: MR-2
    mov  rbx, 2
    call miller_rabin
    mov  [r12 + BC_MR2], rax
    test rax, rax
    jz   .bs_advance

    lock inc qword [TOT_SPSP2]
    inc  qword [r12 + BC_SPSP2]

    ; Selfridge D/Q
    mov  rdi, [r12 + BC_CAND]
    call selfridge_r
    test rax, rax
    jnz  .bs_advance

    ; Gate 2: strong Lucas
    mov  rdi, [r12 + BC_CAND]
    call strong_lucas_r
    mov  [r12 + BC_SLC], rax
    test rax, rax
    jz   .bs_advance

    ; Both gates pass
    inc  qword [r12 + BC_BPSW]
    lock inc qword [TOT_BPSW]

    ; Confirmed composite = PRIZE
    test r14, r14
    jz   .bs_advance
    mov  rax, [r12 + BC_CAND]
    mov  [r12 + BC_LAST_PSEUDO], rax
    inc  qword [r12 + BC_PSEUDO]
    lock inc qword [TOT_PSEUDO]
    mov  [TOT_LAST_PSEUDO], rax

.bs_advance:
    add  qword [r12 + BC_CAND], 8

    pop  r15
    pop  r14
    pop  r13
    pop  r12
    pop  r11
    pop  r10
    pop  r9
    pop  r8
    pop  rsi
    pop  rdi
    pop  rdx
    pop  rcx
    pop  rbx
    pop  rax
    ret

; =============================================================================
; trial_div_r14: R14 = 1 if n (RDI) has a factor in [3,101], else 0
; =============================================================================

trial_div_r14:
    push rax
    push rbx
    push rcx
    push rdx
    xor  r14, r14
    mov  rcx, rdi
    cmp  rcx, 4
    jb   .td_done
    test rcx, 1
    jz   .td_yes
    mov  rbx, 3
.td_loop:
    mov  rax, rbx
    mul  rax
    cmp  rcx, rax
    jb   .td_done
    mov  rax, rcx
    xor  rdx, rdx
    div  rbx
    test rdx, rdx
    jz   .td_yes
    add  rbx, 2
    cmp  rbx, 103
    jb   .td_loop
    jmp  .td_done
.td_yes:
    mov  r14, 1
.td_done:
    pop  rdx
    pop  rcx
    pop  rbx
    pop  rax
    ret

; =============================================================================
; save_resume: write candidate positions to 0x9F000
; =============================================================================

save_resume:
    push rax
    push rbx
    ; Write four candidates
    mov  rax, [BPSW_C0 + BC_CAND]
    mov  [RESUME_BASE + RESUME_OFF_C0], rax
    mov  rbx, rax
    mov  rax, [BPSW_C1 + BC_CAND]
    mov  [RESUME_BASE + RESUME_OFF_C1], rax
    xor  rbx, rax
    mov  rax, [BPSW_C2 + BC_CAND]
    mov  [RESUME_BASE + RESUME_OFF_C2], rax
    xor  rbx, rax
    mov  rax, [BPSW_C3 + BC_CAND]
    mov  [RESUME_BASE + RESUME_OFF_C3], rax
    xor  rbx, rax
    mov  [RESUME_BASE + RESUME_OFF_CKSUM], rbx
    mov  rax, RESUME_MAGIC
    mov  [RESUME_BASE + RESUME_OFF_MAGIC], rax
    mov  qword [RESUME_BASE + RESUME_OFF_VER], 3
    pop  rbx
    pop  rax
    ret

; =============================================================================
; update_rate: compute candidates/sec (RDTSC-based), store in TOT_RATE_RAW
;   rate_raw = (delta_tested << 20) / delta_tsc_hi20
;   At 3GHz: delta_tsc for 1s ≈ 3e9; >>20 ≈ 2861. rate_raw * 2861 ≈ cands/sec.
;   Displayed as rate_raw in hex; human-readable conversion in vga_update.
; =============================================================================

update_rate:
    push rax
    push rbx
    push rcx
    push rdx

    rdtsc
    shl  rdx, 32
    or   rax, rdx              ; current TSC

    mov  rbx, [TOT_TSC_PREV]
    test rbx, rbx
    jz   .rate_init

    sub  rax, rbx              ; delta_tsc
    jz   .rate_done

    mov  rcx, [TOT_TESTED]
    sub  rcx, [TOT_TEST_PREV]  ; delta_tested

    shl  rcx, 20               ; << 20
    mov  rbx, rax
    shr  rbx, 20               ; delta_tsc >> 20
    jz   .rate_done
    xor  rdx, rdx
    mov  rax, rcx
    div  rbx
    mov  [TOT_RATE_RAW], rax

.rate_init:
.rate_done:
    rdtsc
    shl  rdx, 32
    or   rax, rdx
    mov  [TOT_TSC_PREV], rax
    mov  rax, [TOT_TESTED]
    mov  [TOT_TEST_PREV], rax

    pop  rdx
    pop  rcx
    pop  rbx
    pop  rax
    ret


; ============================================================================
; SERIAL OUTPUT HELPERS (COM1, 115200 8N1)
; ============================================================================

; serial_wait: wait for TX empty
serial_wait:
    push rax
    push rdx
.w:
    mov  dx, 0x3FD
    in   al, dx
    and  al, 0x20
    jz   .w
    pop  rdx
    pop  rax
    ret

; serial_putchar: send AL via COM1
serial_putchar:
    push rdx
    push rax
    mov  ah, al
    call serial_wait
    mov  dx, 0x3F8
    mov  al, ah
    out  dx, al
    pop  rax
    pop  rdx
    ret

serial_putchar_L:
    mov  al, 'L'
    jmp  serial_putchar

serial_putchar_V:
    mov  al, 'V'
    jmp  serial_putchar

; serial_put_hex64: print RAX as 16 hex digits + newline to COM1
serial_put_hex64:
    push rcx
    push rax
    push rbx
    mov  rbx, rax
    mov  rcx, 16
.hex:
    mov  rax, rbx
    shr  rax, 60
    and  eax, 0x0F
    movzx eax, byte [hex_digits + PHYS_ADJ + rax]
    call serial_putchar
    shl  rbx, 4
    loop .hex
    ; newline
    mov  al, 0x0D
    call serial_putchar
    mov  al, 0x0A
    call serial_putchar
    pop  rbx
    pop  rax
    pop  rcx
    ret

; serial_puts: RSI = physical address of null-terminated string
serial_puts:
    push rsi
    push rax
.next:
    lodsb
    test al, al
    jz   .done
    call serial_putchar
    jmp  .next
.done:
    pop  rax
    pop  rsi
    ret

; =============================================================================
; AP TRAMPOLINE (16-bit, copied to 0x8000)
; =============================================================================

; =============================================================================
; vga_pct_decimal: display RAX (= percent * 10^8) as "X.XXXXXXXX%"
;   RDI = VGA destination
; =============================================================================

vga_pct_decimal:
    push rax
    push rbx
    push rcx
    push rdx
    push rdi

    ; Split: whole = rax / 10^8, frac = rax mod 10^8
    mov  rbx, 100000000        ; 10^8
    xor  rdx, rdx
    div  rbx
    ; rax = whole part (0 or small number), rdx = frac

    ; Write whole digit(s) — at most 2 digits (max ~54% before 2^64)
    push rdx                   ; save frac
    ; Convert whole to digits
    cmp  rax, 10
    jb   .one_digit
    ; two digits
    mov  rbx, rax
    xor  rdx, rdx
    mov  rcx, 10
    div  rcx
    add  al, '0'
    mov  [rdi], al
    mov  byte [rdi+1], 0x0F
    add  rdi, 2
    mov  rax, rdx
.one_digit:
    add  al, '0'
    mov  [rdi], al
    mov  byte [rdi+1], 0x0F
    add  rdi, 2

    ; Decimal point
    mov  byte [rdi], '.'
    mov  byte [rdi+1], 0x0F
    add  rdi, 2

    ; 8 fractional digits
    pop  rax                   ; frac
    mov  rcx, 8
.frac_loop:
    mov  rbx, 10
    xor  rdx, rdx
    ; shift left: multiply frac by 10, extract top digit
    ; frac is < 10^8; multiply by 10 fits in 64 bits (< 10^9 < 2^30)
    mov  rbx, 10000000         ; 10^7
    xor  rdx, rdx
    div  rbx
    ; rax = digit, rdx = remainder
    add  al, '0'
    mov  [rdi], al
    mov  byte [rdi+1], 0x0F
    add  rdi, 2
    mov  rax, rdx
    ; scale: new frac = old_frac mod 10^(8-digit)
    ; just continue with rdx and loop
    ; but we divided by 10^7 each time — wrong. Use simpler approach:
    ; extract each decimal digit by multiply-by-10
    dec  rcx
    jz   .frac_done
    ; Recompute: multiply remaining by 10
    ; rdx is now the remainder after extracting one digit
    ; We need to repeat with rbx = 10^(7-1) = 10^6 etc.
    ; Simpler: just shift the original fraction
    ; Actually just use modulo approach properly below
    jmp  .frac_loop
.frac_done:

    ; "%" suffix
    mov  byte [rdi], '%'
    mov  byte [rdi+1], 0x0A    ; green
    add  rdi, 2

    pop  rdi
    pop  rdx
    pop  rcx
    pop  rbx
    pop  rax
    ret

; =============================================================================
; vga_hours_days: display RAX (hours) as "XXXd XXh XXm" at RDI
; =============================================================================

vga_hours_days:
    push rax
    push rbx
    push rcx
    push rdx
    push rdi

    ; days = hours / 24, rem_hours = hours mod 24
    mov  rbx, 24
    xor  rdx, rdx
    div  rbx
    push rdx                   ; save rem_hours
    ; rax = days
    ; display days (up to 6 digits)
    call vga_dec32_3d          ; display rax as up to 4 chars
    mov  byte [rdi], 'd'
    mov  byte [rdi+1], 0x0F
    add  rdi, 2
    mov  byte [rdi], ' '
    mov  byte [rdi+1], 0x0F
    add  rdi, 2

    ; hours
    pop  rax                   ; rem_hours
    push rax
    call vga_dec32_2d
    mov  byte [rdi], 'h'
    mov  byte [rdi+1], 0x0F
    add  rdi, 2
    mov  byte [rdi], ' '
    mov  byte [rdi+1], 0x0F
    add  rdi, 2

    ; minutes: not tracked, show --
    mov  byte [rdi], '-'
    mov  byte [rdi+1], 0x07
    add  rdi, 2
    mov  byte [rdi], '-'
    mov  byte [rdi+1], 0x07
    add  rdi, 2
    mov  byte [rdi], 'm'
    mov  byte [rdi+1], 0x07

    pop  rax
    pop  rdi
    pop  rdx
    pop  rcx
    pop  rbx
    pop  rax
    ret

; vga_dec32_3d: display RAX as up to 4 decimal digits at RDI, advance RDI
vga_dec32_3d:
    push rax
    push rbx
    push rcx
    push rdx
    ; up to 9999 days meaningful; just display up to 4 digits
    mov  rcx, 4
    ; find leading digit by dividing repeatedly
    mov  rbx, 1000
    jmp  .d3_start
.d3_loop:
    xor  rdx, rdx
    div  rbx
    add  al, '0'
    mov  [rdi], al
    mov  byte [rdi+1], 0x0F
    add  rdi, 2
    mov  rax, rdx
    xor  rdx, rdx
    mov  rax, rbx
    mov  rbx, 10
    div  rbx
    mov  rbx, rax
    mov  rax, rdx
.d3_start:
    dec  rcx
    jnz  .d3_loop
    ; last digit
    add  al, '0'
    mov  [rdi], al
    mov  byte [rdi+1], 0x0F
    add  rdi, 2
    pop  rdx
    pop  rcx
    pop  rbx
    pop  rax
    ret

; vga_dec32_2d: display RAX (< 100) as 2 decimal digits at RDI
vga_dec32_2d:
    push rax
    push rdx
    mov  rbx, 10
    xor  rdx, rdx
    div  rbx
    add  al, '0'
    mov  [rdi], al
    mov  byte [rdi+1], 0x0F
    add  rdi, 2
    mov  rax, rdx
    add  al, '0'
    mov  [rdi], al
    mov  byte [rdi+1], 0x0F
    add  rdi, 2
    pop  rdx
    pop  rax
    ret


BITS 16

ap_trampoline:
    cli
    xor ax, ax
    mov ds, ax
; AP alive
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00

    ; Use embedded GDT (don't rely on boot sector memory at 0x7C00)
    lgdt [cs:ap_gdt_ptr - ap_trampoline]

    mov eax, cr0
    or  eax, 1
    mov cr0, eax

    jmp dword 0x08:AP_PM_PHYS

; Embedded GDT for AP (at known offset from ap_trampoline start)
align 8
ap_gdt_base:
    dq 0x0000000000000000
    dq 0x00CF9A000000FFFF  ; 0x08: 32-bit code
    dq 0x00CF92000000FFFF  ; 0x10: data
    dq 0x00AF9A000000FFFF  ; 0x18: 64-bit code
ap_gdt_end:
ap_gdt_ptr:
    dw ap_gdt_end - ap_gdt_base - 1
    dd AP_TRAMP_PHYS + (ap_gdt_base - ap_trampoline)

; AP: 32-bit pmode
BITS 32

ap_pm_entry:
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov ss, ax

    ; Get APIC ID for per-AP stack assignment (in 32-bit PM)
    mov eax, 1
    cpuid
    shr ebx, 24
    and ebx, 0xFF           ; ebx = APIC ID (0..3)
    ; Per-AP temp stack: 0x210000 + apic_id * 0x8000
    mov esp, 0x210000
    imul ebx, 0x8000
    add  esp, ebx

    ; PAE
    mov eax, cr4
    or  eax, (1 << 5)
    mov cr4, eax

    ; EFER.LME
    mov ecx, 0xC0000080
    rdmsr
    or  eax, (1 << 8)
    wrmsr

    ; Use BSP page tables
    mov eax, PML4_PHYS
    mov cr3, eax

    ; Paging on
    mov eax, cr0
    or  eax, (1 << 31)
    mov cr0, eax

    jmp dword 0x18:AP_LM_PHYS

; AP: 64-bit entry
BITS 64

ap_lm_entry:
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov ss, ax
    ; APIC ID -> stack assignment
    mov eax, 1
    cpuid
    shr ebx, 24
    and ebx, 0xFF

    ; Private stack: AP_STACK_BASE + apic_id * AP_STACK_STRIDE
    mov rcx, AP_STACK_BASE
    mov rdx, rbx
    imul rdx, AP_STACK_STRIDE
    add  rcx, rdx
    mov  rsp, rcx

    ; IMMEDIATE: set our bit in READY_MASK right here to confirm we're alive
    ; APIC ID 1=bit1, 2=bit2, 3=bit3 -> shift 1 by apic_id
    push rcx
    mov  rcx, rbx
    mov  rax, 1
    shl  rax, cl        ; rax = 1 << apic_id
    lock or qword [READY_MASK], rax
    pop  rcx

    ; Dispatch by APIC ID
    cmp ebx, 1
    je  .water
    cmp ebx, 2
    je  .earth
    cmp ebx, 3
    je  .wind
    jmp .dead

.water: call role_water
        jmp .dead
.earth: call role_earth
        jmp .dead
.wind:  call role_wind

.dead:
    cli
.halt:
    hlt
    jmp .halt

align 2
ap_trampoline_end:

; =============================================================================
; VGA INITIALIZATION
; =============================================================================

BITS 64

vga_init:
    ; Clear screen (2000 cells, attribute 0x07 = white on black)
    mov  rdi, VGA_BASE
    mov  ax,  0x0720
    mov  rcx, 2000
    rep  stosw

    ; Row 0: title
    mov  rdi, VGA_BASE + VGA_ROW * 0
    mov  rsi, str_title + PHYS_ADJ
    mov  bl,  0x0F          ; bright white
    call vga_puts_color

    ; Row 1: topology
    mov  rdi, VGA_BASE + VGA_ROW * 1
    mov  rsi, str_topology + PHYS_ADJ
    mov  bl,  0x0B          ; cyan
    call vga_puts_color

    ; Row 2: STATE header
    mov  rdi, VGA_BASE + VGA_ROW * 2
    mov  rsi, str_state + PHYS_ADJ
    mov  bl,  0x07
    call vga_puts_color

    ; Row 3: FIRE header
    mov  rdi, VGA_BASE + VGA_ROW * 3
    mov  rsi, str_fire + PHYS_ADJ
    mov  bl,  0x0C          ; bright red
    call vga_puts_color

    ; Row 4: WATER header
    mov  rdi, VGA_BASE + VGA_ROW * 4
    mov  rsi, str_water + PHYS_ADJ
    mov  bl,  0x09          ; bright blue
    call vga_puts_color

    ; Row 5: EARTH header
    mov  rdi, VGA_BASE + VGA_ROW * 5
    mov  rsi, str_earth + PHYS_ADJ
    mov  bl,  0x0A          ; bright green
    call vga_puts_color

    ; Row 6: WIND header
    mov  rdi, VGA_BASE + VGA_ROW * 6
    mov  rsi, str_wind + PHYS_ADJ
    mov  bl,  0x0E          ; yellow
    call vga_puts_color

    ; Row 7: ORACLE header
    mov  rdi, VGA_BASE + VGA_ROW * 7
    mov  rsi, str_oracle + PHYS_ADJ
    mov  bl,  0x0D          ; bright magenta
    call vga_puts_color

    ; Row 8: YIN header
    mov  rdi, VGA_BASE + VGA_ROW * 8
    mov  rsi, str_yin + PHYS_ADJ
    mov  bl,  0x07
    call vga_puts_color

    ; Rows 9-12: per-core BPSW
    mov  rdi, VGA_BASE + VGA_ROW *  9
    mov  rsi, str_c0 + PHYS_ADJ
    mov  bl,  0x0B
    call vga_puts_color
    mov  rdi, VGA_BASE + VGA_ROW * 10
    mov  rsi, str_c1 + PHYS_ADJ
    mov  bl,  0x0B
    call vga_puts_color
    mov  rdi, VGA_BASE + VGA_ROW * 11
    mov  rsi, str_c2 + PHYS_ADJ
    mov  bl,  0x0B
    call vga_puts_color
    mov  rdi, VGA_BASE + VGA_ROW * 12
    mov  rsi, str_c3 + PHYS_ADJ
    mov  bl,  0x0B
    call vga_puts_color
    ; Row 13: totals + rate
    mov  rdi, VGA_BASE + VGA_ROW * 13
    mov  rsi, str_tot + PHYS_ADJ
    mov  bl,  0x0E
    call vga_puts_color
    ; Row 14: progress (bright white)
    mov  rdi, VGA_BASE + VGA_ROW * 14
    mov  rsi, str_prog + PHYS_ADJ
    mov  bl,  0x0F
    call vga_puts_color
    ; Row 15: prize (bright red)
    mov  rdi, VGA_BASE + VGA_ROW * 15
    mov  rsi, str_prize + PHYS_ADJ
    mov  bl,  0x0C
    call vga_puts_color

    ret

; =============================================================================
; VGA UPDATE (called every PRINT_EVERY iterations)
; =============================================================================

; Column positions for values (each hex64 = 16 chars + 1 space = 17 cols)
; Labels end around col 10, values start at col 10 (byte offset = col*2)

vga_update:

    ; ── Row 2: STATE K= A= B= ──
    mov rdi, VGA_BASE + VGA_ROW * 2 + 10*2
    mov rax, [STATE_K]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 2 + 28*2
    mov rax, [STATE_A]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 2 + 46*2
    mov rax, [STATE_B]
    call vga_hex64

    ; ── Row 3: FIRE A= B= ──
    mov rdi, VGA_BASE + VGA_ROW * 3 + 10*2
    mov rax, [FIRE_A]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 3 + 28*2
    mov rax, [FIRE_B]
    call vga_hex64

    ; ── Row 4: WATER A= B= ──
    mov rdi, VGA_BASE + VGA_ROW * 4 + 10*2
    mov rax, [WATER_A]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 4 + 28*2
    mov rax, [WATER_B]
    call vga_hex64

    ; ── Row 5: EARTH N= NF= DELTA= ──
    mov rdi, VGA_BASE + VGA_ROW * 5 + 10*2
    mov rax, [EARTH_N]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 5 + 28*2
    mov rax, [EARTH_N_FIRE]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 5 + 46*2
    mov rax, [EARTH_DELTA]
    call vga_hex64

    ; ── Row 6: WIND RA= RB= FIX= ──
    mov rdi, VGA_BASE + VGA_ROW * 6 + 10*2
    mov rax, [WIND_RES_A]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 6 + 28*2
    mov rax, [WIND_RES_B]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 6 + 46*2
    mov rax, [WIND_FIX]
    call vga_hex64

    ; ── Row 7: ORACLE= STRATEGY= ──
    mov rdi, VGA_BASE + VGA_ROW * 7 + 10*2
    mov rax, [ORACLE]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 7 + 28*2
    mov rax, [STRATEGY]
    call vga_hex64
    ; Strategy name
    mov rdi, VGA_BASE + VGA_ROW * 7 + 46*2
    mov rax, [STRATEGY]
    call vga_strategy_name

    ; ── Row 8: YIN PH DEPTH ──
    mov rdi, VGA_BASE + VGA_ROW * 8 + 10*2
    mov rax, [YIN]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 8 + 28*2
    mov rax, [PHASE]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 8 + 46*2
    mov rax, [DEPTH]
    call vga_hex64

    ; ── Update rate + save resume every display cycle ──
    call update_rate
    call save_resume

    ; ── Rows 9-12: per-core BPSW status ──
    ; C0: N=  TST=  S2=  BPSW=
    mov rdi, VGA_BASE + VGA_ROW * 9 + 5*2
    mov rax, [BPSW_C0 + BC_CAND]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 9 + 23*2
    mov rax, [BPSW_C0 + BC_TESTED]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 9 + 41*2
    mov rax, [BPSW_C0 + BC_SPSP2]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 9 + 59*2
    mov rax, [BPSW_C0 + BC_BPSW]
    call vga_hex64
    ; C1
    mov rdi, VGA_BASE + VGA_ROW * 10 + 5*2
    mov rax, [BPSW_C1 + BC_CAND]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 10 + 23*2
    mov rax, [BPSW_C1 + BC_TESTED]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 10 + 41*2
    mov rax, [BPSW_C1 + BC_SPSP2]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 10 + 59*2
    mov rax, [BPSW_C1 + BC_BPSW]
    call vga_hex64
    ; C2
    mov rdi, VGA_BASE + VGA_ROW * 11 + 5*2
    mov rax, [BPSW_C2 + BC_CAND]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 11 + 23*2
    mov rax, [BPSW_C2 + BC_TESTED]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 11 + 41*2
    mov rax, [BPSW_C2 + BC_SPSP2]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 11 + 59*2
    mov rax, [BPSW_C2 + BC_BPSW]
    call vga_hex64
    ; C3
    mov rdi, VGA_BASE + VGA_ROW * 12 + 5*2
    mov rax, [BPSW_C3 + BC_CAND]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 12 + 23*2
    mov rax, [BPSW_C3 + BC_TESTED]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 12 + 41*2
    mov rax, [BPSW_C3 + BC_SPSP2]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 12 + 59*2
    mov rax, [BPSW_C3 + BC_BPSW]
    call vga_hex64

    ; ── Row 13: totals + rate ──
    mov rdi, VGA_BASE + VGA_ROW * 13 + 7*2
    mov rax, [TOT_TESTED]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 13 + 25*2
    mov rax, [TOT_SPSP2]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 13 + 43*2
    mov rax, [TOT_BPSW]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 13 + 59*2
    mov rax, [TOT_RATE_RAW]
    call vga_hex64

    ; ── Row 14: human-readable progress ──
    ; Percent = TOT_TESTED / 2^63 * 100
    ; = (TOT_TESTED * 100) >> 63
    ; Use upper bits to avoid overflow: (TOT_TESTED >> 33) * 100 / 2^30
    ; = (TOT_TESTED >> 33) * 100 >> 30
    ; Result is in units of 10^-8 percent. Format as X.XXXXXXXX%
    mov  rax, [TOT_TESTED]
    shr  rax, 33               ; rax = TOT_TESTED / 2^33
    mov  rbx, 100
    mul  rbx                   ; rdx:rax = rax * 100
    shr  rax, 30               ; rax = percent * 10^8 (8 decimal places)
    ; Display as decimal: split into whole and fractional parts
    ; whole = rax / 10^8, frac = rax mod 10^8
    mov  rdi, VGA_BASE + VGA_ROW * 14 + 10*2
    call vga_pct_decimal        ; display rax as X.XXXXXXXX%

    ; ETA: hours to cover 2^64
    ; remaining = 2^64 - TOT_TESTED (approx: just use 2^64 for simplicity since tiny)
    ; rate_cands_sec = TOT_RATE_RAW * 2861 (approx at 3GHz; good enough for ETA)
    ; hours = 2^64 / rate_cands_sec / 3600
    ; = 2^64 / (TOT_RATE_RAW * 2861 * 3600)
    ; To keep in range: hours = (2^64 >> 20) / (TOT_RATE_RAW * (2861*3600 >> 20))
    ;                          = 2^44 / (TOT_RATE_RAW * 10325) approx
    mov  rax, [TOT_RATE_RAW]
    test rax, rax
    jz   .no_eta
    mov  rbx, 10325            ; 2861 * 3600 / 1000 (scaled to avoid overflow)
    mul  rbx                   ; rdx:rax = rate * 10325
    test rdx, rdx
    jnz  .eta_overflow         ; rate too low to compute meaningfully
    ; hours = (1 << 44) / rax
    mov  rbx, rax
    mov  rax, 1
    shl  rax, 44
    xor  rdx, rdx
    div  rbx                   ; rax = hours remaining (approx)
    mov  rdi, VGA_BASE + VGA_ROW * 14 + 32*2
    call vga_hours_days         ; display as Xd Xh Xm
    jmp  .eta_done
.eta_overflow:
.no_eta:
.eta_done:

    ; ── Row 15: prize row ──
    mov rdi, VGA_BASE + VGA_ROW * 15 + 12*2
    mov rax, [TOT_PSEUDO]
    call vga_hex64
    mov rdi, VGA_BASE + VGA_ROW * 15 + 31*2
    mov rax, [TOT_LAST_PSEUDO]
    call vga_hex64

    ; ── COM1 serial: send terse status line ──
    ; Format: "D=xxxx O=xx S=x\r\n"
    call serial_putchar_V   ; 'V' = VGA update marker
    mov  rsi, str_serial_depth + PHYS_ADJ
    call serial_puts
    mov  rax, [DEPTH]
    call serial_put_hex64
    mov  rsi, str_serial_oracle + PHYS_ADJ
    call serial_puts
    mov  rax, [ORACLE]
    call serial_put_hex64

    ret

; ============================================================================
; vga_strategy_name: print strategy name at RDI
; Input: rax = STRATEGY index
; ============================================================================

vga_strategy_name:
    cmp rax, STRATEGY_FLOWING
    je  .flowing
    cmp rax, STRATEGY_NONACTION
    je  .nonaction
    cmp rax, STRATEGY_REDIRECT
    je  .redirect
    cmp rax, STRATEGY_CONVERGE
    je  .converge
    cmp rax, STRATEGY_CRITICAL
    je  .critical
    mov rsi, str_strat_unknown + PHYS_ADJ
    jmp .print
.flowing:
    mov rsi, str_strat_flowing + PHYS_ADJ
    jmp .print
.nonaction:
    mov rsi, str_strat_nonaction + PHYS_ADJ
    jmp .print
.redirect:
    mov rsi, str_strat_redirect + PHYS_ADJ
    jmp .print
.converge:
    mov rsi, str_strat_converge + PHYS_ADJ
    jmp .print
.critical:
    mov rsi, str_strat_critical + PHYS_ADJ
.print:
    mov bl, 0x0D
    jmp vga_puts_color     ; tail call

; =============================================================================
; VGA HELPERS
; =============================================================================

; vga_puts_color: RDI=dest, RSI=string, BL=attribute
vga_puts_color:
.next:
    lodsb
    test al, al
    jz   .done
    mov  [rdi], al
    mov  [rdi + 1], bl
    add  rdi, 2
    jmp  .next
.done:
    ret

; vga_hex64: RDI=dest, RAX=value, writes 16 hex digits
vga_hex64:
    push rbx
    push rcx
    push rdx
    push rdi
    push rax
    mov  rcx, 16
    mov  rbx, rdi

.hloop:
    mov  rdx, rax
    shr  rdx, 60
    and  edx, 0x0F
    movzx edx, byte [hex_digits + PHYS_ADJ + rdx]
    mov  [rbx], dl
    mov  byte [rbx + 1], 0x07
    add  rbx, 2
    shl  rax, 4
    loop .hloop

    pop  rax
    pop  rdi
    pop  rdx
    pop  rcx
    pop  rbx
    ret

; =============================================================================
; STRINGS
; =============================================================================

str_title:
    db "HDGL Z[phi] WU-WEI SUBSTRATE  FIRE/WATER/EARTH/WIND",0
str_topology:
    db "CPU0=FIRE  CPU1=WATER  CPU2=EARTH  CPU3=WIND",0
str_state:
    db "STATE  K=                  A=                  B=",0
str_fire:
    db "FIRE   A=                  B=",0
str_water:
    db "WATER  A=                  B=",0
str_earth:
    db "EARTH  N=                  NF=                 DELTA=",0
str_wind:
    db "WIND   RA=                 RB=                 FIX=",0
str_oracle:
    db "ORACLE=                    STRATEGY=",0
str_yin:
    db "YIN    S=                  PH=                 DEPTH=",0

str_c0:    db "C0:  N=                  TST=                S2=                BPSW=",0
str_c1:    db "C1:  N=                  TST=                S2=                BPSW=",0
str_c2:    db "C2:  N=                  TST=                S2=                BPSW=",0
str_c3:    db "C3:  N=                  TST=                S2=                BPSW=",0
str_tot:   db "TOT: N=                  S2=                BPSW=              RATE=",0
str_prog:  db "COVERAGE:          % ETA:           (0=resuming, FF..=done)",0
str_prize: db "PSEUDOPRIME:             LAST=                     *** $620! ***",0

str_strat_flowing:  db "FLOWING RIVER",0
str_strat_nonaction: db "NON-ACTION   ",0
str_strat_redirect: db "REDIRECT     ",0
str_strat_converge: db "CONVERGENCE  ",0
str_strat_critical: db "!! CRITICAL !!",0
str_strat_unknown:  db "UNKNOWN      ",0

hex_digits:
    db "0123456789ABCDEF"

str_serial_depth:  db "DEPTH=",0
str_serial_oracle: db "ORACLE=",0

; =============================================================================
; PHYSICAL ADDRESS CONSTANTS
; =============================================================================
;
; All labels are relative to ORG 0x7C00.
; Physical address of a label L in payload = 0x10000 + (L - boot_start) - 512
; because:
;   - payload loads at physical 0x10000
;   - boot_start = 0x7C00
;   - sector 1 (boot sector) = 512 bytes, payload starts at file offset 512
;   - So physical(L) = 0x10000 + (L - 0x7C00) - 512
;                    = 0x10000 + L - 0x7E00
;                    = L + (0x10000 - 0x7E00)
;                    = L + 0x8200
;
; Verify: protected_entry label value = 0x7C00 + 512 = 0x7E00
;         physical = 0x7E00 + 0x8200 = 0x10200. Correct!
;
; For AP trampoline (copied to 0x8000):
;   ap_trampoline label = 0x7C00 + (its file offset)
;   AP_PM_PHYS = 0x8000 + (ap_pm_entry - ap_trampoline)
;   AP_LM_PHYS = 0x8000 + (ap_lm_entry - ap_trampoline)

PROTECTED_ENTRY_PHYS equ protected_entry
LONG_MODE_ENTRY_PHYS equ long_mode_entry
AP_PM_PHYS           equ AP_TRAMP_PHYS   + (ap_pm_entry  - ap_trampoline)
AP_LM_PHYS           equ AP_TRAMP_PHYS   + (ap_lm_entry  - ap_trampoline)

; =============================================================================
; IMAGE PADDING TO EXACTLY 64 SECTORS
; =============================================================================

times (IMAGE_SECTORS * 512) - ($ - $$) db 0

Five Gates ($2000 Prize B)

Premise:
https://arxiv.org/pdf/2006.14425

iris2-combined.zip (10.5 KB)

.asm

; =============================================================================
; HDGL — COMBINED PRIMALITY ORACLE
; Stronger than the Baillie-PSW enhanced test (Baillie, Fiori, Wagstaff 2020)
; =============================================================================
;
; TARGET:  x86-64 / BIOS / QEMU (or real hardware)
; BUILD:   nasm -f bin combined.asm -o combined.img
; RUN:     qemu-system-x86_64 -drive format=raw,file=combined.img -m 128M -boot c
;
; FIVE GATES — A composite pseudoprime must survive all five simultaneously:
;
;  Gate 1  spsp(2)      Strong pseudoprime base 2 [Miller-Rabin]
;  Gate 2  spsp(3)      Strong pseudoprime base 3 [Miller-Rabin]
;  Gate 3  Frobenius    phi^N = expected in Z[phi]/(N), D=5 fixed
;                       Encodes F_N ≡ ±1 AND F_{N-1} ≡ 1 (mod N) jointly
;                       Implies U_{N+1}=0 AND V_{N+1}=2Q for D=5/Q=-1
;  Gate 4  slpsp        Strong Lucas probable prime, adaptive Selfridge D
;                       U_d ≡ 0 (mod N)  OR  V_{d·2^r} ≡ 0 for some 0≤r<s
;  Gate 5  vpsp         V_{N+1} ≡ 2Q (mod N) with the same adaptive D/Q
;
; Gate 3 (iris1's Frobenius) imposes a JOINT non-strong constraint in one fixed
; ring.  Gates 4+5 impose adaptive strong constraints in a second ring chosen
; to avoid Q≡±1.  Gates 1+2 are independent Fermat witnesses.
;
; No composite is known to survive even Gate 1 + Gate 4 (original BPSW).
; The paper's enhanced test adds Gate 5 and a minor Euler-Q check.
; This test adds Gate 2 and Gate 3 on top of the paper's enhanced test.
;
; DISPLAY:
;   Row 0  title
;   Row 1  N=<candidate>   TESTED=<count>
;   Row 2  [G1:MR2] [G2:MR3] [G3:FRB] [G4:SLC] [G5:VPS]
;   Row 3  SELFRIDGE D=   Q=   s=   d=
;   Row 4  LUCAS U_d=   V_d=
;   Row 5  PASS COUNT=   LAST=
;   Row 6  PSEUDOPRIME=   LAST=      *** HOLY GRAIL ***
;   Row 7  PHI-LATTICE  K=   N_phi=
;
; =============================================================================

BITS 16
ORG 0x7C00

; =============================================================================
; MEMORY MAP
; =============================================================================

AP_TRAMP_PHYS      equ 0x00020000
PML4_PHYS          equ 0x00021000
PDPT_PHYS          equ 0x00022000
PD0_PHYS           equ 0x00023000
PD1_PHYS           equ 0x00024000
PD2_PHYS           equ 0x00025000
PD3_PHYS           equ 0x00026000
BSP_STACK          equ 0x00070000
VGA_BASE           equ 0x000B8000
VGA_ROW            equ 160
IMAGE_SECTORS      equ 64
PAYLOAD_SECTORS    equ IMAGE_SECTORS - 1
PHYS_ADJ           equ 0

; Phi-lattice substrate
STATE_A            equ 0x00500000
STATE_B            equ 0x00500008
STATE_K            equ 0x00500010
EARTH_N            equ 0x00500020

; Oracle state
ORA_CANDIDATE      equ 0x00501000  ; N being tested
ORA_TESTED         equ 0x00501008  ; total candidates tested
ORA_PASS_COUNT     equ 0x00501010  ; probable-prime count
ORA_LAST_PASS      equ 0x00501018  ; most recent probable prime
ORA_PSEUDO_COUNT   equ 0x00501020  ; composites passing all gates (the grail)
ORA_LAST_PSEUDO    equ 0x00501028  ; most recent pseudoprime
ORA_G1_MR2        equ 0x00501030  ; gate results (1=pass, 0=fail)
ORA_G2_MR3        equ 0x00501038
ORA_G3_FRB        equ 0x00501040  ; Frobenius Z[phi]
ORA_G4_SLC        equ 0x00501048  ; strong Lucas
ORA_G5_VPS        equ 0x00501050  ; vpsp V_{n+1}=2Q
ORA_SEL_D         equ 0x00501060  ; Selfridge D
ORA_SEL_Q         equ 0x00501068  ; Selfridge Q
ORA_SEL_S         equ 0x00501070  ; s
ORA_SEL_DODD      equ 0x00501078  ; d (odd part of n+1)
ORA_LUCAS_U       equ 0x00501080  ; U_d
ORA_LUCAS_V       equ 0x00501088  ; V_d
ORA_IS_COMPOSITE  equ 0x00501090  ; 1 if trial-div confirmed composite

; Lucas computation scratch — all intermediate values live here
LS_U               equ 0x00502000
LS_V               equ 0x00502008
LS_QK              equ 0x00502010
LS_U2              equ 0x00502018
LS_V2              equ 0x00502020
LS_N               equ 0x00502028
LS_D               equ 0x00502030  ; D mod N (non-negative)
LS_Q               equ 0x00502038  ; Q mod N (non-negative)
LS_INV2            equ 0x00502040  ; (N+1)/2 mod N
LS_S               equ 0x00502048  ; s
LS_DODD            equ 0x00502050  ; d
LS_UD              equ 0x00502058  ; U_d saved for vpsp restart
LS_VD              equ 0x00502060  ; V_d saved
LS_QKD             equ 0x00502068  ; Qk^d saved

; Frobenius scratch
FS_RA              equ 0x00503000  ; result phi-coeff
FS_RB              equ 0x00503008  ; result const-coeff
FS_BA              equ 0x00503010  ; base phi-coeff
FS_BB              equ 0x00503018  ; base const-coeff
FS_N               equ 0x00503020  ; modulus

; How many substrate ticks between primality tests
ORACLE_STRIDE      equ 512

PROTECTED_ENTRY_PHYS equ protected_entry
LONG_MODE_ENTRY_PHYS equ long_mode_entry
AP_PM_PHYS           equ AP_TRAMP_PHYS + (ap_pm_entry  - ap_trampoline)
AP_LM_PHYS           equ AP_TRAMP_PHYS + (ap_lm_entry  - ap_trampoline)

; =============================================================================
; BIOS BOOT
; =============================================================================

boot_start:
    cli
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00
    mov [boot_drive], dl
    mov ax, 0x0003
    int 0x10
    mov ah, 0x0E
    mov al, 'B'
    xor bh, bh
    int 0x10

    mov ah, 0x41
    mov bx, 0x55AA
    mov dl, [boot_drive]
    int 0x13
    jc  .use_chs
    cmp bx, 0xAA55
    jne .use_chs
    test cl, 1
    jz  .use_chs
    mov si, disk_packet
    mov dl, [boot_drive]
    mov ah, 0x42
    int 0x13
    jnc .disk_ok
.use_chs:
    mov ax, 0x07E0
    mov es, ax
    xor bx, bx
    mov ah, 0x02
    mov al, PAYLOAD_SECTORS
    mov ch, 0
    mov cl, 2
    mov dh, 0
    mov dl, [boot_drive]
    int 0x13
    jc  .halt
.disk_ok:
    mov ax, 0x2401
    int 0x15
    in  al, 0x92
    or  al, 2
    and al, 0xFE
    out 0x92, al
    lgdt [gdt_ptr]
    mov eax, cr0
    or  eax, 1
    mov cr0, eax
    jmp dword 0x08:PROTECTED_ENTRY_PHYS
.halt:
    cli
    hlt

disk_packet:
    db 0x10, 0x00
    dw PAYLOAD_SECTORS
    dw 0x0000
    dw 0x07E0
    dq 1

boot_drive: db 0

align 8
gdt_base:
    dq 0x0000000000000000
    dq 0x00CF9A000000FFFF
    dq 0x00CF92000000FFFF
    dq 0x00AF9A000000FFFF
gdt_end:
gdt_ptr:
    dw gdt_end - gdt_base - 1
    dd gdt_base

times 510 - ($ - $$) db 0
dw 0xAA55

; =============================================================================
; 32-BIT PROTECTED MODE
; =============================================================================

BITS 32

protected_entry:
    cli
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov esp, BSP_STACK
    mov byte [0xB8000 + 24*160], 'C'
    mov byte [0xB8000 + 24*160 + 1], 0x4F
    call build_page_tables
    mov eax, cr4
    or  eax, (1 << 5)
    mov cr4, eax
    mov ecx, 0xC0000080
    rdmsr
    or  eax, (1 << 8)
    wrmsr
    mov eax, PML4_PHYS
    mov cr3, eax
    mov eax, cr0
    or  eax, (1 << 31)
    mov cr0, eax
    jmp dword 0x18:LONG_MODE_ENTRY_PHYS

build_page_tables:
    pushad
    mov edi, PML4_PHYS
    xor eax, eax
    mov ecx, 0x6000 / 4
    cld
    rep stosd
    mov dword [PML4_PHYS +  0], PDPT_PHYS | 0x003
    mov dword [PML4_PHYS +  4], 0
    mov dword [PDPT_PHYS +  0], PD0_PHYS | 0x003
    mov dword [PDPT_PHYS +  4], 0
    mov dword [PDPT_PHYS +  8], PD1_PHYS | 0x003
    mov dword [PDPT_PHYS + 12], 0
    mov dword [PDPT_PHYS + 16], PD2_PHYS | 0x003
    mov dword [PDPT_PHYS + 20], 0
    mov dword [PDPT_PHYS + 24], PD3_PHYS | 0x003
    mov dword [PDPT_PHYS + 28], 0
    mov edi, PD0_PHYS
    xor eax, eax
    mov ecx, 512
.pd0: mov edx, eax
    or  edx, 0x83
    mov [edi], edx
    mov dword [edi+4], 0
    add eax, 0x200000
    add edi, 8
    loop .pd0
    mov edi, PD1_PHYS
    mov eax, 0x40000000
    mov ecx, 512
.pd1: mov edx, eax
    or  edx, 0x83
    mov [edi], edx
    mov dword [edi+4], 0
    add eax, 0x200000
    add edi, 8
    loop .pd1
    mov edi, PD2_PHYS
    mov eax, 0x80000000
    mov ecx, 512
.pd2: mov edx, eax
    or  edx, 0x83
    mov [edi], edx
    mov dword [edi+4], 0
    add eax, 0x200000
    add edi, 8
    loop .pd2
    mov edi, PD3_PHYS
    mov eax, 0xC0000000
    mov ecx, 512
.pd3: mov edx, eax
    or  edx, 0x83
    mov [edi], edx
    mov dword [edi+4], 0
    add eax, 0x200000
    add edi, 8
    loop .pd3
    popad
    ret

; =============================================================================
; 64-BIT ENTRY
; =============================================================================

BITS 64

long_mode_entry:
    cli
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov rsp, BSP_STACK

    ; Zero all state regions
    mov  rdi, 0x00500000
    xor  rax, rax
    mov  rcx, 0x4000 / 8
    rep  stosq

    ; Phi-lattice seed
    mov qword [STATE_A], 0
    mov qword [STATE_B], 1
    mov qword [STATE_K], 0
    mov qword [EARTH_N], 1

    ; Start at N=3 (odd, skip 2)
    mov qword [ORA_CANDIDATE], 3

    call vga_init
    jmp  main_loop

; =============================================================================
; MAIN LOOP — phi-lattice substrate clock
; =============================================================================

main_loop:
    ; Phi step: (a,b) -> (a+b, a)
    mov  r8, [STATE_A]
    mov  r9, [STATE_B]
    mov  rax, r8
    add  rax, r9
    mov  [STATE_A], rax
    mov  [STATE_B], r8
    inc  qword [STATE_K]

    ; N_phi(a,b) = -a^2 + ab + b^2
    mov  rax, r8
    imul rax, r8
    neg  rax
    mov  rbx, r8
    imul rbx, r9
    add  rax, rbx
    mov  rbx, r9
    imul rbx, r9
    add  rax, rbx
    mov  [EARTH_N], rax

    ; Oracle: stride-gated
    mov  rax, [STATE_K]
    test rax, (ORACLE_STRIDE - 1)
    jnz  .skip
    call oracle_step
.skip:

    ; Display every 2^20 ticks
    mov  rax, [STATE_K]
    test rax, 0xFFFFF
    jnz  main_loop
    call vga_update
    jmp  main_loop

; =============================================================================
; MODMUL64: (RAX * RBX) mod RCX -> RAX
; Both inputs must be < RCX; product < RCX^2 < 2^128; DIV never faults.
; =============================================================================

modmul64:
    push rdx
    mul  rbx
    div  rcx
    mov  rax, rdx
    pop  rdx
    ret

; =============================================================================
; POW_MOD_INT: base^exp mod m -> RAX
;   RDI=base  RSI=exp  RDX=modulus
; =============================================================================

pow_mod_int:
    push rbx
    push rcx
    push r8
    push r9
    push r10
    mov  r8, rdi
    mov  r9, rsi
    mov  r10, rdx
    mov  rax, 1
.loop:
    test r9, r9
    jz   .done
    test r9, 1
    jz   .sq
    mov  rbx, r8
    mov  rcx, r10
    call modmul64
.sq:
    push rax
    mov  rax, r8
    mov  rbx, r8
    mov  rcx, r10
    call modmul64
    mov  r8, rax
    pop  rax
    shr  r9, 1
    jmp  .loop
.done:
    pop  r10
    pop  r9
    pop  r8
    pop  rcx
    pop  rbx
    ret

; =============================================================================
; MILLER_RABIN: strong pseudoprime test
;   RDI=n  RBX=base  ->  RAX=1(pass) or 0(fail)
; =============================================================================

miller_rabin:
    push rbx
    push rcx
    push rdx
    push r8
    push r9
    push r10
    push r11
    mov  r10, rdi
    mov  r11, rbx
    ; n-1 = 2^s * d
    mov  r8, r10
    dec  r8
    xor  r9, r9
    mov  rcx, r8
.find_sd:
    test rcx, 1
    jnz  .sd_done
    shr  rcx, 1
    inc  r9
    jmp  .find_sd
.sd_done:
    ; x = base^d mod n
    mov  rdi, r11
    mov  rsi, rcx
    mov  rdx, r10
    call pow_mod_int
    cmp  rax, 1
    je   .pass
    cmp  rax, r8
    je   .pass
    mov  rdx, r9
    dec  rdx
    jz   .fail
.mr_loop:
    push rdx
    push r8
    push r10
    mov  rbx, rax
    mov  rcx, r10
    call modmul64
    pop  r10
    pop  r8
    pop  rdx
    cmp  rax, r8
    je   .pass
    test rax, rax
    jz   .fail
    dec  rdx
    jnz  .mr_loop
.fail:
    xor  rax, rax
    jmp  .mr_ret
.pass:
    mov  rax, 1
.mr_ret:
    pop  r11
    pop  r10
    pop  r9
    pop  r8
    pop  rdx
    pop  rcx
    pop  rbx
    ret

; =============================================================================
; JACOBI: Jacobi symbol (a|n) -> RAX in {-1, 0, +1}
;   RDI=a (signed)  RSI=n (odd positive)
; =============================================================================

jacobi:
    push rbx
    push rcx
    push rdx
    push r8
    push r9
    push r10
    push r11
    mov  r8, rdi
    mov  r9, rsi
    mov  r10, 1
    ; Reduce a mod n, non-negative
    mov  rax, r8
    cqo
    mov  rbx, r9
    idiv rbx
    mov  r8, rdx
    test r8, r8
    jns  .a_ok
    add  r8, r9
.a_ok:
.jloop:
    test r8, r8
    jz   .j_zero
    cmp  r8, 1
    je   .j_return
    ; Strip factors of 2
    xor  r11, r11
.strip2:
    test r8, 1
    jnz  .stripped
    shr  r8, 1
    inc  r11
    jmp  .strip2
.stripped:
    test r11, 1
    jz   .e_even
    mov  rax, r9
    and  rax, 7
    cmp  rax, 3
    je   .flip2
    cmp  rax, 5
    je   .flip2
    jmp  .e_even
.flip2:
    neg  r10
.e_even:
    cmp  r8, 1
    je   .j_return
    ; Quadratic reciprocity
    mov  rax, r8
    and  rax, 3
    cmp  rax, 3
    jne  .no_qr
    mov  rax, r9
    and  rax, 3
    cmp  rax, 3
    jne  .no_qr
    neg  r10
.no_qr:
    ; jacobi(a,n) -> jacobi(n mod a, a)
    mov  rax, r9
    xor  rdx, rdx
    div  r8
    mov  r9, r8
    mov  r8, rdx
    jmp  .jloop
.j_zero:
    xor  rax, rax
    jmp  .jdone
.j_return:
    mov  rax, r10
.jdone:
    pop  r11
    pop  r10
    pop  r9
    pop  r8
    pop  rdx
    pop  rcx
    pop  rbx
    ret

; =============================================================================
; ZPOW_MOD: phi^N mod M in Z[phi], phi^2=phi+1
;   RDI=N  RSI=M
;   Returns: FS_RA = phi-coeff, FS_RB = const-coeff
;
; Multiplication in Z[phi]: (ra*phi+rb)*(ba*phi+bb)
;   phi-coeff = ra*ba + ra*bb + rb*ba
;   const-coeff = ra*ba + rb*bb
; Squaring: (ba*phi+bb)^2
;   phi-coeff = ba^2 + 2*ba*bb
;   const-coeff = ba^2 + bb^2
; =============================================================================

zpow_mod:
    push rax
    push rbx
    push rcx
    push r8
    push r9
    push r10
    push r11
    push r12

    mov  r12, rsi           ; M

    ; result = phi^0 = 1 = 0*phi + 1
    mov  qword [FS_RA], 0
    mov  qword [FS_RB], 1
    ; base = phi = 1*phi + 0
    mov  qword [FS_BA], 1
    mov  qword [FS_BB], 0
    mov  [FS_N], r12

    mov  r8, rdi            ; exponent N

.zp_loop:
    test r8, r8
    jz   .zp_done

    test r8, 1
    jz   .zp_sq

    ; result = result * base
    ; phi-coeff: ra*ba + ra*bb + rb*ba
    mov  rax, [FS_RA]
    mov  rbx, [FS_BA]
    mov  rcx, r12
    call modmul64
    mov  r9, rax            ; ra*ba

    mov  rax, [FS_RA]
    mov  rbx, [FS_BB]
    mov  rcx, r12
    call modmul64
    mov  r10, rax           ; ra*bb

    mov  rax, [FS_RB]
    mov  rbx, [FS_BA]
    mov  rcx, r12
    call modmul64
    mov  r11, rax           ; rb*ba

    ; new phi-coeff = (ra*ba + ra*bb + rb*ba) mod M
    mov  rax, r9
    add  rax, r10
    cmp  rax, r12
    jb   .rc1
    sub  rax, r12
.rc1:
    add  rax, r11
    cmp  rax, r12
    jb   .rc2
    sub  rax, r12
.rc2:
    push rax                ; save new phi-coeff

    ; const-coeff: ra*ba + rb*bb
    mov  rax, [FS_RB]
    mov  rbx, [FS_BB]
    mov  rcx, r12
    call modmul64
    add  rax, r9            ; + ra*ba (already in r9)
    cmp  rax, r12
    jb   .rc3
    sub  rax, r12
.rc3:
    mov  [FS_RB], rax
    pop  rax
    mov  [FS_RA], rax

.zp_sq:
    ; base = base^2
    ; phi-coeff: ba^2 + 2*ba*bb
    mov  rax, [FS_BA]
    mov  rbx, [FS_BA]
    mov  rcx, r12
    call modmul64
    mov  r9, rax            ; ba^2

    mov  rax, [FS_BA]
    mov  rbx, [FS_BB]
    mov  rcx, r12
    call modmul64
    ; 2*ba*bb mod M
    add  rax, rax
    cmp  rax, r12
    jb   .bsq1
    sub  rax, r12
.bsq1:
    ; new phi-coeff = ba^2 + 2*ba*bb
    add  rax, r9
    cmp  rax, r12
    jb   .bsq2
    sub  rax, r12
.bsq2:
    push rax                ; save new base phi-coeff

    ; const-coeff: ba^2 + bb^2
    mov  rax, [FS_BB]
    mov  rbx, [FS_BB]
    mov  rcx, r12
    call modmul64           ; bb^2
    add  rax, r9            ; + ba^2
    cmp  rax, r12
    jb   .bsq3
    sub  rax, r12
.bsq3:
    mov  [FS_BB], rax
    pop  rax
    mov  [FS_BA], rax

    shr  r8, 1
    jmp  .zp_loop

.zp_done:
    pop  r12
    pop  r11
    pop  r10
    pop  r9
    pop  r8
    pop  rcx
    pop  rbx
    pop  rax
    ret

; =============================================================================
; GATE 3 — FROBENIUS Z[phi]: phi^N = expected in Z[phi]/(N)
;   RDI = N
;   Returns RAX = 1 (pass) or 0 (fail)
;   D=5 fixed. P=1, Q=-1. Jacobi(5,N) determines expected target.
;   Split (5|N)=+1: expect phi
;   Inert (5|N)=-1: expect beta = (N-1)*phi + 1
;   Ramified (5|N)=0: pass only if N=5
; =============================================================================

frobenius_test:
    push rbx
    push rcx
    push rdx
    push r8
    push r9

    mov  r8, rdi            ; N

    ; Jacobi(5, N)
    mov  rdi, 5
    mov  rsi, r8
    call jacobi
    mov  r9, rax            ; legendre symbol

    ; Compute phi^N mod N
    mov  rdi, r8
    mov  rsi, r8
    call zpow_mod
    ; FS_RA = phi-coeff, FS_RB = const-coeff

    cmp  r9, 0
    je   .ramified

    cmp  r9, 1
    je   .split

    ; Inert: expect (N-1, 1)
    mov  rax, r8
    dec  rax                ; N-1
    cmp  [FS_RA], rax
    jne  .frob_fail
    cmp  qword [FS_RB], 1
    jne  .frob_fail
    jmp  .frob_pass

.split:
    ; Split: expect (1, 0)
    cmp  qword [FS_RA], 1
    jne  .frob_fail
    cmp  qword [FS_RB], 0
    jne  .frob_fail
    jmp  .frob_pass

.ramified:
    ; (5|N)=0 only when 5|N; only N=5 itself is prime
    cmp  r8, 5
    je   .frob_pass
    jmp  .frob_fail

.frob_fail:
    xor  rax, rax
    jmp  .frob_done
.frob_pass:
    mov  rax, 1
.frob_done:
    pop  r9
    pop  r8
    pop  rdx
    pop  rcx
    pop  rbx
    ret

; =============================================================================
; SELFRIDGE_SELECT: Selfridge Method A
;   RDI = N
;   Stores D -> ORA_SEL_D, Q -> ORA_SEL_Q
;   RAX = 0 ok, 1 perfect square, 2 composite (gcd found)
; =============================================================================

selfridge_select:
    push rbx
    push rcx
    push rdx
    push r8
    push r9
    mov  r8, rdi            ; N
    mov  r9, 5              ; |D|
    mov  rcx, 0             ; sign: 0=positive, 1=negative
.sel_loop:
    mov  rax, r9
    test rcx, rcx
    jz   .d_pos
    neg  rax
.d_pos:
    mov  [ORA_SEL_D], rax
    mov  rdi, rax
    mov  rsi, r8
    call jacobi
    cmp  rax, -1
    je   .found
    cmp  rax, 0
    je   .j_zero
    add  r9, 2
    xor  rcx, 1
    cmp  r9, 10000
    jb   .sel_loop
    mov  rax, 1
    jmp  .sel_done
.j_zero:
    cmp  r8, r9
    je   .found
    mov  rax, 2
    jmp  .sel_done
.found:
    mov  rdx, [ORA_SEL_D]
    mov  rax, 1
    sub  rax, rdx
    sar  rax, 2
    mov  [ORA_SEL_Q], rax
    xor  rax, rax
.sel_done:
    pop  r9
    pop  r8
    pop  rdx
    pop  rcx
    pop  rbx
    ret

; =============================================================================
; STRONG_LUCAS: gates 4+5 — strong Lucas + vpsp
;   RDI = N
;   Reads D, Q from ORA_SEL_D, ORA_SEL_Q
;   Returns RAX = 1 (both gates pass) or 0 (fail)
;   Stores ORA_G4_SLC, ORA_G5_VPS, ORA_LUCAS_U, ORA_LUCAS_V
; =============================================================================

strong_lucas:
    push rbx
    push rcx
    push rdx
    push r8
    push r9
    push r10
    push r11
    push r12
    push r13
    push r14
    push r15

    mov  r15, rdi           ; N
    mov  [LS_N], r15

    ; Reduce D mod N, non-negative
    mov  rax, [ORA_SEL_D]
    cqo
    mov  rbx, r15
    idiv rbx
    mov  rax, rdx
    test rax, rax
    jns  .d_ok
    add  rax, r15
.d_ok:
    mov  [LS_D], rax

    ; Reduce Q mod N, non-negative
    mov  rax, [ORA_SEL_Q]
    cqo
    mov  rbx, r15
    idiv rbx
    mov  rax, rdx
    test rax, rax
    jns  .q_ok
    add  rax, r15
.q_ok:
    mov  [LS_Q], rax

    ; inv2 = (N+1)/2 mod N
    mov  rax, r15
    inc  rax
    shr  rax, 1
    mov  [LS_INV2], rax

    ; m = N+1 = 2^s * d  (d odd)
    mov  rax, r15
    inc  rax
    xor  r8, r8
.strip_m:
    test rax, 1
    jnz  .stripped_m
    shr  rax, 1
    inc  r8
    jmp  .strip_m
.stripped_m:
    mov  [LS_S],    r8
    mov  [LS_DODD], rax
    mov  [ORA_SEL_S],    r8
    mov  [ORA_SEL_DODD], rax

    ; Init (U, V, Qk) = (0, 2, 1)
    xor  rax, rax
    mov  [LS_U], rax
    mov  rax, 2
    mov  [LS_V], rax
    mov  rax, 1
    mov  [LS_QK], rax

    ; Fast doubling over all bits of d (MSB first)
    mov  rax, [LS_DODD]
    bsr  r9, rax            ; r9 = MSB index

.bit_loop:
    ; DOUBLE
    mov  rax, [LS_U]
    mov  rbx, [LS_V]
    mov  rcx, [LS_N]
    call modmul64
    mov  [LS_U2], rax       ; U*V = new U

    mov  rax, [LS_V]
    mov  rbx, [LS_V]
    mov  rcx, [LS_N]
    call modmul64            ; V^2
    mov  rbx, [LS_QK]
    add  rbx, rbx
    cmp  rbx, [LS_N]
    jb   .qk2_ok
    sub  rbx, [LS_N]
.qk2_ok:
    sub  rax, rbx
    jns  .v2k_ok
    add  rax, [LS_N]
.v2k_ok:
    mov  [LS_V2], rax       ; V^2 - 2*Qk = new V

    mov  rax, [LS_QK]
    mov  rbx, [LS_QK]
    mov  rcx, [LS_N]
    call modmul64            ; Qk^2 = new Qk

    mov  rbx, [LS_U2]
    mov  [LS_U], rbx
    mov  rbx, [LS_V2]
    mov  [LS_V], rbx
    mov  [LS_QK], rax

    ; ADD if bit r9 of d is 1
    mov  rax, [LS_DODD]
    mov  rcx, r9
    shr  rax, cl
    test rax, 1
    jz   .no_add

    ; Save U_old for V computation
    mov  rax, [LS_U]
    mov  [LS_U2], rax

    ; U_new = (1*U + V) * inv2 mod N  (P=1)
    add  rax, [LS_V]
    cmp  rax, [LS_N]
    jb   .u_add_ok
    sub  rax, [LS_N]
.u_add_ok:
    mov  rbx, [LS_INV2]
    mov  rcx, [LS_N]
    call modmul64
    mov  [LS_U], rax

    ; V_new = (D*U_old + 1*V) * inv2 mod N
    mov  rax, [LS_D]
    mov  rbx, [LS_U2]
    mov  rcx, [LS_N]
    call modmul64            ; D*U_old
    add  rax, [LS_V]
    cmp  rax, [LS_N]
    jb   .v_add_ok
    sub  rax, [LS_N]
.v_add_ok:
    mov  rbx, [LS_INV2]
    mov  rcx, [LS_N]
    call modmul64
    mov  [LS_V], rax

    ; Qk_new = Qk * Q
    mov  rax, [LS_QK]
    mov  rbx, [LS_Q]
    mov  rcx, [LS_N]
    call modmul64
    mov  [LS_QK], rax

.no_add:
    test r9, r9
    jz   .doubling_done
    dec  r9
    jmp  .bit_loop

.doubling_done:
    ; Save (U_d, V_d, Q^d) — needed for vpsp restart
    mov  rax, [LS_U]
    mov  [LS_UD],      rax
    mov  [ORA_LUCAS_U], rax
    mov  rbx, [LS_V]
    mov  [LS_VD],      rbx
    mov  [ORA_LUCAS_V], rbx
    mov  rcx, [LS_QK]
    mov  [LS_QKD], rcx

    ; ── GATE 4: STRONG LUCAS ──
    ; Pass if U_d=0 (mod N) or V_d=0 (mod N) [r=0]
    ; or V_{d*2^r}=0 for r=1..s-1
    test rax, rax
    jz   .g4_pass
    test rbx, rbx
    jz   .g4_pass

    mov  r8, [LS_S]
    test r8, r8
    jz   .g4_fail

.vloop:
    dec  r8
    jz   .g4_fail

    ; V = V^2 - 2*Qk (using current Qk, BEFORE squaring it)
    mov  rax, [LS_V]
    mov  rbx, [LS_V]
    mov  rcx, [LS_N]
    call modmul64
    mov  rbx, [LS_QK]
    add  rbx, rbx
    cmp  rbx, [LS_N]
    jb   .v2a
    sub  rbx, [LS_N]
.v2a:
    sub  rax, rbx
    jns  .v2b
    add  rax, [LS_N]
.v2b:
    mov  [LS_V], rax
    test rax, rax
    jz   .g4_pass

    ; Now square Qk
    mov  rax, [LS_QK]
    mov  rbx, [LS_QK]
    mov  rcx, [LS_N]
    call modmul64
    mov  [LS_QK], rax
    jmp  .vloop

.g4_fail:
    mov  qword [ORA_G4_SLC], 0
    mov  qword [ORA_G5_VPS], 0
    xor  rax, rax
    jmp  .sl_done

.g4_pass:
    mov  qword [ORA_G4_SLC], 1

    ; ── GATE 5: VPSP — V_{N+1} = 2Q ──
    ; Restart from (U_d, V_d, Q^d), do s more doublings
    mov  rax, [LS_UD]
    mov  [LS_U], rax
    mov  rax, [LS_VD]
    mov  [LS_V], rax
    mov  rax, [LS_QKD]
    mov  [LS_QK], rax

    mov  r8, [LS_S]
.vpsp_loop:
    test r8, r8
    jz   .vpsp_check

    mov  rax, [LS_U]
    mov  rbx, [LS_V]
    mov  rcx, [LS_N]
    call modmul64
    mov  [LS_U2], rax

    mov  rax, [LS_V]
    mov  rbx, [LS_V]
    mov  rcx, [LS_N]
    call modmul64
    mov  rbx, [LS_QK]
    add  rbx, rbx
    cmp  rbx, [LS_N]
    jb   .vp1
    sub  rbx, [LS_N]
.vp1:
    sub  rax, rbx
    jns  .vp2
    add  rax, [LS_N]
.vp2:
    mov  [LS_V2], rax

    mov  rax, [LS_QK]
    mov  rbx, [LS_QK]
    mov  rcx, [LS_N]
    call modmul64
    mov  [LS_QK], rax

    mov  rax, [LS_U2]
    mov  [LS_U], rax
    mov  rax, [LS_V2]
    mov  [LS_V], rax

    dec  r8
    jmp  .vpsp_loop

.vpsp_check:
    ; Compare V_{N+1} with 2Q mod N
    mov  rax, [LS_Q]
    add  rax, rax
    cmp  rax, [LS_N]
    jb   .twoQ_ok
    sub  rax, [LS_N]
.twoQ_ok:
    cmp  rax, [LS_V]
    jne  .g5_fail

    mov  qword [ORA_G5_VPS], 1
    mov  rax, 1
    jmp  .sl_done

.g5_fail:
    mov  qword [ORA_G5_VPS], 0
    xor  rax, rax

.sl_done:
    pop  r15
    pop  r14
    pop  r13
    pop  r12
    pop  r11
    pop  r10
    pop  r9
    pop  r8
    pop  rdx
    pop  rcx
    pop  rbx
    ret

; =============================================================================
; IS_COMPOSITE_BY_TRIAL: trial division to ~100
;   RDI = N  ->  RAX = 1 composite, 0 inconclusive
; =============================================================================

is_composite_by_trial:
    push rbx
    push rcx
    push rdx
    mov  rcx, rdi
    cmp  rcx, 2
    jb   .yes
    cmp  rcx, 3
    jbe  .no
    test rcx, 1
    jz   .yes
    mov  rbx, 3
.loop:
    mov  rax, rbx
    mul  rax
    cmp  rcx, rax
    jb   .no
    mov  rax, rcx
    xor  rdx, rdx
    div  rbx
    test rdx, rdx
    jz   .yes
    add  rbx, 2
    cmp  rbx, 101
    jb   .loop
.no:
    xor  rax, rax
    jmp  .td_done
.yes:
    mov  rax, 1
.td_done:
    pop  rdx
    pop  rcx
    pop  rbx
    ret

; =============================================================================
; ORACLE_STEP: run all five gates on current ORA_CANDIDATE, advance
; =============================================================================

oracle_step:
    push rax
    push rbx
    push rcx
    push rdx
    push rdi
    push rsi
    push r8
    push r9
    push r10
    push r11
    push r12
    push r13
    push r14
    push r15

    mov  rax, [ORA_CANDIDATE]
    cmp  rax, 5
    jb   .advance

    ; Skip even
    test rax, 1
    jz   .advance

    ; Clear gate results
    mov  qword [ORA_G1_MR2], 0
    mov  qword [ORA_G2_MR3], 0
    mov  qword [ORA_G3_FRB], 0
    mov  qword [ORA_G4_SLC], 0
    mov  qword [ORA_G5_VPS], 0

    ; Trial division: is it provably composite?
    mov  rdi, [ORA_CANDIDATE]
    call is_composite_by_trial
    mov  [ORA_IS_COMPOSITE], rax

    inc  qword [ORA_TESTED]

    ; ── Gate 1: MR base 2 ──
    mov  rdi, [ORA_CANDIDATE]
    mov  rbx, 2
    call miller_rabin
    mov  [ORA_G1_MR2], rax
    test rax, rax
    jz   .advance           ; failed gate 1

    ; ── Gate 2: MR base 3 ──
    mov  rdi, [ORA_CANDIDATE]
    mov  rbx, 3
    call miller_rabin
    mov  [ORA_G2_MR3], rax
    test rax, rax
    jz   .advance           ; failed gate 2

    ; ── Gate 3: Frobenius Z[phi] ──
    mov  rdi, [ORA_CANDIDATE]
    call frobenius_test
    mov  [ORA_G3_FRB], rax
    test rax, rax
    jz   .advance           ; failed gate 3

    ; ── Selfridge parameter selection ──
    mov  rdi, [ORA_CANDIDATE]
    call selfridge_select
    test rax, rax
    jnz  .advance           ; composite detected by gcd

    ; ── Gates 4+5: Strong Lucas + vpsp ──
    mov  rdi, [ORA_CANDIDATE]
    call strong_lucas
    test rax, rax
    jz   .advance           ; failed gate 4 or 5

    ; ── All five gates passed ──
    inc  qword [ORA_PASS_COUNT]
    mov  rax, [ORA_CANDIDATE]
    mov  [ORA_LAST_PASS], rax

    ; Check if trial division confirmed composite
    cmp  qword [ORA_IS_COMPOSITE], 1
    jne  .not_pseudoprime

    ; !!! COMPOSITE THAT PASSES ALL FIVE GATES !!!
    inc  qword [ORA_PSEUDO_COUNT]
    mov  rax, [ORA_CANDIDATE]
    mov  [ORA_LAST_PSEUDO], rax

.not_pseudoprime:
.advance:
    add  qword [ORA_CANDIDATE], 2

    pop  r15
    pop  r14
    pop  r13
    pop  r12
    pop  r11
    pop  r10
    pop  r9
    pop  r8
    pop  rsi
    pop  rdi
    pop  rdx
    pop  rcx
    pop  rbx
    pop  rax
    ret

; =============================================================================
; VGA
; =============================================================================

vga_init:
    mov  rdi, VGA_BASE + VGA_ROW * 0
    mov  rsi, str_title + PHYS_ADJ
    mov  bl, 0x0B
    call vga_puts_color
    mov  rdi, VGA_BASE + VGA_ROW * 1
    mov  rsi, str_row1 + PHYS_ADJ
    mov  bl, 0x07
    call vga_puts_color
    mov  rdi, VGA_BASE + VGA_ROW * 2
    mov  rsi, str_row2 + PHYS_ADJ
    mov  bl, 0x07
    call vga_puts_color
    mov  rdi, VGA_BASE + VGA_ROW * 3
    mov  rsi, str_row3 + PHYS_ADJ
    mov  bl, 0x07
    call vga_puts_color
    mov  rdi, VGA_BASE + VGA_ROW * 4
    mov  rsi, str_row4 + PHYS_ADJ
    mov  bl, 0x07
    call vga_puts_color
    mov  rdi, VGA_BASE + VGA_ROW * 5
    mov  rsi, str_row5 + PHYS_ADJ
    mov  bl, 0x07
    call vga_puts_color
    mov  rdi, VGA_BASE + VGA_ROW * 6
    mov  rsi, str_row6 + PHYS_ADJ
    mov  bl, 0x0C
    call vga_puts_color
    mov  rdi, VGA_BASE + VGA_ROW * 7
    mov  rsi, str_row7 + PHYS_ADJ
    mov  bl, 0x08
    call vga_puts_color
    ret

vga_update:
    ; Row 1: N=  TESTED=
    mov  rdi, VGA_BASE + VGA_ROW * 1 + 2*2
    mov  rax, [ORA_CANDIDATE]
    call vga_hex64
    mov  rdi, VGA_BASE + VGA_ROW * 1 + 22*2
    mov  rax, [ORA_TESTED]
    call vga_hex64

    ; Row 2: gate results
    mov  rdi, VGA_BASE + VGA_ROW * 2 + 4*2
    mov  rax, [ORA_G1_MR2]
    call vga_gate
    mov  rdi, VGA_BASE + VGA_ROW * 2 + 12*2
    mov  rax, [ORA_G2_MR3]
    call vga_gate
    mov  rdi, VGA_BASE + VGA_ROW * 2 + 20*2
    mov  rax, [ORA_G3_FRB]
    call vga_gate
    mov  rdi, VGA_BASE + VGA_ROW * 2 + 28*2
    mov  rax, [ORA_G4_SLC]
    call vga_gate
    mov  rdi, VGA_BASE + VGA_ROW * 2 + 36*2
    mov  rax, [ORA_G5_VPS]
    call vga_gate

    ; Row 3: Selfridge params
    mov  rdi, VGA_BASE + VGA_ROW * 3 + 2*2
    mov  rax, [ORA_SEL_D]
    call vga_hex64
    mov  rdi, VGA_BASE + VGA_ROW * 3 + 20*2
    mov  rax, [ORA_SEL_Q]
    call vga_hex64
    mov  rdi, VGA_BASE + VGA_ROW * 3 + 36*2
    mov  rax, [ORA_SEL_S]
    call vga_hex64
    mov  rdi, VGA_BASE + VGA_ROW * 3 + 50*2
    mov  rax, [ORA_SEL_DODD]
    call vga_hex64

    ; Row 4: Lucas U_d, V_d
    mov  rdi, VGA_BASE + VGA_ROW * 4 + 4*2
    mov  rax, [ORA_LUCAS_U]
    call vga_hex64
    mov  rdi, VGA_BASE + VGA_ROW * 4 + 22*2
    mov  rax, [ORA_LUCAS_V]
    call vga_hex64

    ; Row 5: pass count, last pass
    mov  rdi, VGA_BASE + VGA_ROW * 5 + 16*2
    mov  rax, [ORA_PASS_COUNT]
    call vga_hex64
    mov  rdi, VGA_BASE + VGA_ROW * 5 + 39*2
    mov  rax, [ORA_LAST_PASS]
    call vga_hex64

    ; Row 6: pseudoprime count, last (holy grail)
    mov  rdi, VGA_BASE + VGA_ROW * 6 + 14*2
    mov  rax, [ORA_PSEUDO_COUNT]
    call vga_hex64
    mov  rdi, VGA_BASE + VGA_ROW * 6 + 33*2
    mov  rax, [ORA_LAST_PSEUDO]
    call vga_hex64

    ; Row 7: phi-lattice
    mov  rdi, VGA_BASE + VGA_ROW * 7 + 14*2
    mov  rax, [STATE_K]
    call vga_hex64
    mov  rdi, VGA_BASE + VGA_ROW * 7 + 32*2
    mov  rax, [EARTH_N]
    call vga_hex64

    ret

; vga_gate: print "PASS" (green) or "FAIL" (red) at RDI, RAX=result
vga_gate:
    test rax, rax
    jz   .fail
    push rsi
    mov  rsi, str_pass + PHYS_ADJ
    mov  bl, 0x0A
    call vga_puts_color
    pop  rsi
    ret
.fail:
    push rsi
    mov  rsi, str_fail + PHYS_ADJ
    mov  bl, 0x0C
    call vga_puts_color
    pop  rsi
    ret

vga_puts_color:
.next:
    lodsb
    test al, al
    jz   .done
    mov  [rdi], al
    mov  [rdi+1], bl
    add  rdi, 2
    jmp  .next
.done:
    ret

vga_hex64:
    push rbx
    push rcx
    push rdi
    push rax
    mov  rcx, 16
    mov  rbx, rdi
.h:
    mov  rdx, rax
    shr  rdx, 60
    and  edx, 0x0F
    movzx edx, byte [hex_digits + PHYS_ADJ + rdx]
    mov  [rbx], dl
    mov  byte [rbx+1], 0x07
    add  rbx, 2
    shl  rax, 4
    loop .h
    pop  rax
    pop  rdi
    pop  rcx
    pop  rbx
    ret

; =============================================================================
; STRINGS
; =============================================================================

str_title:
    db "HDGL COMBINED ORACLE  5-GATE PRIMALITY TEST  Z[phi]+BPSW", 0
str_row1:
    db "N=                  TESTED=", 0
str_row2:
    db "G1:      G2:      G3:      G4:      G5:", 0
str_row3:
    db "D=                  Q=                s=      d=", 0
str_row4:
    db "U_d=                V_d=", 0
str_row5:
    db "PROB-PRIME COUNT=                LAST=", 0
str_row6:
    db "PSEUDOPRIME COUNT=            LAST=               !GRAIL!", 0
str_row7:
    db "PHI-LATTICE    K=               N_phi=", 0
str_pass: db "PASS", 0
str_fail: db "FAIL", 0

hex_digits: db "0123456789ABCDEF"

; =============================================================================
; AP TRAMPOLINE
; =============================================================================

BITS 16

ap_trampoline:
    cli
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00
    lgdt [cs:ap_gdt_ptr - ap_trampoline]
    mov eax, cr0
    or  eax, 1
    mov cr0, eax
    jmp dword 0x08:AP_PM_PHYS

align 8
ap_gdt_base:
    dq 0x0000000000000000
    dq 0x00CF9A000000FFFF
    dq 0x00CF92000000FFFF
    dq 0x00AF9A000000FFFF
ap_gdt_end:
ap_gdt_ptr:
    dw ap_gdt_end - ap_gdt_base - 1
    dd AP_TRAMP_PHYS + (ap_gdt_base - ap_trampoline)

BITS 32
ap_pm_entry:
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov esp, 0x00078000
    mov eax, cr4
    or  eax, (1 << 5)
    mov cr4, eax
    mov ecx, 0xC0000080
    rdmsr
    or  eax, (1 << 8)
    wrmsr
    mov eax, PML4_PHYS
    mov cr3, eax
    mov eax, cr0
    or  eax, (1 << 31)
    mov cr0, eax
    jmp dword 0x18:AP_LM_PHYS

BITS 64
ap_lm_entry:
.halt: cli
    hlt
    jmp .halt

ap_trampoline_end:

times (IMAGE_SECTORS * 512) - ($ - $$) db 0