; ============================================================================
; hdgl_analog_alpine_emit.asm
; ============================================================================
; Generated emit block from hdgl_analog_alpine.hdgl.
; Included by hdgl_router64.asm via:
;   %include "hdgl_analog_alpine_emit.asm"   ; [SEAM 4]
;
; This file is PURE NASM — all labels are local (dot-prefixed) so they
; share the runtime_entry scope of hdgl_router64.asm without name collision.
;
; Verified clean: nasm -f bin (assembled as part of the 32KB runtime binary).
; ============================================================================

; ════════════════════════════════════════════════════════════════════
; SECTION I — CONSTANTS AND EQUATES
; (Included once at the top of the %include block in hdgl_router64.asm)
; ════════════════════════════════════════════════════════════════════

ANALOG_SLOTS_X      equ 32
ANALOG_INSTANCES_Y  equ 4096
ANALOG_TOTAL        equ (ANALOG_SLOTS_X * ANALOG_INSTANCES_Y)   ; 131072

ANALOG_LATTICE_BASE equ 0x210000
PHI_POWERS_TABLE    equ 0x310000
R_DIM_TABLE         equ 0x318000
WAVE_TABLE          equ 0x320000
SIN_COEFF_BASE      equ 0x320100
OMEGA_TIME          equ 0x320130
CYCLE_F64           equ 0x320138
STRAND_PSI          equ 0x320140
BINARY_READOUT      equ 0x321140
ANALOG_DN_AGGREGATE equ 0x325140

; ════════════════════════════════════════════════════════════════════
; SECTION II — INIT
; Call once from .kernel_init (SEAM 1).
; Zeroes lattice, builds phi_powers / r_dim / wave / sin tables,
; sets ANALOG_DN_AGGREGATE and [0x10100C] to 0x88888888 (neutral).
; ════════════════════════════════════════════════════════════════════

.analog_lattice_init:
    push rax
    push rcx
    push rdi

    ; Zero the 1MB lattice (131072 × 8B via 128-bit SSE stores)
    pxor  xmm0, xmm0
    mov   rdi, ANALOG_LATTICE_BASE
    mov   rcx, (ANALOG_TOTAL * 8) / 16   ; 65536 × 128-bit stores
.ali_zero:
    movapd [rdi], xmm0
    add   rdi, 16
    dec   rcx
    jnz   .ali_zero

    ; phi_powers[4096]: p[0] = 1/φ⁷, p[i] = p[i-1] × (1/φ⁷)
    ; phi^7 CORRECTED: 0x403D08D12E6B76C8
    mov   rax, 0x403D08D12E6B76C8
    mov   [rsp - 8], rax
    movsd xmm1, [rsp - 8]          ; xmm1 = φ⁷
    mov   rax, 0x3FF0000000000000
    mov   [rsp - 8], rax
    movsd xmm0, [rsp - 8]
    divsd xmm0, xmm1               ; xmm0 = 1/φ⁷  (p[0])
    movsd xmm2, xmm0               ; xmm2 = multiplier (stays = 1/φ⁷)
    mov   rdi, PHI_POWERS_TABLE
    xor   rcx, rcx
.ali_pp:
    movsd [rdi + rcx*8], xmm0
    mulsd xmm0, xmm2               ; p[i+1] = p[i] × (1/φ⁷)
    inc   rcx
    cmp   rcx, ANALOG_INSTANCES_Y
    jl    .ali_pp

    ; r_dim[4096]: linear spread [0.3, 1.0]
    ; step = 0.7/4095 = 0x3F26586586586587
    mov   rdi, R_DIM_TABLE
    mov   rax, 0x3FD3333333333333   ; 0.3 (base)
    mov   [rsp - 8], rax
    movsd xmm0, [rsp - 8]
    mov   rax, 0x3F26586586586587   ; 0.7/4095 (step)
    mov   [rsp - 8], rax
    movsd xmm1, [rsp - 8]
    xor   rcx, rcx
.ali_rdim:
    movsd [rdi + rcx*8], xmm0
    addsd xmm0, xmm1
    inc   rcx
    cmp   rcx, ANALOG_INSTANCES_Y
    jl    .ali_rdim
    mov   rax, 0x3FF0000000000000   ; force r_dim[4095] = exactly 1.0
    mov   [rdi + (ANALOG_INSTANCES_Y-1)*8], rax

    ; wave_table[32]: period 3, values {+0.3, 0.0, -0.3, ...}
    mov   rdi, WAVE_TABLE
    xor   rcx, rcx
.ali_wave:
    push  rcx
    xor   rdx, rdx
    mov   eax, ecx
    mov   r8d, 3
    div   r8d                       ; edx = x mod 3
    pop   rcx
    cmp   edx, 0
    je    .ali_wv_pos
    cmp   edx, 1
    je    .ali_wv_zero
    mov   r8, 0xBFD3333333333333    ; -0.3
    jmp   .ali_wv_store
.ali_wv_pos:
    mov   r8, 0x3FD3333333333333    ; +0.3
    jmp   .ali_wv_store
.ali_wv_zero:
    xor   r8, r8                    ; 0.0
.ali_wv_store:
    mov   [rdi + rcx*8], r8
    inc   rcx
    cmp   rcx, ANALOG_SLOTS_X
    jl    .ali_wave

    ; Taylor sin() coefficients (Horner, 6 terms, error < 2e-14)
    mov   rdi, SIN_COEFF_BASE
    mov   rax, 0x3FF0000000000000   ; c0 = +1.0
    mov   [rdi + 0],  rax
    mov   rax, 0xBFC5555555555555   ; c1 = -1/6
    mov   [rdi + 8],  rax
    mov   rax, 0x3F81111111111111   ; c2 = +1/120
    mov   [rdi + 16], rax
    mov   rax, 0xBF2A01A01A01A01A   ; c3 = -1/5040
    mov   [rdi + 24], rax
    mov   rax, 0x3EC71DE3A556C734   ; c4 = +1/362880
    mov   [rdi + 32], rax
    mov   rax, 0xBE5AE64567F544E4   ; c5 = -1/39916800
    mov   [rdi + 40], rax

    ; Phase accumulators to 0.0
    xor   rax, rax
    mov   [OMEGA_TIME], rax
    mov   [CYCLE_F64],  rax

    ; Zero STRAND_PSI (4KB) and BINARY_READOUT (16KB)
    mov   rdi, STRAND_PSI
    xor   rax, rax
    mov   rcx, ANALOG_INSTANCES_Y / 8
    rep   stosq
    mov   rdi, BINARY_READOUT
    mov   rcx, (ANALOG_TOTAL / 8) / 8
    rep   stosq

    ; Neutral Dn aggregate: 8 nibbles of 8 (balanced field)
    mov   dword [ANALOG_DN_AGGREGATE], 0x88888888
    mov   dword [0x10100C],            0x88888888

    pop   rdi
    pop   rcx
    pop   rax
    ret

; ════════════════════════════════════════════════════════════════════
; SECTION III — SIN(x)  [SSE2, range-reduced, Horner 6-term]
; IN:  xmm0 = x (radians, any range)
; OUT: xmm0 = sin(x)  (error < 2e-14 for |x_reduced| < π)
; Clobbers: xmm1-xmm3 and rax via stack scratch only.
; ════════════════════════════════════════════════════════════════════

.analog_sin:
    ; Range-reduce: x mod 2π, then shift to (-π, π]
    mov   rax, 0x3FC45F306DC9C883   ; 1/(2π)
    mov   [rsp - 8], rax
    movsd xmm1, [rsp - 8]
    movsd xmm2, xmm0
    mulsd xmm2, xmm1               ; x/(2π)
    cvttsd2si rax, xmm2
    cvtsi2sd  xmm3, rax            ; floor(x/(2π))
    movsd xmm1, xmm2
    comisd xmm1, xmm3
    jae   .asin_fok
    mov   rax, 0x3FF0000000000000
    mov   [rsp - 8], rax
    movsd xmm1, [rsp - 8]
    subsd xmm3, xmm1
.asin_fok:
    mov   rax, 0x401921FB54442D18   ; 2π
    mov   [rsp - 8], rax
    movsd xmm1, [rsp - 8]
    mulsd xmm3, xmm1               ; floor × 2π
    subsd xmm0, xmm3               ; x_reduced ∈ [0, 2π)
    mov   rax, 0x400921FB54442D18   ; π
    mov   [rsp - 8], rax
    movsd xmm1, [rsp - 8]
    comisd xmm0, xmm1
    jbe   .asin_range
    mov   rax, 0x401921FB54442D18
    mov   [rsp - 8], rax
    movsd xmm1, [rsp - 8]
    subsd xmm0, xmm1               ; shift to (-π, π]
.asin_range:
    ; Horner: sin(x) = x*(c0 + x²*(c1 + x²*(c2 + x²*(c3 + x²*(c4 + x²*c5)))))
    movsd xmm1, xmm0
    mulsd xmm1, xmm1               ; x²
    movsd xmm2, [SIN_COEFF_BASE + 40]
    mulsd xmm2, xmm1
    addsd xmm2, [SIN_COEFF_BASE + 32]
    mulsd xmm2, xmm1
    addsd xmm2, [SIN_COEFF_BASE + 24]
    mulsd xmm2, xmm1
    addsd xmm2, [SIN_COEFF_BASE + 16]
    mulsd xmm2, xmm1
    addsd xmm2, [SIN_COEFF_BASE + 8]
    mulsd xmm2, xmm1
    addsd xmm2, [SIN_COEFF_BASE + 0]
    mulsd xmm0, xmm2               ; x × polynomial = sin(x)
    ret

; ════════════════════════════════════════════════════════════════════
; SECTION IV — LATTICE TICK  (~1.8ms per tick at 3GHz)
; Call from .phi_tick (already called every shell loop iteration).
; After tick: calls binary_readout → ternary_psi → fold_to_digital.
; [0x10100C] is updated before .phi_tick returns.
; ════════════════════════════════════════════════════════════════════

.analog_lattice_tick:
    push rbx
    push rcx
    push rdx
    push rsi
    push rdi
    push r8
    push r9
    push r10
    push r11
    push r12
    push r13

    ; Pre-compute sin(omega_time)*0.05 (constant across all 4096 instances)
    movsd xmm0, [OMEGA_TIME]
    call  .analog_sin
    mov   rax, 0x3FA999999999999A   ; 0.05
    mov   [rsp - 8], rax
    movsd xmm7, [rsp - 8]
    mulsd xmm7, xmm0               ; xmm7 = sin(ω)*0.05

    movsd xmm6, [CYCLE_F64]

    ; Outer loop: instance y = 0..4095
    xor   r8d, r8d
.alt_outer:
    cmp   r8d, ANALOG_INSTANCES_Y
    jge   .alt_outer_done

    movsd xmm5, [PHI_POWERS_TABLE + r8*8]   ; omega_i
    movsd xmm4, [R_DIM_TABLE + r8*8]         ; r_dim

    ; resonance_base = 0.1*sin(cycle*0.05 + y)
    movsd xmm0, xmm6
    mov   rax, 0x3FA999999999999A
    mov   [rsp - 8], rax
    mulsd xmm0, [rsp - 8]
    cvtsi2sd xmm1, r8d
    addsd xmm0, xmm1
    call  .analog_sin
    mov   rax, 0x3FB999999999999A   ; 0.1
    mov   [rsp - 8], rax
    mulsd xmm0, [rsp - 8]
    movsd xmm3, xmm0               ; xmm3 = resonance_base

    ; Inner loop: slot x = 0..31
    xor   r9d, r9d
.alt_inner:
    cmp   r9d, ANALOG_SLOTS_X
    jge   .alt_inner_done

    ; Address of slot (y*32 + x) in the lattice
    mov   r10, r8
    imul  r10, ANALOG_SLOTS_X
    add   r10, r9
    lea   r11, [r10*8 + ANALOG_LATTICE_BASE]

    ; resonance: non-zero only at x%4==0
    mov   eax, r9d
    and   eax, 3
    jnz   .alt_no_res
    movsd xmm1, xmm3
    jmp   .alt_res_done
.alt_no_res:
    pxor  xmm1, xmm1
.alt_res_done:

    movsd xmm2, [WAVE_TABLE + r9*8]  ; wave overlay

    ; recursive dimension: r_dim*val*0.5 + 0.25*sin(cycle*r_dim + x)
    movsd xmm8, xmm4
    mulsd xmm8, [r11]              ; r_dim*val
    mov   rax, 0x3FE0000000000000   ; 0.5
    mov   [rsp - 8], rax
    mulsd xmm8, [rsp - 8]          ; r_dim*val*0.5
    movsd xmm9, xmm6
    mulsd xmm9, xmm4               ; cycle*r_dim
    cvtsi2sd xmm10, r9d
    addsd xmm9, xmm10              ; cycle*r_dim + x
    movsd xmm0, xmm9
    call  .analog_sin
    mov   rax, 0x3FD0000000000000   ; 0.25
    mov   [rsp - 8], rax
    mulsd xmm0, [rsp - 8]
    addsd xmm0, xmm8               ; rec = part1 + part2

    ; new_val = val + ω_i + resonance + wave + rec + sin(ω)*0.05
    movsd xmm11, [r11]
    addsd xmm11, xmm5              ; + omega_i
    addsd xmm11, xmm1              ; + resonance
    addsd xmm11, xmm2              ; + wave
    addsd xmm11, xmm0              ; + rec
    addsd xmm11, xmm7              ; + sin(omega_time)*0.05
    movsd [r11], xmm11

    inc   r9d
    jmp   .alt_inner
.alt_inner_done:
    inc   r8d
    jmp   .alt_outer

.alt_outer_done:
    ; Advance phase accumulators
    movsd xmm0, [OMEGA_TIME]
    mov   rax, 0x3FA999999999999A
    mov   [rsp - 8], rax
    addsd xmm0, [rsp - 8]
    movsd [OMEGA_TIME], xmm0

    movsd xmm0, [CYCLE_F64]
    mov   rax, 0x3FF0000000000000
    mov   [rsp - 8], rax
    addsd xmm0, [rsp - 8]
    movsd [CYCLE_F64], xmm0

    ; Readout pipeline (order matters: binary → ternary → fold)
    call .analog_binary_readout     ; lattice → BINARY_READOUT
    call .analog_ternary_psi        ; lattice + wave → STRAND_PSI
    call .analog_fold_to_digital    ; STRAND_PSI → [0x10100C]

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

; ════════════════════════════════════════════════════════════════════
; SECTION V — BINARY READOUT
; Threshold: slot > √φ (CORRECTED: 0x3FF45A3146A88456) → bit = 1.
; Old wrong value 0x3FF45AF6B0FAB6D4 caused ~2% misclassification.
; Uses r9 as counter (avoids the cl-clobber bug in the earlier draft).
; ════════════════════════════════════════════════════════════════════

.analog_binary_readout:
    push rax
    push rbx
    push rcx
    push rdx
    push r8
    push r9
    push rdi

    ; √φ CORRECTED
    mov   rax, 0x3FF45A3146A88456
    mov   [rsp - 8], rax
    movsd xmm1, [rsp - 8]

    ; Zero readout buffer (131072 bits = 16KB = 2048 qwords)
    mov   rdi, BINARY_READOUT
    xor   rax, rax
    mov   rcx, (ANALOG_TOTAL / 8) / 8
    rep   stosq

    xor   r9, r9
    mov   rbx, BINARY_READOUT
.abrx_loop:
    cmp   r9, ANALOG_TOTAL
    jge   .abrx_done
    movsd xmm0, [ANALOG_LATTICE_BASE + r9*8]
    comisd xmm0, xmm1
    jb    .abrx_next
    mov   rax, r9
    shr   rax, 3               ; byte index
    mov   rdx, r9
    and   rdx, 7               ; bit index
    mov   r8b, 1
    mov   cl, dl               ; CL = bit shift (rcx clobber safe here)
    shl   r8b, cl
    or    byte [rbx + rax], r8b
.abrx_next:
    inc   r9
    jmp   .abrx_loop
.abrx_done:
    pop   rdi
    pop   r9
    pop   r8
    pop   rdx
    pop   rcx
    pop   rbx
    pop   rax
    ret

; ════════════════════════════════════════════════════════════════════
; SECTION VI — TERNARY PSI
; For each of 4096 instances: majority vote of wave-signed slots
; above threshold → strand_psi = +1, 0, or -1 (stored as 0x01, 0x00, 0xFF).
; ════════════════════════════════════════════════════════════════════

.analog_ternary_psi:
    push rax
    push rbx
    push rcx
    push rdx
    push r8
    push r9

    mov   rax, 0x3FF45A3146A88456   ; √φ CORRECTED
    mov   [rsp - 8], rax
    movsd xmm1, [rsp - 8]

    xor   rcx, rcx                  ; y = instance index
.atp_outer:
    cmp   rcx, ANALOG_INSTANCES_Y
    jge   .atp_done
    xor   ebx, ebx                  ; polarity accumulator
    xor   rdx, rdx                  ; x = slot index
.atp_inner:
    cmp   rdx, ANALOG_SLOTS_X
    jge   .atp_inner_done
    ; wave sign at slot x
    movsd xmm0, [WAVE_TABLE + rdx*8]
    pxor  xmm2, xmm2
    ucomisd xmm0, xmm2
    jp    .atp_next             ; NaN: skip
    je    .atp_next             ; == 0.0: neutral
    ja    .atp_wp               ; > 0.0: positive vote
    ; wave < 0: negative vote if slot fires
    mov   r8, rcx
    imul  r8, ANALOG_SLOTS_X
    add   r8, rdx
    movsd xmm3, [ANALOG_LATTICE_BASE + r8*8]
    comisd xmm3, xmm1
    jb    .atp_next
    dec   ebx
    jmp   .atp_next
.atp_wp:
    mov   r8, rcx
    imul  r8, ANALOG_SLOTS_X
    add   r8, rdx
    movsd xmm3, [ANALOG_LATTICE_BASE + r8*8]
    comisd xmm3, xmm1
    jb    .atp_next
    inc   ebx
.atp_next:
    inc   rdx
    jmp   .atp_inner
.atp_inner_done:
    test  ebx, ebx
    js    .atp_neg
    jz    .atp_zero
    mov   byte [STRAND_PSI + rcx], 1
    jmp   .atp_next_inst
.atp_neg:
    mov   byte [STRAND_PSI + rcx], 0xFF
    jmp   .atp_next_inst
.atp_zero:
    mov   byte [STRAND_PSI + rcx], 0
.atp_next_inst:
    inc   rcx
    jmp   .atp_outer
.atp_done:
    pop   r9
    pop   r8
    pop   rdx
    pop   rcx
    pop   rbx
    pop   rax
    ret

; ════════════════════════════════════════════════════════════════════
; SECTION VII — FOLD TO DIGITAL   ← THE BRIDGE
;
; Folds 4096 ternary strand votes into one 32-bit Dn aggregate.
; Layout: 8 nibbles, one per bucket of 512 consecutive instances.
;   nibble[i] = clamp(8 + sum(psi[i*512..(i+1)*512-1])/64, 0, 15)
;   → 8 means perfectly balanced, 0 all-negative, 15 all-positive.
;
; Writes to BOTH:
;   [ANALOG_DN_AGGREGATE] = 0x325140  (live mirror)
;   [0x10100C]                        (the exact register .sh_cmd_wave
;                                      and .analog_summary already read)
;
; After SEAM 1, `wave` in the serial shell shows the live analog field.
; ════════════════════════════════════════════════════════════════════

.analog_fold_to_digital:
    push rax
    push rbx
    push rcx
    push rdx
    push r8
    push r9
    push r10

    xor   r10d, r10d                ; aggregate accumulator
    xor   r9, r9                    ; bucket index (0..7)

.afd_bucket:
    cmp   r9, 8
    jge   .afd_done

    ; sum STRAND_PSI[bucket*512 .. (bucket+1)*512 - 1]
    mov   rax, r9
    shl   rax, 9                    ; bucket * 512 = start index
    mov   rcx, 512
    xor   ebx, ebx
.afd_tally:
    movsx edx, byte [STRAND_PSI + rax]
    add   ebx, edx
    inc   rax
    dec   rcx
    jnz   .afd_tally

    ; nibble = clamp(8 + tally/64, 0, 15)
    mov   eax, ebx
    cdq
    mov   r8d, 64
    idiv  r8d                       ; eax = tally/64 (signed)
    add   eax, 8                    ; centre at 8
    cmp   eax, 0
    jge   .afd_lo_ok
    xor   eax, eax                  ; floor at 0 (GOI: saturate)
.afd_lo_ok:
    cmp   eax, 15
    jle   .afd_hi_ok
    mov   eax, 15                   ; ceil at 15 (GOI: saturate)
.afd_hi_ok:
    mov   ecx, r9d
    shl   ecx, 2                    ; nibble position = bucket × 4 bits
    shl   eax, cl
    or    r10d, eax

    inc   r9
    jmp   .afd_bucket

.afd_done:
    ; Write to both the live mirror and the digital bridge register
    mov   dword [ANALOG_DN_AGGREGATE], r10d
    mov   dword [0x10100C],            r10d

    pop   r10
    pop   r9
    pop   r8
    pop   rdx
    pop   rcx
    pop   rbx
    pop   rax
    ret

; ════════════════════════════════════════════════════════════════════
; SECTION VIII — SHELL COMMANDS
;
; analog    — show instance 0, all 32 slots (wave sign, fire bit)
;             and live Dn aggregate
; prismatic — show first 128 strand polarities + cluster vote
;             (4096-instance ternary summary)
;
; Wire into .sh_dispatch via SEAM 3.
; ════════════════════════════════════════════════════════════════════

.sh_cmd_analog:
    mov  rdi, .cmd_analog_s
    call .sh_strcmp_word
    test eax, eax
    jz   .cmd_analog_no
    mov  rsi, .s_analog_hdr
    call .com1_str

    mov   rax, 0x3FF45A3146A88456   ; √φ CORRECTED
    mov   [rsp - 8], rax
    movsd xmm1, [rsp - 8]

    xor   rcx, rcx
.sha_slot_loop:
    cmp   rcx, ANALOG_SLOTS_X
    jge   .sha_done
    mov   rsi, .s_slot_pfx
    call  .com1_str
    mov   eax, ecx
    call  .print_dec             ; uses existing .print_dec / .com1_dec32
    movsd xmm0, [WAVE_TABLE + rcx*8]
    pxor  xmm2, xmm2
    mov   rsi, .s_wave_pfx
    call  .com1_str
    ucomisd xmm0, xmm2
    je    .sha_wz
    ja    .sha_wp
    mov   al, '-'
    jmp   .sha_ws
.sha_wp: mov al, '+' ; jmp .sha_ws
.sha_wz: jmp .sha_wz2
.sha_wz2:
    mov   al, '0'
.sha_ws:
    call  .com1_send
    movsd xmm0, [ANALOG_LATTICE_BASE + rcx*8]
    mov   rsi, .s_fire_pfx
    call  .com1_str
    comisd xmm0, xmm1
    jb    .sha_nf
    mov   al, '1'
    jmp   .sha_fo
.sha_nf:
    mov   al, '0'
.sha_fo:
    call  .com1_send
    mov   al, 0x0A
    call  .com1_send
    inc   rcx
    jmp   .sha_slot_loop
.sha_done:
    mov  rsi, .s_dn_pfx
    call .com1_str
    mov  eax, dword [ANALOG_DN_AGGREGATE]
    call .print_hex8            ; uses existing .print_hex8
    mov  al, 0x0D
    call .com1_send
    mov  al, 0x0A
    call .com1_send
    mov  eax, 1
    ret
.cmd_analog_no:
    xor  eax, eax
    ret

.sh_cmd_prismatic:
    mov  rdi, .cmd_prism_s
    call .sh_strcmp_word
    test eax, eax
    jz   .cmd_prism_no
    mov  rsi, .s_prism_hdr
    call .com1_str
    ; Print first 128 strand PSIs in groups of 8
    xor  ecx, ecx
.spc_loop:
    cmp  ecx, 128
    jge  .spc_summary
    movsx eax, byte [STRAND_PSI + rcx]
    cmp  eax, 1
    je   .spc_p
    test eax, eax
    jz   .spc_z
    mov  al, '-'
    jmp  .spc_s
.spc_p: mov al, '+' ; jmp .spc_s
.spc_z: jmp .spc_z2
.spc_z2:
    mov al, '0'
.spc_s:
    call .com1_send
    inc  ecx
    mov  eax, ecx
    and  eax, 7
    jnz  .spc_loop
    mov  al, ' '
    call .com1_send
    jmp  .spc_loop
.spc_summary:
    mov  al, 0x0A
    call .com1_send
    ; Cluster vote: sum all 4096 ternary values
    xor  ebx, ebx
    xor  ecx, ecx
.spc_vote:
    cmp  ecx, ANALOG_INSTANCES_Y
    jge  .spc_vote_done
    movsx eax, byte [STRAND_PSI + rcx]
    add  ebx, eax
    inc  ecx
    jmp  .spc_vote
.spc_vote_done:
    mov  rsi, .s_cluster_pfx
    call .com1_str
    test ebx, ebx
    js   .spc_neg
    jz   .spc_zero
    mov  al, '+'
    call .com1_send
    mov  al, '1'
    jmp  .spc_emit
.spc_neg:
    mov  al, '-'
    call .com1_send
    mov  al, '1'
    jmp  .spc_emit
.spc_zero:
    mov  al, '0'
.spc_emit:
    call .com1_send
    mov  rsi, .s_dn_live
    call .com1_str
    mov  eax, dword [ANALOG_DN_AGGREGATE]
    call .print_hex8
    mov  al, 0x0D
    call .com1_send
    mov  al, 0x0A
    call .com1_send
    mov  eax, 1
    ret
.cmd_prism_no:
    xor  eax, eax
    ret

; ── String table ────────────────────────────────────────────────────
.cmd_analog_s:  db "analog", 0
.cmd_prism_s:   db "prismatic", 0
.s_analog_hdr:
    db "[analog@4096] inst=0  32 slots  threshold=sqrt(phi)", 0x0D, 0x0A, 0
.s_slot_pfx:    db "  [", 0
.s_wave_pfx:    db "] wave=", 0
.s_fire_pfx:    db " fire=", 0
.s_dn_pfx:      db "  Dn=0x", 0
.s_prism_hdr:
    db "[prismatic@4096] first 128 strand polarities:", 0x0D, 0x0A
    db "  (groups of 8, +/0/-)", 0x0D, 0x0A, 0
.s_cluster_pfx: db "  cluster=", 0
.s_dn_live:     db "  Dn=0x", 0
; ════════════════════════════════════════════════════════════════════
; SECTION I — EQUATES (deduplicate with PART I equates if co-included)
; ════════════════════════════════════════════════════════════════════

%ifndef ZP_BASE
ZP_BASE         equ 0x900000
CMDLINE_BUF     equ 0x901000
KERNEL_HDR_BUF  equ 0x902000
LOAD_ADDR       equ 0x1000000
INITRD_ADDR     equ 0x4000000
E820_SRC_COUNT  equ 0x4F8
E820_SRC_TABLE  equ 0x500
%endif

; ════════════════════════════════════════════════════════════════════
; SECTION II — MULTI-SECTOR ATA PIO WRAPPER
;
; The existing .disk_read reads exactly 1 sector regardless of RCX.
; This wrapper calls it in a loop: 1 call per sector, advancing
; RAX (LBA) and RDI (dest buffer) each iteration.
;
; IN:  RAX = starting LBA, RCX = sector count, RDI = dest
; OUT: fills RDI..RDI+(RCX*512)-1 from disk
; ════════════════════════════════════════════════════════════════════

.alpine_disk_read:
    push rax
    push rbx
    push rcx
    push rdi
    mov  rbx, rcx              ; sector counter
.adr_loop:
    test rbx, rbx
    jz   .adr_done
    push rdi                   ; preserve dest across call
    mov  rcx, 1
    call .disk_read            ; reads 512B from LBA=RAX to RDI
    pop  rdi
    add  rax, 1                ; next LBA
    add  rdi, 512              ; next sector buffer
    dec  rbx
    jmp  .adr_loop
.adr_done:
    pop  rdi
    pop  rcx
    pop  rbx
    pop  rax
    ret

; ════════════════════════════════════════════════════════════════════
; SECTION III — HEX8 FORMATTER
;
; Formats EAX as 8 uppercase ASCII hex digits at [RDI], advances
; RDI by 8. No lookup table dependency beyond a 16-byte nibble table.
; ════════════════════════════════════════════════════════════════════

.alpine_hex8:
    push rbx
    push rcx
    push rdx
    lea  rbx, [.alpine_hex_nibble]
    mov  ecx, 8
.ah8_l:
    rol  eax, 4
    mov  edx, eax
    and  edx, 0x0F
    mov  dl, [rbx + rdx]
    mov  [rdi], dl
    inc  rdi
    dec  ecx
    jnz  .ah8_l
    pop  rdx
    pop  rcx
    pop  rbx
    ret
.alpine_hex_nibble:  db "0123456789ABCDEF"

; ════════════════════════════════════════════════════════════════════
; SECTION IV — CMDLINE BUILDER
;
; Builds the kernel command line into CMDLINE_BUF:
;   root=/dev/sda2 rootfstype=ext4 console=ttyS0,9600n8
;   hdgl.dn=XXXXXXXX hdgl.tick=XXXXXXXX rdinit=/init
;
; hdgl.dn   = [0x10100C] — the Dn aggregate (analog fold output)
; hdgl.tick = [0x101010] — the phi_tick counter (low 32 bits)
;
; Alpine's initramfs /init reads these two tokens from /proc/cmdline
; and uses them in place of the lattice_init.sh derivation that the
; Docker prototype used from generated files. The logic is unchanged;
; only the input source changes (cmdline vs generated file).
; ════════════════════════════════════════════════════════════════════

.alpine_build_cmdline:
    push rax
    push rsi
    push rdi
    lea  rdi, [CMDLINE_BUF]

    ; prefix: "root=... hdgl.dn="
    lea  rsi, [.cmd_pre]
.abc_pre:
    mov  al, [rsi]
    test al, al
    jz   .abc_pre_done
    mov  [rdi], al
    inc  rsi
    inc  rdi
    jmp  .abc_pre
.abc_pre_done:

    ; Dn aggregate as 8 hex chars (digital-over-analog bridge value)
    mov  eax, dword [0x10100C]
    call .alpine_hex8          ; RDI advances by 8

    ; mid: " hdgl.tick="
    lea  rsi, [.cmd_mid]
.abc_mid:
    mov  al, [rsi]
    test al, al
    jz   .abc_mid_done
    mov  [rdi], al
    inc  rsi
    inc  rdi
    jmp  .abc_mid
.abc_mid_done:

    ; phi_tick low 32 as 8 hex chars
    mov  eax, dword [0x101010]
    call .alpine_hex8

    ; post: " rdinit=/init" + NUL
    lea  rsi, [.cmd_post]
.abc_post:
    mov  al, [rsi]
    mov  [rdi], al
    test al, al
    jz   .abc_post_done
    inc  rsi
    inc  rdi
    jmp  .abc_post
.abc_post_done:

    pop  rdi
    pop  rsi
    pop  rax
    ret

.cmd_pre:   db "earlyprintk=ttyS0,9600 console=ttyS0,9600n8 rdinit=/init hdgl.dn=", 0
.cmd_mid:   db " hdgl.tick=", 0
.cmd_post:  db " rdinit=/init", 0

; ════════════════════════════════════════════════════════════════════
; SECTION V — E820 COVERAGE CHECK
;
; Requires at least one usable (type=1) E820 entry that spans
; [LOAD_ADDR, INITRD_ADDR + 32MB). If none found: print message,
; return EAX=0. The caller aborts before handoff.
;
; Source: stage2 .do_e820 wrote 24B-stride entries to E820_SRC_TABLE
; with the count at E820_SRC_COUNT. This is the real E820 from BIOS.
; ════════════════════════════════════════════════════════════════════

.alpine_check_e820:
    push rbx
    push rcx
    push rdx
    push rsi
    movzx ecx, word [E820_SRC_COUNT]
    lea   rsi, [E820_SRC_TABLE]
.ace_loop:
    test  ecx, ecx
    jz    .ace_fail
    mov   eax, [rsi+16]        ; type field
    cmp   eax, 1               ; 1 = usable RAM
    jne   .ace_next
    mov   rax, [rsi]           ; base
    mov   rdx, [rsi+8]         ; length
    add   rdx, rax             ; end = base + length
    cmp   rax, LOAD_ADDR
    ja    .ace_next             ; base must be <= LOAD_ADDR
    mov   rbx, INITRD_ADDR
    add   rbx, 0x2000000       ; INITRD_ADDR + 32MB
    cmp   rdx, rbx
    jb    .ace_next             ; end must cover initrd
    mov   eax, 1
    jmp   .ace_done
.ace_next:
    add   rsi, 24
    dec   ecx
    jmp   .ace_loop
.ace_fail:
    mov   rsi, .msg_alpine_e820_fail
    call  .com1_str
    xor   eax, eax
.ace_done:
    pop   rsi
    pop   rdx
    pop   rcx
    pop   rbx
    ret
.msg_alpine_e820_fail:
    db "[alpine] no E820 usable region covers kernel+initrd -- aborting", 0x0D, 0x0A, 0

; ════════════════════════════════════════════════════════════════════
; SECTION VI — ZERO PAGE BUILDER
;
; Zeroes the 4KB boot_params at ZP_BASE and fills every field
; the 64-bit boot protocol requires (verified against
; Documentation/x86/boot.txt, field offsets cited inline).
;
; Copies REAL E820 entries from stage2 (24B stride, 0x500)
; into boot_params.e820_table (20B stride, ZP_BASE+0x2D0).
; This replaces the single-entry placeholder in the earlier draft.
;
; Checks XLF_KERNEL_64 (offset 0x236, bit 0): aborts if unset
; so we never jump to a non-64-bit-capable entry point.
;
; IN:  RDI = KERNEL_HDR_BUF (first sector of bzImage)
;      EDX = initrd byte size
; OUT: EAX = ZP_BASE on success, 0 on validation failure
; ════════════════════════════════════════════════════════════════════

.alpine_build_zero_page:
    push rbx
    push rcx
    push rdx
    push rsi
    push rdi
    push r12
    push r13
    mov  r12, rdi              ; r12 = kernel header buffer
    mov  r13d, edx             ; r13d = initrd size

    ; Validate boot_flag (0x1FE, word) == 0xAA55
    movzx eax, word [r12 + 0x1FE]
    cmp  ax, 0xAA55
    je   .azp_flag_ok
    mov  rsi, .msg_alpine_boot_flag_bad
    call .com1_str
    xor  eax, eax
    jmp  .azp_out
.azp_flag_ok:

    ; Validate "HdrS" magic (0x202, dword)
    mov  eax, [r12 + 0x202]
    cmp  eax, 0x53726448       ; "HdrS" LE
    je   .azp_hdrs_ok
    mov  rsi, .msg_alpine_hdrs_bad
    call .com1_str
    xor  eax, eax
    jmp  .azp_out
.azp_hdrs_ok:

    ; Check XLF_KERNEL_64 (0x236, bit 0) — require 64-bit entry
    movzx eax, word [r12 + 0x236]
    test  eax, 0x0001
    jnz   .azp_xlf_ok
    mov  rsi, .msg_alpine_no64
    call .com1_str
    xor  eax, eax
    jmp  .azp_out
.azp_xlf_ok:

    ; Zero the 4096-byte zero page
    mov  rdi, ZP_BASE
    xor  eax, eax
    mov  ecx, 4096 / 8
    rep  stosq

    ; Copy setup_header (offset 0x1F1 .. 0x2F0) into zero page
    ; at the same relative offset (required by spec)
    mov  rsi, r12
    add  rsi, 0x1F1
    mov  rdi, ZP_BASE
    add  rdi, 0x1F1
    mov  ecx, 0x100
    rep  movsb

    ; type_of_loader (0x210) = 0xFF (undefined/other)
    mov  byte [ZP_BASE + 0x210], 0xFF

    ; loadflags (0x211): bit0 LOADED_HIGH, bit7 CAN_USE_HEAP
    or   byte [ZP_BASE + 0x211], 0x81

    ; code32_start (0x214): where we loaded the protected-mode kernel
    mov  dword [ZP_BASE + 0x214], LOAD_ADDR

    ; cmd_line_ptr (0x228): 32-bit pointer to cmdline
    mov  dword [ZP_BASE + 0x228], CMDLINE_BUF

    ; ramdisk_image (0x218) / ramdisk_size (0x21C)
    mov  dword [ZP_BASE + 0x218], INITRD_ADDR
    mov  [ZP_BASE + 0x21C], r13d

    ; e820_entries (0x1E8) and e820_table (0x2D0, 20B/entry)
    ; Copy real E820 from stage2 (24B stride → 20B stride), cap 128
    movzx ecx, word [E820_SRC_COUNT]
    cmp   ecx, 128
    jbe   .azp_e820_count_ok
    mov   ecx, 128
.azp_e820_count_ok:
    mov   byte [ZP_BASE + 0x1E8], cl
    lea   rsi, [E820_SRC_TABLE]
    lea   rdi, [ZP_BASE + 0x2D0]
    test  ecx, ecx
    jz    .azp_e820_done
.azp_e820_loop:
    mov   rax, [rsi]           ; base (8B)
    mov   [rdi], rax
    mov   rax, [rsi + 8]       ; length (8B)
    mov   [rdi + 8], rax
    mov   eax, [rsi + 16]      ; type (4B)
    mov   [rdi + 16], eax
    add   rsi, 24              ; src stride = 24B
    add   rdi, 20              ; dst stride = 20B
    dec   ecx
    jnz   .azp_e820_loop
.azp_e820_done:

    mov  eax, ZP_BASE
.azp_out:
    pop  r13
    pop  r12
    pop  rdi
    pop  rsi
    pop  rdx
    pop  rcx
    pop  rbx
    ret

.msg_alpine_boot_flag_bad: db "[alpine] bad boot_flag (not 0xAA55) -- aborting", 0x0D, 0x0A, 0
.msg_alpine_hdrs_bad:      db "[alpine] bad HdrS magic -- aborting", 0x0D, 0x0A, 0
.msg_alpine_no64:          db "[alpine] kernel lacks XLF_KERNEL_64 -- aborting", 0x0D, 0x0A, 0

; ════════════════════════════════════════════════════════════════════
; SECTION VII — HANDOFF
;
; Sets up segments per 64-bit boot protocol spec, prints a message,
; then jumps to LOAD_ADDR + 0x200 (the 64-bit entry point).
;
; Reuses gdt64_ptr from hdgl_router64.asm — no second GDT is built.
; Router64's GDT64 already has:
;   selector 0x08 = flat code  (L=1 D=0)
;   selector 0x10 = flat data  (read/write)
; These are exactly what the 64-bit boot protocol requires.
;
; Register state at entry per spec:
;   RSI = boot_params pointer (ZP_BASE)
;   RBX = 0, RBP = 0, RDI = 0
;   CS = flat code (0x08), DS/ES/SS = flat data (0x10)
;   interrupts disabled
;
; IN:  RDI = kernel load address (LOAD_ADDR)
; Does not return.
; ════════════════════════════════════════════════════════════════════

.alpine_handoff:
    mov  r8, rdi               ; r8 = kernel load address

    lgdt [gdt64_ptr]           ; reload — gdt64_ptr from hdgl_router64.asm

    mov  ax, 0x10              ; flat data selector
    mov  ds, ax
    mov  es, ax
    mov  ss, ax

    mov  rsi, .msg_alpine_handoff
    call .com1_str

    cli

    mov  rsi, ZP_BASE          ; RSI = boot_params (per spec)
    xor  ebx, ebx             ; RBX = 0 (per spec)
    xor  ebp, ebp             ; RBP = 0 (per spec)
    xor  edi, edi             ; RDI = 0 (per spec)

    add  r8, 0x200             ; 64-bit entry = load_addr + 0x200
    push qword 0x08            ; __BOOT_CS selector
    push r8
    db   0x48                  ; REX.W prefix for 64-bit far return
    retf                       ; far jump: pop RIP then CS

.msg_alpine_handoff:
    db "[alpine] kernel entry — phi-lattice over", 0x0D, 0x0A, 0

; ════════════════════════════════════════════════════════════════════
; SECTION VIII — SHELL COMMAND
;
; alpine K I
;   K = kernel LBA (decimal)   e.g. 512
;   I = initrd LBA (decimal)   e.g. 16896
;
; Reads kernel (8MB ceiling), reads initrd (4MB ceiling),
; builds cmdline, validates header + E820, hands off.
;
; If either arg is omitted / zero: prints usage and returns.
; Wire into .sh_dispatch before .sh_unknown via SEAM 3.
; ════════════════════════════════════════════════════════════════════

.sh_cmd_alpine:
    mov  rdi, .cmd_alpine_s
    call .sh_strcmp_word
    test eax, eax
    jz   .cmd_alpine_no

    ; Parse kernel LBA
    call .sh_skip_token
    call .sh_parse_dec
    mov  r12, rax              ; r12 = kernel LBA
    ; Parse initrd LBA
    call .sh_skip_token
    call .sh_parse_dec
    mov  r13, rax              ; r13 = initrd LBA

    ; Require both args
    test r12, r12
    jz   .cmd_alpine_usage
    test r13, r13
    jz   .cmd_alpine_usage

    mov  rsi, .msg_alpine_aload
    call .com1_str

    ; ── Step 1: read first 2 sectors of kernel → KERNEL_HDR_BUF ──────
    ; HdrS magic is at byte 0x202 = sector 2, so read 2 sectors.
    mov  rax, r12
    mov  rcx, 2
    mov  rdi, KERNEL_HDR_BUF
    call .alpine_disk_read

    ; ── Step 2: read protected-mode kernel → LOAD_ADDR ───────────
    ; Start LBA = kernel_LBA + setup_sects + 1 (skip real-mode setup)
    movzx ecx, byte [KERNEL_HDR_BUF + 0x1F1]  ; setup_sects
    test  cl, cl
    jnz   .cka_sects_ok
    mov   cl, 4               ; default per boot protocol spec
.cka_sects_ok:
    movzx ecx, cl
    inc   ecx                  ; + 1 = first sector of protected-mode code
    mov   rax, r12
    add   rax, rcx             ; LBA of protected-mode code
    mov   rcx, 16384           ; 8MB ceiling (modern bzImage fits easily)
    mov   rdi, LOAD_ADDR
    call  .alpine_disk_read

    mov  rsi, .msg_alpine_kernel_ok
    call .com1_str

    ; ── Step 3: read initrd → INITRD_ADDR ────────────────────────
    mov  rax, r13
    mov  rcx, 8192             ; 4MB ceiling for Alpine initrd
    mov  rdi, INITRD_ADDR
    call .alpine_disk_read

    mov  rsi, .msg_alpine_initrd_ok
    call .com1_str

    ; ── Step 4: build cmdline ─────────────────────────────────────
    call .alpine_build_cmdline

    ; ── Step 5: check E820 covers target region ───────────────────
    call .alpine_check_e820
    test eax, eax
    jz   .cmd_alpine_abort

    ; ── Step 6: build zero page ───────────────────────────────────
    mov  rdi, KERNEL_HDR_BUF
    mov  edx, 0x400000         ; 4MB initrd ceiling (bytes)
    call .alpine_build_zero_page
    test eax, eax
    jz   .cmd_alpine_abort

    ; ── Step 7: handoff — does not return ─────────────────────────
    mov  rdi, LOAD_ADDR
    call .alpine_handoff

    ; (unreachable — handoff is a one-way far jump)
.cmd_alpine_abort:
    mov  rsi, .msg_alpine_abort
    call .com1_str
    mov  eax, 1
    ret
.cmd_alpine_usage:
    mov  rsi, .msg_alpine_usage
    call .com1_str
    mov  eax, 1
    ret
.cmd_alpine_no:
    xor  eax, eax
    ret

; ── String table ────────────────────────────────────────────────────
.cmd_alpine_s:  db "alpine", 0
.msg_alpine_aload:     db "[alpine] loading...", 0x0D, 0x0A, 0
.msg_alpine_kernel_ok: db "[alpine] kernel loaded (8MB)", 0x0D, 0x0A, 0
.msg_alpine_initrd_ok: db "[alpine] initrd loaded (4MB)", 0x0D, 0x0A, 0
.msg_alpine_abort:     db "[alpine] aborted", 0x0D, 0x0A, 0
.msg_alpine_usage:
    db "usage: alpine <kernel_lba> <initrd_lba>", 0x0D, 0x0A
    db "  ex:  alpine 512 16896", 0x0D, 0x0A, 0