Earth, Air, Fire, Water

files (14).zip (14.1 KB)

; =============================================================================
; HDGL — FOUR-ELEMENT BARE-METAL Z[φ] SUBSTRATE WITH WU-WEI ORACLE
; =============================================================================
;
; TARGET:     x86-64 / BIOS / QEMU
; BUILD:      nasm -f bin hdgl_wuwei.asm -o hdgl_wuwei.img
; RUN:        qemu-system-x86_64 -drive format=raw,file=hdgl_wuwei.img -smp 4 -m 128M -boot c
;
; ARCHITECTURE:
;   CPU 0 = FIRE    (operator / strategy selector)
;   CPU 1 = WATER   (inverse verification)
;   CPU 2 = EARTH   (N_phi pattern oracle)
;   CPU 3 = WIND    (T(X) fixed-point residual)
;
; WU-WEI ORACLE:
;   Each element reports resistance as a SIGNAL, not a failure.
;   ORACLE is a bitfield:
;     bit 0: WATER invariant broken
;     bit 1: EARTH pattern broken  (delta not alternating sign)
;     bit 2: EARTH magnitude wrong (|delta| != 2)
;     bit 3: WIND fixed point detected
;     bit 4: WIND diverging
;     bit 7: CRITICAL
;   FIRE reads ORACLE and selects strategy:
;     0x00 -> FLOWING RIVER  (advance normally)
;     0x02 -> NON-ACTION     (hold, log)
;     0x04 -> REDIRECT       (rebase)
;     0x08 -> CONVERGENCE    (log phi approach)
;     0x80+ -> CRITICAL      (halt + display)
;
; PHI INVARIANT:
;   N_phi(a,b) = -a² + ab + b²
;   For Fibonacci pairs: N oscillates ±2 every step.
;   This oscillation IS the healthy signal, not a failure.
;   EARTH verifies the pattern (alternating ±2), not invariance.
;
; =============================================================================

BITS 16
ORG 0x7C00

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

PAYLOAD_PHYS       equ 0x00010000
AP_TRAMP_PHYS      equ 0x00008000

PML4_PHYS          equ 0x00009000
PDPT_PHYS          equ 0x0000A000
PD0_PHYS           equ 0x0000B000
PD1_PHYS           equ 0x0000C000
PD2_PHYS           equ 0x0000D000
PD3_PHYS           equ 0x0000E000

BSP_STACK          equ 0x00070000
AP_STACK_BASE      equ 0x00090000
AP_STACK_STRIDE    equ 0x00010000

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

; Physical address adjustment for 64-bit code
; Label values are ORG-relative (0x7C00+), actual physical = label + PHYS_ADJ
PHYS_ADJ           equ 0x8200

; 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

    ; Load sectors 2..64 to 0x10000
    mov si, disk_address_packet
    mov dl, [boot_drive]
    mov ah, 0x42
    int 0x13
    jc boot_disk_error

    ; Copy AP trampoline to 0x8000
    ; Source: payload at 0x10000 + (ap_trampoline - payload_start_in_file)
    ; payload_start_in_file = 512 bytes (sector 2)
    ; ap_trampoline offset within payload = (ap_trampoline - boot_start) - 512
    mov ax, 0x1000
    mov ds, ax
    mov ax, 0x0800
    mov es, ax
    ; SI = offset within payload (NOT offset from boot_start)
    mov si, ap_trampoline - boot_start - 512
    xor di, di
    mov cx, (ap_trampoline_end - ap_trampoline + 1) / 2
    cld
    rep movsw

    xor ax, ax
    mov ds, ax
    mov es, ax

    ; A20
    in  al, 0x92
    or  al, 00000010b
    and al, 11111110b
    out 0x92, al

    ; GDT
    lgdt [gdt_ptr]

    ; 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 0x1000
    dq 1

boot_drive:  db 0

boot_error_msg:  db "HDGL DISK ERROR",0

; =============================================================================
; 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

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

    ; 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

    ; 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

    ; 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

    cmp ebx, 4
    jb  .serial
    mov qword [PARALLEL_MODE], 1
    jmp .mode_done
.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 [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

.bsp_fire:
    call role_fire

.halt:
    cli
    hlt
    jmp .halt

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

start_aps:
    ; Enable BSP local APIC
    mov ecx, 0x1B
    rdmsr
    or  eax, 0x800
    wrmsr

    mov r8, LAPIC_BASE

    ; INIT IPI to all excluding self
    mov dword [r8 + LAPIC_ICR_HIGH], 0
    mov dword [r8 + LAPIC_ICR_LOW],  0x000C4500
    call apic_wait

    ; SIPI #1 — vector 0x08 -> physical 0x8000
    mov dword [r8 + LAPIC_ICR_HIGH], 0
    mov dword [r8 + LAPIC_ICR_LOW],  0x000C4608
    call apic_wait

    ; SIPI #2
    mov dword [r8 + LAPIC_ICR_HIGH], 0
    mov dword [r8 + LAPIC_ICR_LOW],  0x000C4608
    call apic_wait

    ; Wait for all 4 CPUs to set their bits in READY_MASK
.wait_aps:
    mov rax, [READY_MASK]
    and eax, 0xF
    cmp eax, 0xF
    jne .wait_aps
    ret

apic_wait:
    mov ecx, 100000
.spin:
    pause
    loop .spin
    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

    ; ── 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

.wait:
    mov rax, [REQUEST_K]
    cmp rax, r15
    je  .wait
    mov r15, rax
    call water_compute
    mov [DONE_WATER], r15
    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:
    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
    ret
.tri_neg:
    mov qword [TRINARY], -1
    ret
.tri_zero:
    mov qword [TRINARY], 0
    ret

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

role_earth:
    xor r15d, r15d
    lock or qword [READY_MASK], 4

.wait:
    mov rax, [REQUEST_K]
    cmp rax, r15
    je  .wait
    mov r15, rax
    call earth_compute
    mov [DONE_EARTH], r15
    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:
    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
    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:
    ret

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

role_wind:
    xor r15d, r15d
    lock or qword [READY_MASK], 8

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

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

BITS 16

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

    ; Use GDT already loaded in boot sector (still at 0x7C00 in RAM)
    lgdt [gdt_ptr]

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

    jmp dword 0x08:AP_PM_PHYS

; AP: 32-bit pmode
BITS 32

ap_pm_entry:
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov esp, 0x00078000     ; temporary stack for AP

    ; 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

    ; 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

    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

    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
    jmp .print
.flowing:
    mov rsi, str_strat_flowing
    jmp .print
.nonaction:
    mov rsi, str_strat_nonaction
    jmp .print
.redirect:
    mov rsi, str_strat_redirect
    jmp .print
.converge:
    mov rsi, str_strat_converge
    jmp .print
.critical:
    mov rsi, str_strat_critical
.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_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"

; =============================================================================
; 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 + (0x10000 - 0x7E00)
LONG_MODE_ENTRY_PHYS equ long_mode_entry  + (0x10000 - 0x7E00)
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