from pathlib import Path
import os, shutil, zipfile, json, math, textwrap
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from reportlab.lib import colors
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch
from reportlab.platypus import (
    BaseDocTemplate, PageTemplate, Frame, Paragraph, Spacer, PageBreak, Image,
    Table, TableStyle, KeepTogether, LongTable, ListFlowable, ListItem, HRFlowable
)
from reportlab.pdfgen import canvas
from reportlab.pdfbase.pdfmetrics import stringWidth
from reportlab.lib.utils import ImageReader

ROOT = Path('/mnt/data')
OUT = ROOT / 'rbfl_scaled_coherent_geometry_preprint'
FIG = OUT / 'figures'
DATA = OUT / 'data'
CODE = OUT / 'code'
DOCS = OUT / 'docs'
SRC = OUT / 'source_docs'
for p in [OUT, FIG, DATA, CODE, DOCS, SRC]:
    if p.exists():
        shutil.rmtree(p)
    p.mkdir(parents=True, exist_ok=True)

# Colors
NAVY = colors.HexColor('#07172F')
DARK = colors.HexColor('#0B1020')
BLUE = colors.HexColor('#0E4C92')
CYAN = colors.HexColor('#00A6D6')
TEAL = colors.HexColor('#00C2A8')
GOLD = colors.HexColor('#FFB000')
ORANGE = colors.HexColor('#FF6B35')
GRAY = colors.HexColor('#5A6478')
LIGHT = colors.HexColor('#F5F8FC')
MID = colors.HexColor('#DCE6F2')
WHITE = colors.white
BLACK = colors.HexColor('#111111')

# Helper to copy if exists
def copy_if_exists(src, dst_dir, new_name=None):
    src = Path(src)
    if src.exists():
        dst = Path(dst_dir) / (new_name or src.name)
        try:
            shutil.copy2(src, dst)
        except PermissionError:
            # fall back via read/write
            dst.write_bytes(src.read_bytes())
        return dst
    return None

# Copy relevant data and docs into working package tree.
source_files = [
    'RBFL-RBFT 6.0 Improved Unified Model handover.pdf',
    'RBFL_Phase_Core_Ignition_Hypothesis_Report.pdf',
    'RBFL_Unified_Law candidate.pdf',
    'RBFL_RBFT_6_Improved_Architecture_and_Dictionary.pdf',
    'DB4v5_variables.pdf',
    'HDB5v2.3_variable_definitions.doc',
    'HDB5V2.3.xlsx',
]
for f in source_files:
    copy_if_exists(ROOT / f, SRC)

# Copy processed data used by report
paths_to_copy = {
    ROOT / 'rbfl_design_mode_north_aligned_coherence_test/design_mode_north_aligned_coherence_timeseries.csv': DATA / 'design_mode_north_aligned_coherence_timeseries.csv',
    ROOT / 'rbfl_design_mode_north_aligned_coherence_test/design_mode_north_aligned_milestones.csv': DATA / 'design_mode_north_aligned_milestones.csv',
    ROOT / 'rbfl_design_mode_north_aligned_coherence_test/design_mode_north_aligned_result.json': DATA / 'design_mode_north_aligned_result.json',
    ROOT / 'itpa_cphi_real_run/itpa_random_holdout_log_tauth_results.csv': DATA / 'itpa_random_holdout_log_tauth_results.csv',
    ROOT / 'itpa_cphi_real_run/itpa_leave_one_tokamak_out_results.csv': DATA / 'itpa_leave_one_tokamak_out_results.csv',
    ROOT / 'itpa_cphi_real_run/itpa_HIPB98Y2_residual_test.csv': DATA / 'itpa_HIPB98Y2_residual_test.csv',
    ROOT / 'itpa_cphi_real_run/itpa_hdb5v23_cphi_features_valid_benchmark_rows.csv': DATA / 'itpa_hdb5v23_cphi_features_valid_benchmark_rows.csv',
    ROOT / 'rbfl_itpa_parent_field_pipeline_v6_jet_evidence_sweep/outputs/JET_orientation_promotion_decision.csv': DATA / 'JET_orientation_promotion_decision.csv',
    ROOT / 'rbfl_itpa_parent_field_pipeline_v6_jet_evidence_sweep/outputs/JET_manual_visual_review_checklist.csv': DATA / 'JET_manual_visual_review_checklist.csv',
    ROOT / 'rbfl_itpa_parent_field_pipeline_v6_jet_evidence_sweep/data/orientation_evidence/JET_candidate_source_sweep.csv': DATA / 'JET_candidate_source_sweep.csv',
    ROOT / 'rbfl_itpa_parent_field_pipeline_v5_vector_class_gate/outputs/vector_class_gate_audit.csv': DATA / 'vector_class_gate_audit.csv',
    ROOT / 'rbfl_itpa_parent_field_pipeline_v3_site_metadata/outputs/tokamak_metadata_priority_counts.csv': DATA / 'tokamak_metadata_priority_counts.csv',
}
for src, dst in paths_to_copy.items():
    if src.exists():
        dst.write_bytes(src.read_bytes())

# Copy figures used
figure_sources = [
    ROOT / 'rbfl_design_mode_north_aligned_coherence_test/plot_coherence_lock.png',
    ROOT / 'rbfl_design_mode_north_aligned_coherence_test/plot_gnet_and_coherence.png',
    ROOT / 'rbfl_design_mode_north_aligned_coherence_test/plot_temperature_ignition_stabilization.png',
    ROOT / 'rbfl_design_mode_north_aligned_coherence_test/plot_fusion_to_cooling.png',
    ROOT / 'rbfl_design_mode_north_aligned_coherence_test/plot_radius_mass_stabilization.png',
    ROOT / 'rbfl_design_mode_north_aligned_coherence_test/snapshot_pre_coherence.png',
    ROOT / 'rbfl_design_mode_north_aligned_coherence_test/snapshot_field_coherence.png',
    ROOT / 'rbfl_design_mode_north_aligned_coherence_test/snapshot_ignition.png',
    ROOT / 'rbfl_design_mode_north_aligned_coherence_test/snapshot_stabilization.png',
    ROOT / 'itpa_cphi_real_run/plot_cphi_distribution.png',
    ROOT / 'itpa_cphi_real_run/plot_cphi_vs_log_HIPB98Y2.png',
    ROOT / 'itpa_cphi_real_run/plot_leave_one_tokamak_delta_r2.png',
    ROOT / 'itpa_cphi_real_run/plot_random_holdout_delta_r2.png',
    ROOT / 'rbfl_itpa_parent_field_pipeline_v6_jet_evidence_sweep/plots/JET_candidate_sources_by_usability.png',
    ROOT / 'rbfl_itpa_parent_field_pipeline_v6_jet_evidence_sweep/plots/JET_source_promotion_allowed.png',
    ROOT / 'rbfl_itpa_parent_field_pipeline_v5_vector_class_gate/plots/vector_class_gate_audit.png',
    ROOT / 'rbfl_itpa_parent_field_pipeline_v5_vector_class_gate/plots/vector_class_orientation_priority.png',
]
fig_copies = {}
for src in figure_sources:
    if src.exists():
        dst = FIG / src.name
        dst.write_bytes(src.read_bytes())
        fig_copies[src.name] = dst

# Load data
milestones = pd.read_csv(DATA / 'design_mode_north_aligned_milestones.csv') if (DATA / 'design_mode_north_aligned_milestones.csv').exists() else pd.DataFrame()
random_results = pd.read_csv(DATA / 'itpa_random_holdout_log_tauth_results.csv') if (DATA / 'itpa_random_holdout_log_tauth_results.csv').exists() else pd.DataFrame()
group_results = pd.read_csv(DATA / 'itpa_leave_one_tokamak_out_results.csv') if (DATA / 'itpa_leave_one_tokamak_out_results.csv').exists() else pd.DataFrame()
priority = pd.read_csv(DATA / 'tokamak_metadata_priority_counts.csv') if (DATA / 'tokamak_metadata_priority_counts.csv').exists() else pd.DataFrame()

# Derived summary numbers
rand_summary = {}
if not random_results.empty:
    rand_summary = {
        'baseline_R2': random_results['baseline_R2'].mean(),
        'augmented_R2': random_results['augmented_Cphi_R2'].mean(),
        'delta_Cphi': random_results['Delta_R2_Cphi'].mean(),
        'delta_naive': random_results['Delta_R2_naive_B_as_Q'].mean(),
        'delta_shuffle': random_results['Delta_R2_shuffled_Cphi'].mean(),
    }
if not group_results.empty:
    group_delta = group_results['Delta_R2_Cphi'].mean()
else:
    group_delta = np.nan

# Generate math equation images
def render_math(equation, name, fontsize=24, width=9.0, height=0.95, color='#07172F'):
    fig = plt.figure(figsize=(width, height), dpi=240)
    ax = fig.add_axes([0,0,1,1])
    ax.axis('off')
    ax.text(0.5, 0.52, equation, ha='center', va='center', fontsize=fontsize, color=color)
    path = FIG / f'eq_{name}.png'
    fig.savefig(path, transparent=True, bbox_inches='tight', pad_inches=0.02)
    plt.close(fig)
    return path

eqs = {
    'rbfl_law': r'$\mathbf{g}_{\rm RBFL}=\mathbf{g}_b+A_{\rm eff}\sqrt{a_\phi |\mathbf{g}_b|}\,\hat{\mathbf{g}}_b$',
    'aeff': r'$A_{\rm eff}=A_{\rm lock}S_{\rm phase}D_{\rm phase}G_{\rm rot}B_{\rm switch}H_{\rm proxy}P_{3D}$',
    'p3d': r'$P_{3D}=Z_{\rm phase}^{-1}$',
    'cphi': r'$C_\phi=0.18G+0.18S+0.18D+0.18Q+0.16Q_{B,\rm eff}-0.20I_{B,\rm eff}$',
    'mag_split': r'$Q_{B,\rm eff}=Q_BA_{\rm align},\qquad I_{B,\rm eff}=Q_B(1-A_{\rm align})+I_{\rm measured}$',
    'rphi': r'$R_\phi=\Gamma_{\rm lab}\Gamma_{\rm wall}\Gamma_b C_{\phi,\rm internal}A_NA_{\rm opp}$',
    'gnet': r'$g_{\rm net}=g_{\rm parent}(1-R_\phi)$',
    'neutral': r'$R_\phi\approx1\quad\Longleftrightarrow\quad g_{\rm net}\approx0$',
    'ignition': r'$T_{\rm core}\geq4.0\times10^6\,\mathrm{K},\quad Y_{\rm fusion}/L_{\rm cool}\geq1$',
    'stabilize': r'$|g_{\rm net}/g_{\rm parent}|\leq0.02,\quad C_{\rm field}\geq0.85$',
}
eq_paths = {k: render_math(v, k, fontsize=25 if k not in ['aeff','cphi','mag_split'] else 21) for k,v in eqs.items()}

# Generate conceptual diagrams with matplotlib

def draw_flowchart(path):
    fig, ax = plt.subplots(figsize=(11, 3.2), dpi=220)
    ax.set_xlim(0, 1); ax.set_ylim(0, 1); ax.axis('off')
    labels = [
        ('RBFL galaxy law', 'baryons -> phase operator'),
        ('Phase impedance', 'P3D = Zphase^-1'),
        ('Phase-core extension', 'structure first'),
        ('Earth parent well', 'local field boundary'),
        ('Scaled coherent node', 'Rphi ~ 1'),
        ('Ignition output', 'stabilized emission')
    ]
    xs = np.linspace(0.08, 0.92, len(labels))
    for i,(x,(t,s)) in enumerate(zip(xs,labels)):
        ax.add_patch(plt.Rectangle((x-0.065,0.37),0.13,0.26,facecolor='#F5F8FC',edgecolor='#0E4C92',lw=1.8,joinstyle='round'))
        ax.text(x,0.54,t,ha='center',va='center',fontsize=9.2,color='#07172F',weight='bold')
        ax.text(x,0.44,s,ha='center',va='center',fontsize=7.4,color='#5A6478')
        if i < len(xs)-1:
            ax.annotate('', xy=(xs[i+1]-0.08,0.50), xytext=(x+0.075,0.50), arrowprops=dict(arrowstyle='-|>', lw=1.8, color='#00A6D6'))
    ax.text(0.5,0.82,'From RBFL phase-field architecture to design-mode phase-core engineering', ha='center', fontsize=13, weight='bold', color='#07172F')
    ax.text(0.5,0.18,'The engineering extension is kept separate from the locked galaxy law: it is a falsifiable test pipeline, not a proof.', ha='center', fontsize=8.5, color='#5A6478')
    fig.savefig(path, bbox_inches='tight', pad_inches=0.05)
    plt.close(fig)

def draw_ladder(path):
    fig, ax = plt.subplots(figsize=(8, 4.4), dpi=220)
    ax.axis('off')
    steps = ['detect\nresiduals','map\norientation','fit nested\ntransfer','neutral\nband','stable\nnode','ignition\noutput']
    colors_ = ['#0E4C92','#0A71B8','#00A6D6','#00C2A8','#FFB000','#FF6B35']
    for i, step in enumerate(steps):
        x = 0.08 + i*0.155
        y = 0.50 + 0.08*np.sin(i)
        ax.add_patch(plt.Circle((x,y),0.06,color=colors_[i],alpha=0.94))
        ax.text(x,y,step,ha='center',va='center',fontsize=8,color='white',weight='bold')
        if i < len(steps)-1:
            ax.annotate('', xy=(x+0.135,0.50+0.08*np.sin(i+1)), xytext=(x+0.065,y), arrowprops=dict(arrowstyle='-|>', lw=1.8, color='#07172F'))
    ax.text(0.5,0.86,'Falsification-first engineering ladder', ha='center', fontsize=16, weight='bold', color='#07172F')
    ax.text(0.5,0.20,'The package deliberately separates discovery-mode proxy scans from diagnostic evidence.', ha='center', fontsize=9, color='#5A6478')
    fig.savefig(path, bbox_inches='tight', pad_inches=0.05)
    plt.close(fig)

def draw_def_ladder(path):
    fig, ax = plt.subplots(figsize=(10, 4.8), dpi=220)
    ax.set_xlim(0,1); ax.set_ylim(0,1); ax.axis('off')
    rows = [
        ('Locked RBFL law', 'baryonic source + square-root phase response', '#0E4C92'),
        ('Impedance discovery', 'P3D = Zphase^-1 from detector ratio', '#00A6D6'),
        ('Phase-core hypothesis', 'structure/coherence score Cphi', '#00C2A8'),
        ('Parent-field well', 'lab node inside Earth field', '#FFB000'),
        ('Design-mode proxy', 'north-aligned controlled geometry', '#FF6B35'),
    ]
    for i,(a,b,c) in enumerate(rows):
        y = 0.78 - i*0.16
        ax.add_patch(plt.Rectangle((0.08,y-0.055),0.30,0.11,facecolor=c,alpha=0.93))
        ax.text(0.23,y,a,ha='center',va='center',color='white',weight='bold',fontsize=10)
        ax.add_patch(plt.Rectangle((0.43,y-0.055),0.49,0.11,facecolor='#F5F8FC',edgecolor=c,lw=1.5))
        ax.text(0.675,y,b,ha='center',va='center',color='#07172F',fontsize=9.4)
        if i < len(rows)-1:
            ax.annotate('', xy=(0.23,y-0.12), xytext=(0.23,y-0.065), arrowprops=dict(arrowstyle='-|>', lw=1.5, color='#07172F'))
    ax.text(0.50,0.94,'Definition derivation ladder',ha='center',fontsize=16,weight='bold',color='#07172F')
    fig.savefig(path, bbox_inches='tight', pad_inches=0.05)
    plt.close(fig)

flow_path = FIG / 'fig_conceptual_flow.png'; draw_flowchart(flow_path)
ladder_path = FIG / 'fig_falsification_ladder.png'; draw_ladder(ladder_path)
def_path = FIG / 'fig_definition_ladder.png'; draw_def_ladder(def_path)

# ReportLab PDF generation
pdf_path = OUT / 'RBFL_Scaled_Coherent_Geometry_Hypothesis_Preprint.pdf'
PAGESIZE = letter
PAGE_W, PAGE_H = PAGESIZE
MARGIN = 0.62*inch

styles = getSampleStyleSheet()
styles.add(ParagraphStyle(name='TitleWhite', parent=styles['Title'], fontName='Helvetica-Bold', fontSize=28, leading=32, textColor=WHITE, alignment=TA_LEFT, spaceAfter=16))
styles.add(ParagraphStyle(name='SubtitleWhite', parent=styles['Normal'], fontName='Helvetica', fontSize=12.5, leading=17, textColor=colors.HexColor('#D8E9FF'), alignment=TA_LEFT))
styles.add(ParagraphStyle(name='H1x', parent=styles['Heading1'], fontName='Helvetica-Bold', fontSize=18, leading=22, textColor=NAVY, spaceBefore=10, spaceAfter=10))
styles.add(ParagraphStyle(name='H2x', parent=styles['Heading2'], fontName='Helvetica-Bold', fontSize=13.5, leading=16, textColor=BLUE, spaceBefore=8, spaceAfter=6))
styles.add(ParagraphStyle(name='Bodyx', parent=styles['BodyText'], fontName='Helvetica', fontSize=9.6, leading=13.2, textColor=BLACK, spaceAfter=6))
styles.add(ParagraphStyle(name='Smallx', parent=styles['BodyText'], fontName='Helvetica', fontSize=7.7, leading=10.2, textColor=GRAY, spaceAfter=4))
styles.add(ParagraphStyle(name='Captionx', parent=styles['BodyText'], fontName='Helvetica-Oblique', fontSize=7.6, leading=10, textColor=GRAY, alignment=TA_CENTER, spaceBefore=2, spaceAfter=8))
styles.add(ParagraphStyle(name='Callout', parent=styles['BodyText'], fontName='Helvetica-Bold', fontSize=10.2, leading=14, textColor=NAVY, backColor=colors.HexColor('#EAF7FB'), borderColor=CYAN, borderWidth=0.7, borderPadding=7, spaceBefore=6, spaceAfter=8))
styles.add(ParagraphStyle(name='Warning', parent=styles['BodyText'], fontName='Helvetica-Bold', fontSize=9.7, leading=13.5, textColor=colors.HexColor('#5A2A00'), backColor=colors.HexColor('#FFF3D6'), borderColor=GOLD, borderWidth=0.7, borderPadding=7, spaceBefore=6, spaceAfter=8))
styles.add(ParagraphStyle(name='TOC', parent=styles['BodyText'], fontName='Helvetica', fontSize=9.0, leading=12, textColor=NAVY, leftIndent=8))
styles.add(ParagraphStyle(name='Mono', parent=styles['BodyText'], fontName='Courier', fontSize=7.3, leading=9, textColor=BLACK))

class NumberedCanvas(canvas.Canvas):
    def __init__(self, *args, **kwargs):
        canvas.Canvas.__init__(self, *args, **kwargs)
        self._saved_page_states = []
    def showPage(self):
        self._saved_page_states.append(dict(self.__dict__))
        self._startPage()
    def save(self):
        num_pages = len(self._saved_page_states)
        for state in self._saved_page_states:
            self.__dict__.update(state)
            self.draw_page_number(num_pages)
            canvas.Canvas.showPage(self)
        canvas.Canvas.save(self)
    def draw_page_number(self, page_count):
        if self._pageNumber == 1:
            return
        self.setFont('Helvetica', 7)
        self.setFillColor(GRAY)
        self.drawRightString(PAGE_W - MARGIN, 0.38*inch, f'{self._pageNumber} / {page_count}')
        self.drawString(MARGIN, 0.38*inch, 'RBFL/RBFT Scaled Coherent Geometry Hypothesis - speculative research note')

def on_page(canv, doc):
    if canv._pageNumber == 1:
        return
    canv.saveState()
    canv.setFillColor(colors.HexColor('#F7FAFE'))
    canv.rect(0,0,PAGE_W,PAGE_H,stroke=0,fill=1)
    canv.setFillColor(NAVY)
    canv.rect(0,PAGE_H-0.18*inch,PAGE_W,0.18*inch,stroke=0,fill=1)
    canv.setFillColor(CYAN)
    canv.rect(0,PAGE_H-0.18*inch,1.25*inch,0.18*inch,stroke=0,fill=1)
    canv.setStrokeColor(MID)
    canv.line(MARGIN,0.62*inch,PAGE_W-MARGIN,0.62*inch)
    canv.restoreState()

def img(path, w=None, h=None, kind='plot'):
    path = Path(path)
    if not path.exists():
        return Paragraph(f'[missing figure: {path.name}]', styles['Warning'])
    ir = ImageReader(str(path))
    iw, ih = ir.getSize()
    if w is None and h is None:
        w = PAGE_W - 2*MARGIN
    if h is None:
        h = w * ih / iw
    if w is None:
        w = h * iw / ih
    return Image(str(path), width=w, height=h)

def section(title):
    return [Paragraph(title, styles['H1x']), HRFlowable(width='100%', thickness=0.8, color=MID, spaceAfter=8)]

def sub(title):
    return Paragraph(title, styles['H2x'])

def para(t, style='Bodyx'):
    return Paragraph(t, styles[style])

def eq(name, width=6.8*inch):
    return img(eq_paths[name], w=width)

def caption(t):
    return Paragraph(t, styles['Captionx'])

def callout(t):
    return Paragraph(t, styles['Callout'])

def warning(t):
    return Paragraph(t, styles['Warning'])

# Tables

def styled_table(data, col_widths=None, font_size=7.4, header=True, row_colors=True):
    tbl = LongTable(data, colWidths=col_widths, repeatRows=1 if header else 0)
    ts = [
        ('GRID', (0,0), (-1,-1), 0.25, colors.HexColor('#B8C7DA')),
        ('VALIGN', (0,0), (-1,-1), 'TOP'),
        ('LEFTPADDING', (0,0), (-1,-1), 4),
        ('RIGHTPADDING', (0,0), (-1,-1), 4),
        ('TOPPADDING', (0,0), (-1,-1), 3),
        ('BOTTOMPADDING', (0,0), (-1,-1), 3),
        ('FONTSIZE', (0,0), (-1,-1), font_size),
        ('FONTNAME', (0,0), (-1,-1), 'Helvetica'),
    ]
    if header:
        ts += [
            ('BACKGROUND', (0,0), (-1,0), NAVY),
            ('TEXTCOLOR', (0,0), (-1,0), WHITE),
            ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
        ]
    if row_colors:
        for r in range(1 if header else 0, len(data)):
            if r % 2 == 0:
                ts.append(('BACKGROUND', (0,r), (-1,r), colors.HexColor('#F1F5FA')))
    tbl.setStyle(TableStyle(ts))
    return tbl

# Create document/story
doc = BaseDocTemplate(str(pdf_path), pagesize=PAGESIZE, leftMargin=MARGIN, rightMargin=MARGIN, topMargin=0.65*inch, bottomMargin=0.62*inch)
frame = Frame(MARGIN, 0.70*inch, PAGE_W - 2*MARGIN, PAGE_H - 1.38*inch, id='normal')
doc.addPageTemplates([PageTemplate(id='main', frames=frame, onPage=on_page)])

story = []

# Cover page custom flowable via canvas on first page using a full-page image/table trick: use Paragraph with canvas? Simpler: add cover content using regular flowables but no on_page.
story.append(Spacer(1, 0.3*inch))
# cover background isn't possible through on_page for page 1, use a custom canvas flowable? We'll draw with a full-width colored table.
cover_table = Table([[Paragraph('RBFL/RBFT 6.0', styles['SubtitleWhite'])]], colWidths=[PAGE_W-2*MARGIN], rowHeights=[0.2*inch])
cover_table.setStyle(TableStyle([('BACKGROUND',(0,0),(-1,-1),NAVY),('LEFTPADDING',(0,0),(-1,-1),10),('VALIGN',(0,0),(-1,-1),'MIDDLE')]))
story.append(cover_table)
story.append(Spacer(1, 0.25*inch))
story.append(Paragraph('The Scaled Coherent Geometry Hypothesis', ParagraphStyle(name='CoverTitle', parent=styles['Title'], fontName='Helvetica-Bold', fontSize=31, leading=36, textColor=NAVY, alignment=TA_LEFT)))
story.append(Paragraph('A falsifiable parent-field framework for phase-core ignition', ParagraphStyle(name='CoverSub', parent=styles['Normal'], fontName='Helvetica', fontSize=15, leading=20, textColor=BLUE)))
story.append(Spacer(1, 0.25*inch))
story.append(img(flow_path, w=PAGE_W-2*MARGIN))
story.append(Spacer(1, 0.15*inch))
story.append(callout('<b>Core thesis:</b> Build scaled coherent geometry first. Star-like ignition is treated as the output of a stable phase-node, not the object being brute-forced.'))
story.append(Spacer(1, 0.10*inch))
story.append(para('Prepared for Levi S. Haye / RBFL Research Project. Status: speculative hypothesis and reproducible toy-model report. Version: 2026-06-16.', 'Smallx'))
story.append(para('This document is not a reactor design, not a construction guide, not proof of RBFL/RBFT, and not evidence that current fusion devices are aligned to the proposed parent-field condition.', 'Smallx'))
story.append(PageBreak())

# Scientific boundary
story += section('Scientific boundary and release-safe claim')
story.append(warning('This report presents a speculative, falsifiable hypothesis. It must not be read as established physics, a replacement for plasma physics, a reactor design, or proof that phase-coherent fusion has been achieved.'))
story.append(para('The work reframes the engineering target. Conventional fusion optimizes temperature, density, and confinement time. The proposed phase-core route asks whether a separate geometry/coherence condition must be satisfied before ignition becomes naturally stable.'))
story.append(eq('neutral', width=5.2*inch))
story.append(para('The design-mode toy simulation shows that, inside the model, a north-aligned parent-field proxy can drive the node into a near-neutral band, then ignition, then stabilization. This is an internal consistency result, not a physical validation.'))
story.append(callout('Release-safe claim: A reproducible toy model demonstrates the logic of a geometry-first ignition sequence and defines null-controlled tests needed before any real-device interpretation.'))

# Abstract
story += section('Abstract')
story.append(para('This report introduces the Scaled Coherent Geometry Hypothesis for phase-core ignition. The hypothesis states that a star-like ignition state should be treated as the consequence of coherent nested-field geometry, not as an object to be forced by heat alone. The framework extends the RBFL/RBFT phase-field research program as a separate engineering-side hypothesis. It preserves the RBFL locked galaxy law while translating the structure-first idea into a lab-scale parent-field model.'))
story.append(para('The central design variable is a rejection index R_phi, which couples internal phase-core coherence, laboratory boundary transfer, baryonic coupling, and a design-mode north-alignment factor. In the toy model, neutral parent-field balance occurs near R_phi = 1. A north-aligned design-mode simulation reaches field coherence, then ignition, then stabilization. The result is not evidence of real fusion, but it supplies a falsifiable test protocol: future experiments or device residual analyses must beat shuffled metadata, machine identity leakage, and leave-one-machine-out controls.'))

# TOC
story += section('Contents')
for i, item in enumerate([
    '1. Relationship to RBFL/RBFT and why the locked law is preserved',
    '2. Definition derivation: from phase impedance to phase-core coherence',
    '3. Mathematical core of the parent-field hypothesis',
    '4. ITPA/HDB5 real-data baseline and why it is not yet enough',
    '5. Metadata gates: location, orientation, vector class, and null controls',
    '6. Design-mode north-aligned simulation: coherence -> ignition -> stabilization',
    '7. Falsification protocol and next experiments',
    '8. Copyright, data-use, and release limitations',
    'Appendix A. Full definition dictionary',
    'Appendix B. Reproducibility package contents'
], 1):
    story.append(Paragraph(item, styles['TOC']))
story.append(PageBreak())

# Relationship to RBFL
story += section('1. Relationship to RBFL/RBFT')
story.append(para('RBFL/RBFT 6.0 is treated here as a speculative baryon-derived phase-field research program. The galaxy-side locked law is not altered by the phase-core fusion hypothesis. Instead, the engineering hypothesis borrows the same structural principle: baryonic organization first, coherent response second, observable output third.'))
story.append(eq('rbfl_law'))
story.append(eq('aeff'))
story.append(eq('p3d', width=3.0*inch))
story.append(para('In the galaxy program, the distinctive burden is not merely the square-root low-acceleration term. The burden is whether the phase response can be predicted from baryonic structure, projection, and impedance terms before residuals are inspected. The phase-core hypothesis uses this as a methodological template, not as a proof of fusion physics.'))
story.append(img(def_path, w=PAGE_W-2*MARGIN))
story.append(caption('Definition derivation ladder: the engineering extension is downstream of the RBFL/RBFT phase-field architecture, but remains separate from the locked galaxy law.'))
story.append(warning('Separation rule: RBFL Unified Law Candidate -> galaxy/field architecture. Phase-Core Ignition Hypothesis -> speculative engineering/fusion test pipeline. Do not use fusion outcomes as validation of the galaxy law.'))

# Definitions derivation
story += section('2. Definition derivation')
story.append(para('The definitions were not chosen as arbitrary labels. They were derived by moving step-by-step from RBFL phase-response architecture to a lab-node geometry problem. The table records the derivation pathway.'))

defs = [
    ('g_b', 'Baryonic acceleration field from the source distribution.', 'RBFL locked law: baryons -> potential -> acceleration.'),
    ('A_eff', 'Effective phase response multiplying the square-root acceleration carrier.', 'RBFL unified candidate: phase operator factors plus P3D.'),
    ('P3D', 'Projected 3D phase correction; reciprocal of Z_phase.', 'Discovered from detector/oracle ratio as projected impedance.'),
    ('Z_phase', 'Effective baryonic phase impedance.', 'Interpretation of P3D inverse in the unified law report.'),
    ('C_phi_internal', 'Internal phase-core coherence score from geometry, shear, density, confinement, aligned magnetic support, and instability penalty.', 'Phase-core fusion hypothesis: measurable structure-first variable.'),
    ('Q_B_eff', 'Useful magnetic support after alignment gating.', 'Magnetic field split into aligned confinement minus forced boundary stress.'),
    ('I_B_eff', 'Instability penalty from misaligned field plus measured instability.', 'Forced magnetic cage correction.'),
    ('Earth parent-field well', 'Local field environment dominated by Earth as parent mass.', 'Nested lab-node correction: chamber is not isolated even in vacuum.'),
    ('R_phi', 'Parent-field rejection/coherence index.', 'Neutral-buoyancy branch of the parent-field model.'),
    ('g_net', 'Residual effective parent-field acceleration in the toy model.', 'g_parent times one minus R_phi.'),
    ('A_N', 'North-aligned design-mode coherence factor.', 'Controlled design assumption, not historical machine orientation.'),
    ('A_opp', 'Opposition controller that tunes coherent rejection toward neutral balance.', 'Buoyant-stability branch of the parent-field model.'),
    ('Gamma_lab', 'Laboratory transfer factor.', 'Chamber and lab environment coupling.'),
    ('Gamma_wall', 'Boundary allowance or suppression factor.', 'Wall/chamber transfer function.'),
    ('Gamma_b', 'Baryonic fuel-scale coupling factor.', 'Collective plasma/baryonic coupling placeholder.'),
]
def_table = [['Symbol', 'Definition', 'Derived from']] + [[Paragraph(a, styles['Mono']), Paragraph(b, styles['Smallx']), Paragraph(c, styles['Smallx'])] for a,b,c in defs]
story.append(styled_table(def_table, col_widths=[1.15*inch, 2.75*inch, 2.75*inch], font_size=7.0))
story.append(PageBreak())

# Math core
story += section('3. Mathematical core')
story.append(para('The parent-field framework splits internal plasma coherence from environmental compatibility. This prevents the model from treating all magnetic confinement as useful or all Earth-field effects as evidence.'))
story.append(sub('Internal phase-core score'))
story.append(eq('cphi'))
story.append(eq('mag_split'))
story.append(para('The split is essential: raw magnetic field strength can either assist coherence or behave like a forced boundary wall. Only aligned confinement raises C_phi; misalignment contributes to I_B_eff.'))
story.append(sub('Parent-field rejection index'))
story.append(eq('rphi'))
story.append(eq('gnet'))
story.append(eq('neutral', width=5.7*inch))
story.append(para('In design-mode north-aligned proxy testing, A_N is set to 1 by construction. This is not a claim about the historical orientation of a real tokamak. It is a proposed design alignment used to ask whether the equations can produce a coherent sequence.'))
story.append(eq('ignition', width=5.4*inch))
story.append(eq('stabilize', width=5.4*inch))
story.append(PageBreak())

# ITPA
story += section('4. ITPA/HDB5 internal benchmark')
story.append(para('The ITPA/HDB5 run was used as the first real-data baseline for internal C_phi. It mapped H-mode database variables into G, S, D, Q, Q_B, A_align, I_measured, Q_B_eff, I_B_eff, and C_phi. The target was log thermal confinement time, with an IPB98-style baseline.'))
if rand_summary:
    data = [
        ['Metric', 'Mean value'],
        ['Baseline R2', f"{rand_summary['baseline_R2']:.6f}"],
        ['Baseline + C_phi R2', f"{rand_summary['augmented_R2']:.6f}"],
        ['Delta R2 from C_phi', f"{rand_summary['delta_Cphi']:.8f}"],
        ['Delta R2 from naive B-as-Q', f"{rand_summary['delta_naive']:.8f}"],
        ['Delta R2 from shuffled C_phi', f"{rand_summary['delta_shuffle']:.8f}"],
        ['Leave-one-tokamak mean Delta R2', f"{group_delta:.8f}"],
    ]
    story.append(styled_table(data, col_widths=[3.4*inch, 2.0*inch], font_size=8.4))
story.append(Spacer(1, 8))
story.append(img(FIG / 'plot_random_holdout_delta_r2.png', w=3.15*inch))
story.append(caption('Random holdout: internal C_phi produced only a tiny positive gain. This is not a validation; it is a pointer for refinement.'))
story.append(img(FIG / 'plot_leave_one_tokamak_delta_r2.png', w=5.75*inch))
story.append(caption('Leave-one-tokamak-out: cross-machine generalization is mixed and not yet strong.'))
story.append(warning('Real-data status: Internal C_phi contains structure but does not yet robustly validate the hypothesis. The current fusion extension remains speculative.'))
story.append(PageBreak())

# Metadata gates
story += section('5. Parent-field metadata and null gates')
story.append(para('The parent-field layer is a detection instrument, not a conclusion engine. It requires location, verified vector class, machine axis, transfer factors, and null-correlation gates. If the metadata are unknown, the only valid status is scaffold-only or design-mode proxy.'))
story.append(img(FIG / 'vector_class_orientation_priority.png', w=5.9*inch))
story.append(caption('Vector-class priority: JET dominates the valid benchmark rows, so it is the first orientation target.'))
story.append(img(FIG / 'JET_source_promotion_allowed.png', w=3.4*inch))
story.append(caption('JET source sweep: no source promoted JET beyond location-only because no true-north machine physics axis was verified.'))
story.append(callout('Strict diagnostic gate: no verified device/machine physics axis -> no parent-field diagnostic claim.'))
story.append(sub('Two modes, deliberately separated'))
mode_table = [
    ['Mode', 'Allowed use', 'Confidence meaning'],
    ['Diagnostic mode', 'Evidence-bearing device residual test.', 'Requires verified location, vector class, axis, transfer factors, null controls.'],
    ['Design-mode proxy', 'Hypothesis exploration and design requirement test.', 'May assign north alignment by design; cannot claim historical device evidence.'],
]
story.append(styled_table([[Paragraph(c, styles['Smallx']) for c in r] for r in mode_table], col_widths=[1.35*inch, 2.55*inch, 2.5*inch], font_size=7.8))
story.append(PageBreak())

# Design-mode simulation
story += section('6. Design-mode north-aligned simulation')
story.append(para('The north-aligned design-mode run intentionally sets the machine reference axis to true north. This is a controlled design assumption, not a historical claim. The purpose is to ask whether, inside the toy model, a correctly aligned artificial node can reach coherent neutral balance and then ignite.'))
story.append(eq('rphi'))
story.append(eq('neutral', width=5.6*inch))
story.append(img(FIG / 'plot_coherence_lock.png', w=6.7*inch))
story.append(caption('The opposition controller tunes R_phi toward the neutral parent-field band.'))
story.append(img(FIG / 'plot_gnet_and_coherence.png', w=6.7*inch))
story.append(caption('Field coherence occurs when g_net/g_parent approaches zero and the coherence index rises.'))
story.append(PageBreak())

story += section('7. Ignition and stabilization results')
if not milestones.empty:
    md = milestones.copy()
    for c in ['tau','C_phi_internal','A_opp','R_phi_design','gnet_over_gparent','field_coherence_index','M_core_Msun','R_core_Rsun','T_core_K','fusion_to_cooling']:
        if c in md.columns:
            md[c] = md[c].apply(lambda x: f'{x:.4g}' if pd.notna(x) else '')
    display_cols = ['milestone','step','tau','C_phi_internal','R_phi_design','gnet_over_gparent','field_coherence_index','T_core_K','fusion_to_cooling','state']
    data = [display_cols] + md[display_cols].values.tolist()
    story.append(styled_table([[Paragraph(str(c), styles['Smallx']) for c in row] for row in data], col_widths=[0.9*inch,0.45*inch,0.55*inch,0.75*inch,0.75*inch,0.85*inch,0.85*inch,0.7*inch,0.75*inch,1.15*inch], font_size=6.6))
story.append(Spacer(1, 8))
story.append(img(FIG / 'plot_temperature_ignition_stabilization.png', w=6.5*inch))
story.append(caption('Core temperature crosses the toy ignition threshold after coherence is established.'))
story.append(img(FIG / 'plot_fusion_to_cooling.png', w=6.5*inch))
story.append(caption('Fusion/cooling proxy crosses unity and remains above unity during the stabilized phase.'))
story.append(PageBreak())

story += section('8. Visual node states')
# Put snapshots 2x2
snapshots = [
    ('Pre-coherence', FIG / 'snapshot_pre_coherence.png'),
    ('Field coherence', FIG / 'snapshot_field_coherence.png'),
    ('Ignition', FIG / 'snapshot_ignition.png'),
    ('Stabilization', FIG / 'snapshot_stabilization.png'),
]
snap_tbl = []
for i in range(0, len(snapshots), 2):
    row_imgs = []
    row_caps = []
    for name, path in snapshots[i:i+2]:
        row_imgs.append(img(path, w=3.05*inch))
        row_caps.append(Paragraph(name, styles['Captionx']))
    snap_tbl.append(row_imgs)
    snap_tbl.append(row_caps)
t = Table(snap_tbl, colWidths=[3.25*inch,3.25*inch])
t.setStyle(TableStyle([('VALIGN',(0,0),(-1,-1),'TOP'),('ALIGN',(0,0),(-1,-1),'CENTER')]))
story.append(t)
story.append(callout('Within the toy model, the visible sequence is: diffuse node -> field coherence -> ignition -> stabilized star-like output. The result is a design-mode model output, not physical evidence.'))
story.append(PageBreak())

# Falsification
story += section('9. Falsification protocol')
story.append(para('The package is designed to fail cleanly. The parent-field hypothesis cannot be considered supported unless it beats nulls and machine identity leakage.'))
story.append(img(ladder_path, w=6.2*inch))
story.append(caption('Falsification-first ladder: residual detection precedes experimental interpretation.'))
gates = [
    ('Shuffled metadata', 'Real parent-field terms must beat globally shuffled metadata.'),
    ('TOK-preserving shuffle', 'The result must beat metadata shuffled within each machine.'),
    ('Machine identity leakage', 'Parent-field terms must add power after TOK one-hot controls.'),
    ('Leave-one-tokamak-out', 'A signal must generalize across held-out machines.'),
    ('Coefficient sign stability', 'The parent-field coefficient must keep a stable sign across folds.'),
    ('Vector-class gate', 'A hall/building axis cannot become a machine physics vector without evidence.'),
]
story.append(styled_table([['Gate', 'Failure condition']] + [[Paragraph(a, styles['Smallx']), Paragraph(b, styles['Smallx'])] for a,b in gates], col_widths=[2.2*inch,4.1*inch], font_size=7.8))
story.append(warning('Design-mode success does not override diagnostic gates. It only identifies a mathematically coherent design target for future testing.'))
story.append(PageBreak())

# Limitations
story += section('10. Limitations and next work')
limitations = [
    'The design-mode simulation is a toy model, not a plasma solver, stellar evolution model, or reactor model.',
    'The north alignment is assigned by design. It is not a claim about historical JET or any actual machine.',
    'The fusion and cooling terms are qualitative proxies, not a nuclear reaction network.',
    'Transfer factors Gamma_lab, Gamma_wall, and Gamma_b are placeholders requiring calibration.',
    'ITPA/HDB5 internal C_phi results are mixed and not yet validating.',
    'Real parent-field diagnostics require verified device-axis orientation and null-controlled residual tests.',
]
story.append(ListFlowable([ListItem(Paragraph(x, styles['Bodyx'])) for x in limitations], bulletType='bullet', leftIndent=18))
story.append(sub('Next work'))
nexts = [
    'Find true-north JET machine/physics-axis evidence or keep JET location-only.',
    'Define transfer-factor priors from chamber/wall geometry and vector class.',
    'Run proxy scans only under explicit design-mode labels.',
    'Re-run HDB5 residual tests with verified orientation metadata when available.',
    'Create a laboratory orientation scan protocol for a controlled tabletop or plasma diagnostic system before any fusion claim.'
]
story.append(ListFlowable([ListItem(Paragraph(x, styles['Bodyx'])) for x in nexts], bulletType='1', leftIndent=18))
story.append(PageBreak())

# Copyright
story += section('11. Copyright, data-use, and safety notice')
story.append(para('<b>Original research-package notice.</b> Original text, diagrams, code scaffolds, and generated toy-model outputs in this package are prepared for Levi S. Haye / RBFL Research Project, 2026. Review institutional, journal, and collaborator requirements before public posting.'))
story.append(para('<b>Third-party materials.</b> ITPA/H-mode database files, HDB5 variable definitions, EFDA/ITER/JET materials, and all cited public documents retain their respective copyrights and data-use terms. This package includes or references them for research reproducibility in this working context; public redistribution should follow the original licenses and permissions.'))
story.append(warning('<b>Safety notice:</b> This document is not a construction guide, not an operating procedure, not a reactor design, and not a recommendation to build high-energy devices. It contains conceptual and data-analysis methods only.'))
story.append(para('<b>Recommended release wording:</b> "This work proposes a highly speculative, falsifiable phase-core geometry hypothesis. It demonstrates internal model consistency and defines validation gates. It does not claim working fusion technology."'))
story.append(PageBreak())

# Appendix A definitions
story += section('Appendix A. Full definition dictionary')
more_defs = [
    ('G', 'Geometry quality', 'Elongation, triangularity, shape coherence.'),
    ('S', 'Shear/coherence quality', 'Internal inductance, q95, smooth current/field profile proxies.'),
    ('D', 'Density organization', 'Density peaking or Greenwald-limit proximity proxy.'),
    ('Q', 'Confinement quality', 'Predrive or measured confinement depending on target; never use target tau_E inside C_phi when predicting tau_E.'),
    ('Q_B', 'Raw magnetic drive', 'Normalized toroidal magnetic field or external magnetic strength.'),
    ('A_align', 'Magnetic alignment factor', 'q95/rational avoidance, geometry, shear, and instability suppression.'),
    ('I_measured', 'Measured instability penalty', 'Radiation fraction, Greenwald fraction, ELM/disruption, dW/dt.'),
    ('C_phi_parent', 'Parent-field coherence term', 'Environmental compatibility of child node with Earth parent-field well.'),
    ('hall_axis_azimuth_deg', 'Building or Torus Hall structural axis.', 'Can be recorded but cannot be used as physics vector without proof.'),
    ('machine_axis_azimuth_deg', 'Verified machine/physics vector azimuth.', 'Only allowed from engineering source, true-north plan, or validated georeference.'),
    ('orientation_vector_class', 'Which vector is being tested.', 'Prevents hall-axis/machine-axis confusion.'),
    ('diagnostic_ready', 'Metadata is eligible for evidence-bearing tests.', 'Requires location, machine axis, vector class, transfer factors, confidence > 0.'),
    ('design_mode', 'Exploratory mode where axis is assigned by design.', 'Allowed for hypothesis scanning but not evidence.'),
]
story.append(styled_table([['Term', 'Meaning', 'Derivation/use']] + [[Paragraph(a, styles['Mono']), Paragraph(b, styles['Smallx']), Paragraph(c, styles['Smallx'])] for a,b,c in more_defs], col_widths=[1.55*inch,2.35*inch,2.8*inch], font_size=7.0))
story.append(PageBreak())

# Appendix B repro
story += section('Appendix B. Reproducibility package')
story.append(para('The accompanying reproducibility package contains the PDF, generated figures, processed CSV data, source documents used in this working session, and scripts sufficient to reproduce the design-mode north-aligned toy run and inspect the real-data benchmark outputs.'))
repro_items = [
    ('paper/', 'Final PDF preprint.'),
    ('figures/', 'All figures embedded in the report.'),
    ('data/', 'Processed CSVs: design-mode time series, milestone table, ITPA benchmark outputs, vector-class metadata gates.'),
    ('source_docs/', 'Uploaded RBFL docs and HDB/ITPA variable references, subject to original data-use terms.'),
    ('code/', 'Standalone scripts, requirements, and paper-generation script.'),
    ('docs/', 'README, release notes, copyright/data-use notice, and how-to-run instructions.'),
]
story.append(styled_table([['Folder', 'Contents']] + [[Paragraph(a, styles['Mono']), Paragraph(b, styles['Smallx'])] for a,b in repro_items], col_widths=[1.6*inch,4.8*inch], font_size=7.8))
story.append(para('Primary reproduction command for the design-mode toy model:', 'Bodyx'))
story.append(Paragraph('python code/run_design_mode_north_aligned.py', styles['Mono']))
story.append(para('Primary output expected: design_mode_north_aligned_coherence_timeseries.csv and the five core plots showing coherence lock, neutral parent-field band, ignition, fusion/cooling, and stabilization.', 'Smallx'))

# Build PDF
doc.build(story, canvasmaker=NumberedCanvas)
print(f'PDF written: {pdf_path}')

# Write reproduction scripts and docs
run_design_script = r'''#!/usr/bin/env python3
"""
Reproduce the design-mode north-aligned phase-core toy simulation.
This script intentionally assigns proxy_axis_azimuth_deg = 0 degrees true north.
It is a hypothesis/proxy simulation, not a diagnostic result.
"""
from pathlib import Path
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import json

outdir = Path('outputs/design_mode_north_aligned')
outdir.mkdir(parents=True, exist_ok=True)
G = 6.67430e-11; M_earth = 5.9722e24; R_earth = 6.371e6
M_sun = 1.98847e30; AU = 1.495978707e11; R_sun = 6.957e8
m_p = 1.67262192369e-27; k_B = 1.380649e-23
height_m = 65.0; g_parent = G*M_earth/(R_earth+height_m)**2
Gamma_lab=1.10; Gamma_wall=0.95; Gamma_b=1.00; Gamma_total=Gamma_lab*Gamma_wall*Gamma_b
A_N=1.0; A_opp_max=1.25
M_core0=0.08*M_sun; M_env=0.92*M_sun; R0=120*AU; R_stable=0.0030*AU
T_ignition=4.0e6; rho_ref=2e-14; alpha_conf=75000.0; beta_accrete=5.5
n_steps=420; tau=np.linspace(0,1,n_steps)
C_internal=1/(1+np.exp(-12*(tau-0.45))); C_internal=(C_internal-C_internal.min())/(C_internal.max()-C_internal.min())
A_opp=np.zeros(n_steps); A_opp[0]=A_opp_max; R_core=np.zeros(n_steps); R_core[0]=R0
records=[]; ignition_step=None; stabilization_step=None; coherence_step=None; prev_fusion_to_cooling=1e-9
for i in range(n_steps):
    C=C_internal[i]
    if i>0:
        A_required=1/(Gamma_total*max(C,1e-6)*A_N)
        A_target=min(A_opp_max,max(0.20,A_required))
        response_rate=0.06 if ignition_step is None else 0.16
        A_opp[i]=A_opp[i-1]+response_rate*(A_target-A_opp[i-1])
    R_phi=Gamma_total*C*A_N*A_opp[i]
    gnet_over_gparent=1-R_phi; g_net_m_s2=g_parent*gnet_over_gparent
    stability_index=float(np.clip(np.exp(-(gnet_over_gparent/0.09)**2)*np.clip(C,0,1)**0.35,0,1))
    compression_index=float(np.clip(C*np.exp(-(gnet_over_gparent/0.16)**2),0,1.2))
    M_core=M_core0+M_env*(1-np.exp(-beta_accrete*C**2))
    R_pre=R0/(1+alpha_conf*compression_index**3)
    fusion_pressure=0.0 if ignition_step is None else 1/(1+np.exp(-(np.log10(max(prev_fusion_to_cooling,1e-30))-0.5)*4))
    R_target=max(R_pre,R_stable*(1+0.03*fusion_pressure))
    if i>0: R_core[i]=R_core[i-1]+0.08*(R_target-R_core[i-1])
    T_core=max(30.0, G*M_core*0.61*m_p/(5*k_B*R_core[i])*(0.90+0.10*stability_index))
    rho_core=M_core/((4/3)*np.pi*R_core[i]**3); rho_rel=rho_core/rho_ref
    temp_gate=1/(1+np.exp(-(np.log10(max(T_core,1))-6.2)*8))
    fusion_to_cooling=(T_core/T_ignition)**5*(rho_rel/1e19)**0.25*temp_gate*(max(stability_index,1e-9)/0.60)**2
    if stability_index<0.05: fusion_to_cooling*=stability_index/0.05
    prev_fusion_to_cooling=fusion_to_cooling
    field_coherent=(abs(gnet_over_gparent)<=0.05) and (stability_index>=0.80)
    ignited=(T_core>=T_ignition) and (fusion_to_cooling>=1) and (stability_index>=0.55)
    radius_change_fraction=0 if i==0 else abs((R_core[i]-R_core[i-1])/max(R_core[i],1e-30))
    stabilized=ignited and abs(gnet_over_gparent)<=0.02 and stability_index>=0.85 and fusion_to_cooling>=1 and radius_change_fraction<=0.02
    if coherence_step is None and field_coherent: coherence_step=i
    if ignition_step is None and ignited: ignition_step=i
    if stabilization_step is None and ignition_step is not None and i>ignition_step+10 and stabilized: stabilization_step=i
    state='parent_field_dominated'
    if field_coherent and not ignited: state='field_coherent_pre_ignition'
    if ignited and not stabilized: state='ignited_seeking_stabilization'
    if stabilized: state='ignited_stabilized'
    records.append(dict(step=i,tau=tau[i],C_phi_internal=C,A_opp=A_opp[i],R_phi_design=R_phi,gnet_over_gparent=gnet_over_gparent,g_net_m_s2=g_net_m_s2,field_coherence_index=stability_index,M_core_Msun=M_core/M_sun,R_core_AU=R_core[i]/AU,R_core_Rsun=R_core[i]/R_sun,T_core_K=T_core,fusion_to_cooling=fusion_to_cooling,field_coherent=field_coherent,ignited=ignited,stabilized=stabilized,state=state))
df=pd.DataFrame(records); df.to_csv(outdir/'design_mode_north_aligned_coherence_timeseries.csv',index=False)
print({'coherence_step':coherence_step,'ignition_step':ignition_step,'stabilization_step':stabilization_step})
for y,label in [('R_phi_design','R_phi'),('T_core_K','T_core'),('fusion_to_cooling','fusion_to_cooling')]:
    plt.figure(figsize=(8,5)); plt.plot(df['tau'],df[y]); plt.title(label); plt.xlabel('tau'); plt.tight_layout(); plt.savefig(outdir/f'{label}.png',dpi=160)
'''
(CODE / 'run_design_mode_north_aligned.py').write_text(run_design_script)

# Copy current build script itself
try:
    shutil.copy2(Path(__file__), CODE / 'build_preprint_pdf_and_package.py')
except Exception:
    pass

# Copy metadata scripts from v6 if exist
for d in [ROOT / 'rbfl_itpa_parent_field_pipeline_v6_jet_evidence_sweep/scripts', ROOT / 'rbfl_itpa_parent_field_pipeline_v5_vector_class_gate/scripts']:
    if d.exists():
        subdir = CODE / d.parent.name
        subdir.mkdir(exist_ok=True)
        for f in d.glob('*.py'):
            copy_if_exists(f, subdir)

(CODE / 'requirements.txt').write_text('pandas\nnumpy\nmatplotlib\nreportlab\nopenpyxl\nPillow\n')

README = '''# RBFL/RBFT Scaled Coherent Geometry Hypothesis - Reproducibility Package

This package accompanies the PDF preprint `RBFL_Scaled_Coherent_Geometry_Hypothesis_Preprint.pdf`.

## Status

Speculative hypothesis and toy-model demonstration. Not a reactor design, not a construction guide, not validated fusion physics, and not proof of RBFL/RBFT.

## Quick reproduction

```bash
python -m venv .venv
source .venv/bin/activate  # Windows: .venv\\Scripts\\activate
pip install -r code/requirements.txt
python code/run_design_mode_north_aligned.py
```

The script reproduces the design-mode north-aligned toy time series and simplified plots.

## Important mode distinction

Diagnostic mode requires verified machine orientation, vector class, transfer factors, and null controls. Design-mode proxy scans intentionally assign north alignment to test the design equations. The design-mode result cannot be used as evidence for historical JET orientation or any real device effect.

## Folder map

- `paper/`: final PDF.
- `figures/`: all figures embedded in the PDF.
- `data/`: processed CSVs and result tables.
- `source_docs/`: uploaded/source documents used for context. Third-party materials retain their own rights.
- `code/`: reproduction scripts and requirements.
- `docs/`: release notes and copyright/data-use notice.
'''
(DOCS / 'README.md').write_text(README)

copyright_notice = '''# Copyright, third-party data, and safety notice

Original report text, generated figures, and toy-model scaffolds are prepared for Levi S. Haye / RBFL Research Project, 2026. Review ownership, contributor, and institutional terms before public release.

Third-party data and documents - including ITPA/HDB5 materials, EFDA/ITER/JET materials, and public facility documents - retain their original copyright and data-use terms. Public redistribution should follow those terms.

This package is not a reactor design, not a construction guide, not a safety procedure, and not a recommendation to build any high-energy device. It is a conceptual and computational research package only.
'''
(DOCS / 'COPYRIGHT_AND_DATA_USE_NOTICE.md').write_text(copyright_notice)

# Package final PDF into paper folder and figure copies are already in FIG.
PAPER = OUT / 'paper'; PAPER.mkdir(exist_ok=True)
shutil.copy2(pdf_path, PAPER / pdf_path.name)

# Repro zip
zip_path = ROOT / 'RBFL_Scaled_Coherent_Geometry_Hypothesis_Reproducibility_Package.zip'
if zip_path.exists(): zip_path.unlink()
with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_DEFLATED) as z:
    for p in sorted(OUT.rglob('*')):
        if p.is_file():
            z.write(p, arcname=str(p.relative_to(OUT.parent)))
print(f'Package written: {zip_path}')
