"""
Paper 1 figure regeneration at FULL-RATE Mode B (0% drift, true price stability).
Clean consistent modern style. Mode A=-1.6% defl, Mode B=0% (price stable), Mode C=+2% infl.
"""
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import os
FIGDIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'figures')
os.makedirs(FIGDIR, exist_ok=True)

# ---- consistent style ----
plt.rcParams.update({
    "figure.dpi": 120,
    "font.size": 12,
    "axes.titlesize": 13,
    "axes.titleweight": "bold",
    "axes.spines.top": False,
    "axes.spines.right": False,
    "axes.grid": True,
    "grid.alpha": 0.25,
    "legend.frameon": True,
    "legend.framealpha": 0.9,
})
C = {"A":"#2563eb", "B":"#16a34a", "C":"#ea580c", "current":"#dc2626", "omega":"#7c3aed"}

# ---- core economic assumptions (full-rate) ----
R_NOM = 0.07            # nominal equity return
YEARS = 65
# Drifts
DRIFT = {"current":0.03, "C":0.02, "B":0.0, "A":-0.0165}
# M2 growth rates (full-rate Mode B ~2%; Mode A 0.35%; C 4.5%; current 6.5%)
M2G = {"current":0.065, "C":0.040, "B":0.020, "A":0.0035}

# Validated residual-calibration Stable Floor real figures (2025$, projected launch cohort)
# General-equilibrium realizable Stable Floor (2025$, projected launch cohort; Neo-Solon 2026e §6.7):
# A ~$233K, B ~$413K (60/40 split, 60% to floor), C ~$230K (shares A's K1/K2).
SF_REAL = {"A":0.233e6, "B":0.413e6, "C":0.230e6}   # GE realizable floors (2025$): A/C ~$230K, B ~$413K
MEDIAN_401K = 95e3

def cpi_path(drift, years=YEARS):
    return np.array([(1+drift)**t for t in range(years+1)])

def real_accum(target_real, years=YEARS):
    """Smooth monotone accumulation curve ending at target_real (2025$)."""
    t=np.arange(years+1)
    # exponential-ish growth shape normalized to hit target at 65
    shape=(np.exp(R_NOM*t)-1)/(np.exp(R_NOM*years)-1)
    return target_real*shape

# ============ FIGURE BUILDERS ============
def fig1_wealth_accumulation(path):
    """Paper 1 Figure 3 — nominal + real Stable Floor accumulation, A/B/C vs median 401(k)."""
    fig,(ax1,ax2)=plt.subplots(1,2,figsize=(15,6))
    fig.suptitle("Citizen Wealth Accumulation Over a Working Life\n"
                 "(Representative cohort born at framework launch; general-equilibrium realizable return)",
                 fontweight="bold")
    t=np.arange(YEARS+1)
    # NOMINAL: real * cpi
    for m in ["A","B","C"]:
        real=real_accum(SF_REAL[m])
        cpi=cpi_path(DRIFT[m])
        nominal=real*cpi
        ax1.plot(t,nominal/1e3,color=C[m],lw=2.5,
                 label=f"Mode {m} (nominal)")
    med401k_nom=real_accum(MEDIAN_401K)*cpi_path(DRIFT["current"])
    ax1.plot(t,med401k_nom/1e3,"--",color=C["current"],lw=2,label="Median US 401(k) — Vanguard 2025")
    ax1.set_title("Stable Floor — Nominal Balance")
    ax1.set_xlabel("Age (years from birth)"); ax1.set_ylabel("$K (nominal)")
    ax1.legend(); ax1.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x,_:f"${x:.0f}K"))
    # REAL
    labels={"A":"Mode A (real — ~$233K at 65)","B":"Mode B (real — ~$413K at 65)","C":"Mode C (real — ~$230K at 65)"}
    for m in ["A","B","C"]:
        real=real_accum(SF_REAL[m])
        ax2.plot(t,real/1e3,color=C[m],lw=2.5,label=labels[m])
    ax2.plot(t,real_accum(MEDIAN_401K)/1e3,":",color=C["current"],lw=2.2,label="Median US 401(k) (~$95K)")
    # endpoint annotations
    ax2.annotate("$233K",(YEARS,SF_REAL["A"]/1e3),color=C["A"],fontweight="bold",fontsize=11,xytext=(3,0),textcoords="offset points")
    ax2.annotate("$413K",(YEARS,SF_REAL["B"]/1e3),color=C["B"],fontweight="bold",fontsize=11,xytext=(3,-12),textcoords="offset points")
    ax2.annotate("$230K",(YEARS,SF_REAL["C"]/1e3),color=C["C"],fontweight="bold",fontsize=11,xytext=(3,0),textcoords="offset points")
    ax2.set_title("Stable Floor — Real (2025 Dollars)")
    ax2.set_xlabel("Age (years from birth)"); ax2.set_ylabel("$K (2025 dollars)")
    ax2.legend(loc="upper left"); ax2.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x,_:f"${x:.0f}K"))
    fig.tight_layout(rect=[0,0,1,0.94]); fig.savefig(path); plt.close(fig)

def fig2_purchasing_power(path):
    """Paper 1 Figure 2 — purchasing power of $1 over 65 years, by mode."""
    fig,ax=plt.subplots(figsize=(14,7))
    t=np.arange(YEARS+1)
    series=[("current","Current (3%/yr inflation)",C["current"]),
            ("C","Mode C (2%/yr inflation)",C["C"]),
            ("B","Mode B (0% drift — price stable)",C["B"]),
            ("A","Mode A (−1.65%/yr deflation)",C["A"])]
    end={}
    for key,lab,col in series:
        pp=1.0/cpi_path(DRIFT[key])
        end[key]=pp[-1]
        ax.plot(t,pp,color=col,lw=2.5,label=f"{lab} → ${pp[-1]:.2f}")
    ax.axhline(1.0,ls="--",color="gray",alpha=0.6)
    ax.fill_between(t,1.0,1.0/cpi_path(DRIFT["A"]),color=C["A"],alpha=0.08)
    ax.fill_between(t,1.0/cpi_path(DRIFT["current"]),1.0,color=C["current"],alpha=0.08)
    ax.set_title("Purchasing Power of $1 Over 65 Years\n(What does $1 buy over time, relative to the year of launch?)")
    ax.set_xlabel("Years from launch"); ax.set_ylabel("Real purchasing power ($1 = launch year)")
    ax.legend(loc="upper left")
    fig.text(0.5,-0.01,"Current system: ~85% purchasing power loss over 65 years at 3%/yr. "
             "Mode B holds the dollar stable; Mode A grows purchasing power for cash holders.",
             ha="center",fontsize=9,style="italic",color="gray")
    fig.tight_layout(); fig.savefig(path,bbox_inches="tight"); plt.close(fig)

def fig3_composite(path):
    """Paper 1 Figure 1 — 4-panel: price level, M2, Stable Floor, income at age 65."""
    fig,axes=plt.subplots(2,2,figsize=(15,11))
    fig.suptitle("Three Base Modes vs. Current System — 65-Year Trajectories",fontweight="bold",fontsize=15)
    t=np.arange(YEARS+1)
    # Panel 1: price level
    ax=axes[0,0]
    for key,lab in [("current","Current (~3%/yr)"),("C","Mode C (+2%/yr)"),("B","Mode B (0% — price stable)"),("A","Mode A (−1.65%/yr)")]:
        ax.plot(t,cpi_path(DRIFT[key]),color=C[key if key!='current' else 'current'],lw=2.3,label=lab)
    ax.axhline(1.0,ls="--",color="gray",alpha=0.5)
    ax.set_title("Price Level Over Time"); ax.set_xlabel("Years from launch"); ax.set_ylabel("CPI (1.0 = launch)"); ax.legend()
    # Panel 2: M2 log
    ax=axes[0,1]
    M2_0=22.4
    for key,lab in [("current","Current (6.5%/yr)"),("C","Mode C (4.5%/yr)"),("B","Mode B (2.0%/yr)"),("A","Mode A (0.35%/yr)")]:
        ax.plot(t,M2_0*(1+M2G[key])**t,color=C[key if key!='current' else 'current'],lw=2.3,label=lab)
    ax.set_yscale("log"); ax.set_title("Circulating Money Supply (log scale)")
    ax.set_xlabel("Years from launch"); ax.set_ylabel("M2 ($T, log scale)"); ax.legend()
    # Panel 3: stable floor real
    ax=axes[1,0]
    ax.plot(t,real_accum(SF_REAL["A"])/1e3,color=C["A"],lw=2.3,label="Mode A (~$233K real)")
    ax.plot(t,real_accum(SF_REAL["B"])/1e3,color=C["B"],lw=2.3,label="Mode B (~$413K real)")
    ax.plot(t,real_accum(SF_REAL["C"])/1e3,color=C["C"],lw=2.3,label="Mode C floor (~$230K)")
    ax.plot(t,real_accum(MEDIAN_401K)/1e3,":",color=C["current"],lw=2.2,label="Median US 401(k) (~$95K)")
    ax.set_title("Stable Floor Capital Stake (2025 Dollars)")
    ax.set_xlabel("Age (years from birth)"); ax.set_ylabel("$K (2025 dollars)")
    ax.legend(); ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x,_:f"${x:.0f}K"))
    # Panel 4: income at 65 stacked
    ax=axes[1,1]
    modes=["Current\n(median)","Mode A","Mode B","Mode C"]
    floors_k=[MEDIAN_401K/1e3,SF_REAL["A"]/1e3,SF_REAL["B"]/1e3,SF_REAL["C"]/1e3]   # 95,152,737,150
    ss=[22,22,22,22]
    cap=[round(0.05*f,1) for f in floors_k]                       # 5% withdrawal cap (paper convention)
    defl=[0,round(abs(DRIFT["A"])*SF_REAL["A"]/1e3,1),0,0]        # Mode A: ~1.65%/yr real money appreciation
    ki=[0,0,0,2.4]                                                # Mode C: ongoing KI dividend ~$199/mo
    x=np.arange(4); ss_a,cap_a,defl_a=np.array(ss),np.array(cap),np.array(defl)
    ax.bar(x,ss,color="#9ca3af",label="Social Security (~$22K)")
    ax.bar(x,cap,bottom=ss,color=[C["current"],C["A"],C["B"],C["C"]],label="Capital stake income (5%)")
    ax.bar(x,defl,bottom=ss_a+cap_a,color=C["A"],hatch="\\\\",alpha=0.55,label="Mode A deflationary gain (~1.65%/yr real)")
    ax.bar(x,ki,bottom=ss_a+cap_a+defl_a,color=C["C"],hatch="//",alpha=0.7,label="Ongoing KI dividend (Mode C)")
    totals=[s+c+d+k for s,c,d,k in zip(ss,cap,defl,ki)]
    for i,tot in enumerate(totals):
        ax.annotate(f"${tot:.0f}K",(i,tot),ha="center",va="bottom",fontweight="bold",fontsize=10)
    ax.text(1,totals[1]+5.5,"cash & savings appreciate\n~2.95\u00d7 in real terms / lifetime",
            ha="center",va="bottom",fontsize=7.3,style="italic",color=C["A"])
    ax.set_ylim(0,72)
    ax.set_xticks(x); ax.set_xticklabels(modes)
    ax.set_title("Annual Income at Age 65"); ax.set_ylabel("$K/year (2025 dollars)")
    ax.legend(fontsize=8,loc="upper left"); ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda v,_:f"${v:.0f}K"))
    fig.tight_layout(rect=[0,0,1,0.96]); fig.savefig(path); plt.close(fig)

# Mode Omega environment (shared across fig5/6) — illustrative 65-yr path
def omega_inflation_path():
    """Mode Omega effective inflation. The 60%-capture base is mildly
    deflationary (-0.80%/yr). Governors ADD issuance during demographic
    stress / productivity boom, REDUCING the deflation toward 0%
    (grounded anchors: -0.80% normal, -0.44% under demographic stress,
    -0.08% at the productivity-boom peak)."""
    t=np.arange(YEARS+1)
    base=-0.0080
    infl=np.full(YEARS+1, base)
    for i,yr in enumerate(t):
        if yr<=1:
            infl[i]=base
        elif 2<=yr<30:
            infl[i]=base + 0.0010*(1-np.exp(-(yr-1)/8))   # mild standing demographic governor -> ~-0.70%
        elif 30<=yr<38:
            infl[i]=-0.0070 + 0.0062*(yr-30)/8            # productivity boom ramps: -0.70% -> -0.08%
        elif 38<=yr<=42:
            infl[i]=-0.0008                               # demo stress + boom peak: ~-0.08% (near zero)
        elif 43<=yr<=45:
            infl[i]=-0.0008 - 0.0036*(yr-42)/3            # easing back: -0.08% -> -0.44%
        elif 46<=yr<=55:
            infl[i]=-0.0044                               # demographic governor active: -0.44%
        else:
            infl[i]=-0.0060                               # reverting toward the -0.80% base
    return t,infl

def fig5_omega_zoom(path):
    t,infl=omega_inflation_path()
    fig,ax=plt.subplots(figsize=(15,7))
    ax.axhline(0.0,ls="--",color=C["B"],lw=2.5,label="Mode B (0% drift) \u2014 price-stable baseline")
    ax.axhline(-0.0080,ls=":",color=C["omega"],lw=1.8,alpha=0.7,label="Mode \u03a9 base (\u22120.80%) \u2014 governors inactive")
    ax.axhline(-0.0165,ls=":",color=C["A"],lw=1.8,label="Mode A (\u22121.65%) \u2014 for reference")
    ax.plot(t,infl,color=C["omega"],lw=3,label="Mode \u03a9 (adaptive band)")
    ax.fill_between(t,0.0,infl,color=C["omega"],alpha=0.12)
    ax.axvspan(30,38,color="#f59e0b",alpha=0.08); ax.axvspan(38,46,color=C["omega"],alpha=0.06); ax.axvspan(48,55,color="#3b82f6",alpha=0.07)
    peak=infl.max()
    ax.annotate(f"Least deflation: {peak*100:+.2f}%\n(governors at max)",(int(np.argmax(infl)),peak),color=C["omega"],fontweight="bold",
                xytext=(-30,28),textcoords="offset points",arrowprops=dict(arrowstyle="->",color=C["omega"]))
    ax.text(34,-0.0030,"Productivity\nboom",color="#b45309",ha="center",fontsize=10,style="italic")
    ax.text(42,-0.0030,"Demo stress",color=C["omega"],ha="center",fontsize=10,style="italic")
    ax.text(51,-0.0055,"Demographic\ngovernor active",color="#1d4ed8",ha="center",fontsize=10,style="italic")
    ax.set_ylim(-0.018,0.004)
    ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda v,_:f"{v*100:.1f}%"))
    ax.set_title("Mode \u03a9 Effective Inflation Rate \u2014 Zoomed View\n(Mildly deflationary base; governors ease the deflation toward 0% under stress)")
    ax.set_xlabel("Years from launch"); ax.set_ylabel("Annual inflation rate (%)")
    ax.legend(loc="lower right")
    fig.tight_layout(); fig.savefig(path); plt.close(fig)

def fig6_omega_deviation(path):
    t,infl=omega_inflation_path()
    gap=infl-0.0  # Mode Omega relative to Mode B (0%); negative = below Mode B (deflationary)
    fig,ax=plt.subplots(figsize=(15,6))
    ax.axhline(0.0,ls="--",color=C["B"],lw=2.5,label="Mode B baseline (0%)")
    ax.axhline(-0.0080,ls=":",color=C["omega"],lw=1.5,alpha=0.6,label="Mode \u03a9 base gap (\u22120.80%)")
    ax.plot(t,gap,color=C["omega"],lw=3,label="Mode \u03a9 below Mode B (governors narrow the gap)")
    ax.fill_between(t,0,gap,color=C["omega"],alpha=0.15)
    ax.axvspan(30,38,color="#f59e0b",alpha=0.08); ax.axvspan(38,47,color=C["omega"],alpha=0.06); ax.axvspan(48,55,color="#3b82f6",alpha=0.07)
    ax.text(34,-0.0020,"Productivity\nboom",color="#b45309",ha="center",fontsize=10,style="italic")
    ax.text(42,-0.0020,"Demo stress",color=C["omega"],ha="center",fontsize=10,style="italic")
    ax.text(51,-0.0052,"Demographic\ngovernor active",color="#1d4ed8",ha="center",fontsize=10,style="italic")
    ax.set_ylim(-0.010,0.002)
    ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda v,_:f"{v*100:+.2f}%"))
    ax.set_title("Mode \u03a9 Inflation Gap Below the Mode B Baseline (Governors Narrow the Gap Under Stress)")
    ax.set_xlabel("Years from launch"); ax.set_ylabel("Gap vs. Mode B (%)")
    ax.legend(loc="lower right")
    fig.text(0.5,-0.02,f"Smallest gap: ~{gap.max()*100:+.2f}% (governors at maximum, near price stability); base gap \u22120.80% (mild deflation). "
             "Mode \u03a9 reverts toward its \u22120.80% deflationary base once conditions normalize.",
             ha="center",fontsize=9,style="italic",color="gray")
    fig.tight_layout(); fig.savefig(path,bbox_inches="tight"); plt.close(fig)

def fig7_omega_scenarios(path):
    # Mode Omega floors are the published Table 8 values (Section 8.6; macro model Neo-Solon 2026e
    # Section 6.7 supplies the general-equilibrium realizable return per scenario, including the
    # capture->return feedback that the simple replication engine does not solve). Mode A and Mode B
    # central-case floors ($233K, $413K) are shown for reference.
    scen=["Normal\n(+0.3% pop)","Neg pop\n(−0.5%)","Neg pop +\noptimistic","Neg pop +\npessimistic","Prod boom +\nneg pop"]
    Om=[403,480,638,343,541]               # Table 8 (2025$K)
    A_REF, B_REF = 233, 413                 # Mode A / Mode B central-case floors (2025$K)
    x=np.arange(5)
    fig,ax1=plt.subplots(1,1,figsize=(13,7))
    fig.suptitle("Mode Ω Stable Floor Across the Five Table 8 Scenarios\n"
                 "(general-equilibrium realizable basis; Mode A and Mode B central-case floors for reference)",
                 fontweight="bold")
    bars=ax1.bar(x,Om,0.62,color=C["omega"],label="Mode Ω (Table 8)")
    for i,v in enumerate(Om):
        ax1.annotate(f"${v}K",(i,v),ha="center",va="bottom",fontsize=11,color=C["omega"],fontweight="bold")
    ax1.axhline(B_REF,ls="--",color=C["B"],lw=2,label=f"Mode B central (~${B_REF}K)")
    ax1.axhline(A_REF,ls=":",color=C["A"],lw=2,label=f"Mode A central (~${A_REF}K)")
    ax1.set_xticks(x); ax1.set_xticklabels(scen,fontsize=10)
    ax1.set_title("Stable Floor Capital Stake at Age 65 (2025 Dollars)")
    ax1.set_ylabel("$K (2025 dollars)"); ax1.legend(loc="upper left")
    ax1.yaxis.set_major_formatter(mticker.FuncFormatter(lambda v,_:f"${v:.0f}K"))
    ax1.set_ylim(0, max(Om)*1.15)
    fig.text(0.5,-0.02,"Mode \u03a9 runs a 60-percent base capture with adaptive demographic and productivity governors. "
             "At the central return it sits just below fixed-capture Mode B; at the low end of the return band the "
             "demographic governor outweighs Mode B (Mode \u03a9 leads Mode B's roughly $277K there), and it leads most "
             "clearly when the productivity governor also engages.",
             ha="center",fontsize=9,style="italic",color="gray")
    fig.tight_layout(rect=[0,0.02,1,0.93]); fig.savefig(path,bbox_inches="tight"); plt.close(fig)

def fig8_transitions(path):
    fig,axes=plt.subplots(2,2,figsize=(15,12))
    fig.suptitle("Constitutional Mode Transitions at Year 30 — 65-Year Horizon",fontweight="bold",fontsize=15)
    t=np.arange(YEARS+1)
    # Panel 1: A/B -> transition at yr 30
    ax=axes[0,0]
    cpiA=cpi_path(DRIFT["A"]); cpiB=cpi_path(DRIFT["B"])
    ax.plot(t,cpiA,color=C["A"],lw=2.5,label="Pure Mode A")
    # B is flat at 1.0 now
    def transit(base_drift,new_drift,switch=30):
        p=np.ones(YEARS+1)
        for i in range(1,YEARS+1):
            d=base_drift if i<=switch else new_drift
            p[i]=p[i-1]*(1+d)
        return p
    ax.plot(t,transit(DRIFT["A"],DRIFT["B"]),"--",color=C["A"],alpha=0.8,label="A→B@30")
    ax.plot(t,transit(DRIFT["A"],DRIFT["C"]),"--",color=C["C"],alpha=0.8,label="A→C@30")
    ax.plot(t,transit(DRIFT["B"],DRIFT["A"]),":",color=C["B"],lw=2,label="B→A@30")
    ax.plot(t,transit(DRIFT["B"],DRIFT["C"]),":",color=C["C"],lw=2,label="B→C@30")
    ax.axvline(30,ls=":",color="gray",alpha=0.6)
    ax.set_title("Price Level: A/B → Transition at Yr 30"); ax.set_xlabel("Years"); ax.set_ylabel("CPI (1.0 = launch)"); ax.legend(fontsize=8)
    # Panel 2: C transitions
    ax=axes[0,1]
    ax.plot(t,cpi_path(DRIFT["C"]),color=C["C"],lw=2.5,label="Pure Mode C")
    ax.plot(t,transit(DRIFT["C"],DRIFT["A"]),"-.",color=C["A"],label="C→A@30")
    ax.plot(t,transit(DRIFT["C"],DRIFT["B"]),"-.",color=C["B"],label="C→B@30")
    ax.axvline(30,ls=":",color="gray",alpha=0.6)
    ax.set_title("Price Level: C → Transition at Yr 30"); ax.set_xlabel("Years"); ax.set_ylabel("CPI (1.0 = launch)"); ax.legend(fontsize=8)
    # Panel 3: K3 activate/deactivate
    ax=axes[1,0]
    # Mode C citizen dividend (KI), flat ~$199 at launch scaling toward ~$210; not the old 173->280 ramp.
    k3_ac=np.where(t>=30,210,0.0)
    ax.plot(t,k3_ac,color=C["C"],lw=2.5,label="A→C: KI dividend activates (~$199–210/mo)")
    ax.plot(t,np.where(t<30,210,0.0),"--",color=C["A"],lw=2,label="C→A: dividend ends")
    ax.axvline(30,ls=":",color="gray",alpha=0.6)
    ax.set_title("K3 Channel Activates / Deactivates Cleanly"); ax.set_xlabel("Years"); ax.set_ylabel("$/month per citizen"); ax.legend(fontsize=9)
    # Panel 4: lifetime real value by scenario
    ax=axes[1,1]
    labels=["Pure A","Pure B","Pure C","A→B@30","A→C@30","B→C@30","C→A@30","C→B@30"]
    # Lifetime real captured value (locked floor + cumulative K3 dividend + cumulative KI),
    # general-equilibrium realizable basis (A/C er 5.4%, B er 4.3%, Mode B 60/40 floor share;
    # mode switch at age 30). Pure A floor $235K; Pure B floor $420K + K3 $57K = $477K;
    # Pure C floor $232K + KI $262K = $494K. Computed by switch_calc.py on the GE engine.
    vals=[235,477,494,298,411,664,322,383]
    cols=[C["A"],C["B"],C["C"],C["A"],C["C"],C["C"],C["A"],C["B"]]
    ax.bar(range(8),vals,color=cols)
    ax.set_xticks(range(8)); ax.set_xticklabels(labels,rotation=30,ha="right",fontsize=9)
    ax.set_title("Lifetime Real Value by Transition Scenario"); ax.set_ylabel("$K (2025 dollars)")
    ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda v,_:f"${v:.0f}K"))
    fig.tight_layout(rect=[0,0,1,0.96]); fig.savefig(path); plt.close(fig)

# ============ BUILD ALL FIGURES ============
# name -> (builder, Paper 1 reference)
FIGURES = [
    (fig3_composite,           "modes_vs_current.png"),          # Paper 1, Figure 1
    (fig2_purchasing_power,    "purchasing_power.png"),          # Paper 1, Figure 2
    (fig1_wealth_accumulation, "stable_floor_accumulation.png"), # Paper 1, Figure 3
    (fig5_omega_zoom,          "mode_omega_inflation.png"),      # Paper 1, Figure 7a
    (fig6_omega_deviation,     "mode_omega_deviation.png"),      # Paper 1, Figure 7b
    (fig7_omega_scenarios,     "mode_omega_scenarios.png"),      # Paper 1, Tables 7/8
    (fig8_transitions,         "mode_transitions.png"),          # Paper 1, Appendix A.1
]

if __name__ == "__main__":
    for builder, name in FIGURES:
        path = os.path.join(FIGDIR, name)
        builder(path)
        print("wrote", os.path.relpath(path, os.path.dirname(__file__)))
