deviant/scripts/sim-dra-controller.mjs
Local Dev 631541c98c docs(reorg): regroup native-ASM specs under ASM_Native + move Pari_P2P into Betting
Four moves (git mv, history preserved on all 39 files):
- ASM_Potidaea_OrbitalSwap -> ASM_Native/Potidaea_OrbitalSwap  (renamed, dropped the ASM_ prefix; now nested under ASM_Native)
- MBA_Shares               -> ASM_Native/MBA_Shares
- FIATm                    -> ASM_Native/FIATm
- Pari_P2P                 -> Betting/Pari_P2P

ASM_Native now groups the native-BCH swap (Potidaea) with the native asset families it swaps (FIATμ/BCHm, MBA/shares). Rewrote ASM_Native/README.md from a pointer stub into a real folder index describing the contained subfolders + how they fit (mechanism vs assets).

Rewrote every reference: Potidaea internal links (depth +1; self-folder refs collapsed), external links from DEX_Anthemus/wiki/ASM_CrossChain, prose/path refs across Parameters + wiki + CLAUDE.md + docs + code-comment doc-pointers (scripts/src/contracts), memory, and .gitignore (transcripts ignore rule). Co-moved siblings (FIATm<->MBA_Shares) kept their ../sibling links untouched since they stayed siblings under ASM_Native.

Verified with a link-resolver: all 453 relative links across 165 files resolve.
Snapshots keep old paths (historical changelog). Parallel FX-desk/uniform-reserve track left untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 17:09:40 +02:00

542 lines
26 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* sim-dra-controller.mjs — simulator for the reflective Dynamic Rate Asset (DRA).
*
* Implements the asymmetric three-force model defined in
* Parameters/ASM_Native/FIATm/dra_dynamic-rate-asset_FOR-design-record.md
*
* Three forces, all biased toward the §6 maturity ratio of 1 DRA = €357:
* 1. Fee accrual — always positive (from FIATm-family transactions)
* 2. Reflective rate — asymmetric (full positive on EUR weakness,
* dampened negative on EUR strength)
* 3. Catalyst counter — strengthens negative-side dampening with adoption
*
* Stage-1 tests (per §9 of design record):
* T1 Fee accrual lift at varied FIATm-family volumes (low/medium/high)
* T2 Reflective asymmetric response under sustained EUR cycles
* T3 Catalyst convergence — β sweep, which β reaches A=30 + maturity envelope?
* T4 Adversarial trajectory — early sustained EUR strengthening
* T5 Vintage analysis — fairness across mint-vintage cohorts
*
* Run: node scripts/sim-dra-controller.mjs
*/
// ── Genesis parameters ──────────────────────────────────────────────────
const BCH_EUR_GENESIS = 200; // BCH = €200 at launch
const EUR_RATE_0 = Math.round(1e8 / BCH_EUR_GENESIS); // sats per EURm at launch = 500,000
const LAUNCH_RATIO = 357; // 1 EUR = 357 DRA (1998 ERM-entry parity)
const DRA_RATE_0 = Math.round(EUR_RATE_0 / LAUNCH_RATIO); // sats per DRA at launch = 1,401
// ── Reflective formula parameters ───────────────────────────────────────
const ALPHA = 1.0; // base reflection amplitude (full linear mirror)
const BETA = 0.10; // per-catalyst counter-strengthening (negative-side dampening)
const GAMMA = 0.05; // per-catalyst gain (positive-side acceleration) — NEW
// ── Amplitude clamp (adoption-scaled, asymmetric widening) ──────────────
const LOWER_CLAMP_0 = 0.5; // [0.5×, 2.0×] at launch
const UPPER_CLAMP_0 = 2.0;
const LOWER_WIDEN_PER_CAT = 0.015; // lower bound drops 1.5% baseline per catalyst
const UPPER_WIDEN_PER_CAT = 0.10; // upper bound rises 10% baseline per catalyst
const LOWER_FLOOR = 0.05; // never below 5% of baseline
// ── Catalyst trigger ─────────────────────────────────────────────────────
const CATALYST_TRIGGER = 210_000; // matches §12a.2 (FIATm controller)
const MAX_CATALYSTS = 30; // hard cap (asymptote — matches design's A=30 target)
// ── Fee schedule (chipnet-tunable) ──────────────────────────────────────
const FEE_MINT_REDEEM = 0.0005; // 0.05% on FIATm mint/redeem
const FEE_SWAP = 0.0010; // 0.10% on FIATm-to-FIATm swap
// ── Maturity target ─────────────────────────────────────────────────────
const MATURITY_EUR_PER_DRA = 357; // 1 DRA = €357 at maturity (opposite reverse)
// ────────────────────────────────────────────────────────────────────────
// DRA controller — the shared-pool state machine
// ────────────────────────────────────────────────────────────────────────
class DraController {
constructor(beta = BETA) {
this.beta = beta;
this.pool_sats = 0; // BCH in pool (sats)
this.supply = 0; // DRA units issued
this.cumulative_mints = 0; // for catalyst trigger
this.catalysts = 0;
this.eur_rate_history = []; // for time-averaging
this.last_dra_rate = DRA_RATE_0;
}
// EUR_rate moving average — window lengthens with adoption
averagingWindowHours() {
if (this.catalysts === 0) return 6;
if (this.catalysts < 10) return 6 + (this.catalysts * 1.8); // 6h → 24h
if (this.catalysts < 30) return 24 + ((this.catalysts - 10) * 7.2); // 24h → 168h (7d)
return 168; // frozen 7-day reference at maturity
}
// Push EUR_rate observation; return time-averaged reference
observeEurRate(eur_rate_spot, hours_since_genesis) {
this.eur_rate_history.push({ t: hours_since_genesis, rate: eur_rate_spot });
// Drop observations older than averaging window
const window = this.averagingWindowHours();
const cutoff = hours_since_genesis - window;
this.eur_rate_history = this.eur_rate_history.filter(o => o.t >= cutoff);
const sum = this.eur_rate_history.reduce((s, o) => s + o.rate, 0);
return sum / this.eur_rate_history.length;
}
// counter(r, A) = max(r × (1 + A × β), 1) — dampens negative side
counter(r) {
if (r <= 1) return 1;
return r * (1 + this.catalysts * this.beta);
}
// gain(A) = 1 + A × γ — amplifies positive side (NEW per user revision 2026-06-28)
gain() {
return 1 + this.catalysts * GAMMA;
}
// Amplitude clamp bounds (adoption-scaled, asymmetric)
clampBounds() {
const lower = Math.max(LOWER_CLAMP_0 - this.catalysts * LOWER_WIDEN_PER_CAT, LOWER_FLOOR);
const upper = UPPER_CLAMP_0 + this.catalysts * UPPER_WIDEN_PER_CAT;
return { lower, upper };
}
// Compute current DRA mint rate given a (time-averaged) EUR rate.
//
// Doubly asymmetric:
// r > 1 (EUR strengthens vs BCH) → counter dampens negative response
// r ≤ 1 (EUR weakens vs BCH) → gain amplifies positive response
draRate(eur_rate_averaged) {
const r = eur_rate_averaged / EUR_RATE_0;
const delta = eur_rate_averaged - EUR_RATE_0;
let raw;
if (r > 1) {
// Negative side — dampened
const c = this.counter(r);
raw = DRA_RATE_0 - ALPHA * delta / c;
} else {
// Positive side — amplified
const g = this.gain();
raw = DRA_RATE_0 - ALPHA * delta * g;
}
const { lower, upper } = this.clampBounds();
const min_rate = DRA_RATE_0 * lower;
const max_rate = DRA_RATE_0 * upper;
const clamped = Math.max(min_rate, Math.min(raw, max_rate));
this.last_dra_rate = clamped;
return clamped;
}
// Mint N DRA at max(reflective DRA_rate, current NAV)
//
// The max-of-two pricing is structural: new mints cannot enter below NAV
// (no dilution of existing holders), and during EUR-weakening events the
// reflective rate exceeds NAV → new mints pay a PREMIUM that lifts NAV
// for all holders. This is the mechanism by which catalyst-driven gain
// (γ) actually accretes per-DRA NAV over the adoption arc.
mint(n_dra, dra_rate) {
const nav = this.navPerDra();
const mint_price = Math.max(dra_rate, nav);
const sats_in = Math.round(n_dra * mint_price);
this.pool_sats += sats_in;
this.supply += n_dra;
this.cumulative_mints += n_dra;
// Catalyst trigger — capped at MAX_CATALYSTS asymptote
const expected = Math.min(
Math.floor(this.cumulative_mints / CATALYST_TRIGGER),
MAX_CATALYSTS
);
while (expected > this.catalysts) {
this.catalysts += 1;
}
return sats_in;
}
// Add fees (no supply change — pure NAV lift)
receiveFees(sats) {
this.pool_sats += sats;
}
// Redeem N DRA at current NAV
redeem(n_dra) {
const nav = this.navPerDra();
const sats_out = Math.round(n_dra * nav);
this.pool_sats -= sats_out;
this.supply -= n_dra;
return sats_out;
}
navPerDra() {
return this.supply > 0 ? this.pool_sats / this.supply : DRA_RATE_0;
}
navInEur(bch_eur_spot) {
return this.navPerDra() * bch_eur_spot / 1e8;
}
}
// ────────────────────────────────────────────────────────────────────────
// Trajectory generators — BCH/EUR over time
// ────────────────────────────────────────────────────────────────────────
const trajectories = {
calm: (hours) => BCH_EUR_GENESIS, // flat
eur_weakens_2x: (hours, total) => BCH_EUR_GENESIS * (1 + hours / total),
eur_strengthens_50: (hours, total) => BCH_EUR_GENESIS * (1 - 0.5 * hours / total),
eur_strengthens_then_recovers: (hours, total) => {
const mid = total / 2;
if (hours < mid) return BCH_EUR_GENESIS * (1 - 0.5 * hours / mid); // drop to €100
return BCH_EUR_GENESIS * 0.5 + BCH_EUR_GENESIS * 1.5 * (hours - mid) / mid; // €100 → €400
},
oscillating: (hours, total) => {
const cycles = 4;
return BCH_EUR_GENESIS * (1 + 0.5 * Math.sin(2 * Math.PI * cycles * hours / total));
},
sustained_debasement: (hours, total) => BCH_EUR_GENESIS * Math.pow(5, hours / total), // BCH €200 → €1000
adversarial_early_strengthen: (hours, total) => {
const phase = total * 0.3;
if (hours < phase) return BCH_EUR_GENESIS * (1 - 0.7 * hours / phase); // drop 70% fast
return BCH_EUR_GENESIS * 0.3 * Math.pow(10, (hours - phase) / (total - phase)); // recover slowly
},
};
// ────────────────────────────────────────────────────────────────────────
// Adoption profile — how the FIATm family grows over time
// ────────────────────────────────────────────────────────────────────────
const adoptionProfiles = {
low: { mint_per_day: 2_000, tx_per_day: 50_000, notional_per_tx_eur: 50 },
med: { mint_per_day: 20_000, tx_per_day: 500_000, notional_per_tx_eur: 50 },
high:{ mint_per_day: 200_000, tx_per_day: 5_000_000, notional_per_tx_eur: 50 },
};
// Per-currency profiles for T8 multi-currency competition test.
//
// All FIATm currencies feed fees into the SAME DRA pool (architectural intent —
// the DraFee variant is the mainnet default for the whole family, not an
// opt-in variant). EUR is conspicuously NOT in this list — DRA's reflective
// rate is anchored to the EUR oracle, so EUR is the structural counterparty
// rather than a peer feeder currency.
//
// Volumes are MODERATED (~1/10 of original calibration) to keep the 5y
// multi-currency outcome approaching — not blowing past — the €357 maturity
// envelope. Realistic chipnet/mainnet calibration TBD; these numbers are
// plausible adoption assumptions, not commitments.
const currencyProfiles = {
USDm: { mint_per_day: 10_000, tx_per_day: 250_000, notional_per_tx_eur: 60 },
GBPm: { mint_per_day: 2_500, tx_per_day: 50_000, notional_per_tx_eur: 55 },
CHFm: { mint_per_day: 800, tx_per_day: 12_000, notional_per_tx_eur: 80 },
TRYm: { mint_per_day: 1_500, tx_per_day: 35_000, notional_per_tx_eur: 25 },
HUFm: { mint_per_day: 300, tx_per_day: 6_000, notional_per_tx_eur: 35 },
ILSm: { mint_per_day: 1_000, tx_per_day: 20_000, notional_per_tx_eur: 50 },
};
// ────────────────────────────────────────────────────────────────────────
// Single simulation run
// ────────────────────────────────────────────────────────────────────────
function runSim({
trajectoryName,
adoptionName,
total_days,
beta = BETA,
seed_mint_dra = 10_000, // genesis seed mint
verbose = false,
}) {
const ctrl = new DraController(beta);
const trajectory_fn = trajectories[trajectoryName];
const adoption = adoptionProfiles[adoptionName];
const total_hours = total_days * 24;
// Genesis seed mint
ctrl.mint(seed_mint_dra, DRA_RATE_0);
const samples = [];
const hours_per_day = 24;
const step_hours = 24; // daily granularity
for (let day = 0; day < total_days; day++) {
const hours = day * hours_per_day;
const bch_eur = trajectory_fn(hours, total_hours);
const eur_rate_spot = 1e8 / bch_eur;
const eur_rate_avg = ctrl.observeEurRate(eur_rate_spot, hours);
const dra_rate = ctrl.draRate(eur_rate_avg);
// Mints today (linear adoption)
const daily_mints = adoption.mint_per_day;
ctrl.mint(daily_mints, dra_rate);
// Fees today (from FIATm-family transactions)
// Daily FIATm tx notional in EUR → in sats:
const daily_notional_eur = adoption.tx_per_day * adoption.notional_per_tx_eur;
const daily_notional_sats = (daily_notional_eur / bch_eur) * 1e8;
// Assume 80% of tx are swaps, 20% are mint/redeem (typical pattern)
const fees_sats = Math.round(
0.8 * daily_notional_sats * FEE_SWAP +
0.2 * daily_notional_sats * FEE_MINT_REDEEM
);
ctrl.receiveFees(fees_sats);
// Sample every 30 days
if (day % 30 === 0 || day === total_days - 1) {
samples.push({
day,
bch_eur: Math.round(bch_eur),
eur_rate: Math.round(eur_rate_spot),
dra_rate: Math.round(dra_rate),
catalysts: ctrl.catalysts,
supply: ctrl.supply,
pool_sats: ctrl.pool_sats,
nav_per_dra_sats: Math.round(ctrl.navPerDra()),
nav_per_dra_eur: +ctrl.navInEur(bch_eur).toFixed(4),
});
}
}
return {
trajectoryName, adoptionName, total_days, beta,
final: samples[samples.length - 1],
samples,
maturityHit: samples[samples.length - 1].nav_per_dra_eur >= MATURITY_EUR_PER_DRA,
catalystsHit30: ctrl.catalysts >= MAX_CATALYSTS,
};
}
// ────────────────────────────────────────────────────────────────────────
// Stage-1 tests
// ────────────────────────────────────────────────────────────────────────
function fmt(n) {
if (n >= 1e9) return (n / 1e9).toFixed(2) + 'B';
if (n >= 1e6) return (n / 1e6).toFixed(2) + 'M';
if (n >= 1e3) return (n / 1e3).toFixed(2) + 'k';
return String(Math.round(n));
}
function printResult(label, r) {
const verdict = r.maturityHit ? '✓ MATURITY HIT' : '✗ short of maturity';
const catVerdict = r.catalystsHit30 ? '✓ A≥30' : `(A=${r.final.catalysts})`;
console.log(` ${label.padEnd(35)} → NAV €${String(r.final.nav_per_dra_eur).padStart(8)} | supply ${fmt(r.final.supply).padStart(8)} | catalysts ${String(r.final.catalysts).padStart(3)} | ${verdict} ${catVerdict}`);
}
function T1_fee_accrual_lift() {
console.log('\n=== T1 — Fee accrual lift at varied FIATm-family volumes (24-month calm trajectory) ===');
for (const adopt of ['low', 'med', 'high']) {
const r = runSim({ trajectoryName: 'calm', adoptionName: adopt, total_days: 730 });
printResult(`adoption=${adopt}`, r);
}
}
function T2_reflective_asymmetric_response() {
console.log('\n=== T2 — Reflective asymmetric response (5-year trajectories, medium adoption) ===');
const trajs = ['calm', 'eur_weakens_2x', 'eur_strengthens_50', 'oscillating', 'sustained_debasement'];
for (const traj of trajs) {
const r = runSim({ trajectoryName: traj, adoptionName: 'med', total_days: 1825 });
printResult(`trajectory=${traj}`, r);
}
}
function T3_catalyst_convergence_beta_sweep() {
console.log('\n=== T3 — β sweep on EUR-STRENGTHENING trajectory (where counter activates) — medium adoption, 5y ===');
for (const beta of [0.05, 0.10, 0.15, 0.20, 0.30]) {
const r = runSim({ trajectoryName: 'eur_strengthens_50', adoptionName: 'med', total_days: 1825, beta });
printResult(`β=${beta.toFixed(2)}`, r);
}
}
function T6_long_horizon_trajectory() {
console.log('\n=== T6 — Long-horizon trajectory (15y sustained debasement, medium adoption — multi-decade target) ===');
const r = runSim({ trajectoryName: 'sustained_debasement', adoptionName: 'med', total_days: 5475 });
printResult('15y med-debasement', r);
console.log(' Yearly NAV trajectory:');
for (let i = 0; i < r.samples.length; i += 12) {
const s = r.samples[i];
const year = (s.day / 365).toFixed(1);
console.log(` year ${String(year).padStart(5)}: BCH €${String(s.bch_eur).padStart(7)} | NAV €${String(s.nav_per_dra_eur).padStart(10)} | A=${String(s.catalysts).padStart(3)} | supply ${fmt(s.supply).padStart(8)}`);
}
}
function T7_three_horizons() {
console.log('\n=== T7 — Three-horizon comparison (calm baseline, medium adoption — long-arc design intent) ===');
for (const days of [1825, 3650, 5475, 10950, 18250]) {
const r = runSim({ trajectoryName: 'sustained_debasement', adoptionName: 'med', total_days: days });
printResult(`${(days / 365).toFixed(0)}y sustained debasement`, r);
}
}
// T8 — Multi-currency competition.
//
// Six FIATm currencies (USDm/GBPm/CHFm/TRYm/HUFm/ILSm) feed fees into ONE
// shared DRA pool simultaneously. Compares per-DRA NAV against the single-
// currency baseline at the same horizon. The structural question: does the
// combined family fee inflow accelerate DRA's trajectory to maturity?
//
// Each currency's mint contribution rises through the DRA catalyst counter
// (catalysts trigger on cumulative DRA mints, not per-currency mints).
// All currencies share the same EUR_rate reference (DRA's reflective anchor).
function T8_multicurrency_competition() {
console.log('\n=== T8 — Multi-currency competition (USDm/GBPm/CHFm/TRYm/HUFm/ILSm feed one DRA pool, 5y debasement) ===');
const ctrl = new DraController();
ctrl.mint(10_000, DRA_RATE_0);
const total_days = 1825;
const total_hours = total_days * 24;
const traj = trajectories.sustained_debasement;
const currencies = Object.entries(currencyProfiles);
// Per-currency fee tally (for reporting attribution)
const currencyFeeTally = Object.fromEntries(currencies.map(([code]) => [code, 0]));
let totalFeesIn = 0;
for (let day = 0; day < total_days; day++) {
const hours = day * 24;
const bch_eur = traj(hours, total_hours);
const eur_rate_spot = 1e8 / bch_eur;
const eur_rate_avg = ctrl.observeEurRate(eur_rate_spot, hours);
const dra_rate = ctrl.draRate(eur_rate_avg);
// Each currency contributes its own daily mint volume (acts as DRA-pool
// pressure via cumulative mints driving catalysts) and its own fee stream.
for (const [code, profile] of currencies) {
// Treat each currency's "mints" as contributing to DRA pool growth via
// their daily fees (the actual DRA mints are tracked separately for
// catalyst calculation, but in this sim we approximate that the DRA
// catalysts also fire from family-wide volume).
const daily_notional_sats = (profile.tx_per_day * profile.notional_per_tx_eur / bch_eur) * 1e8;
const fee_sats = Math.round(
0.8 * daily_notional_sats * FEE_SWAP +
0.2 * daily_notional_sats * FEE_MINT_REDEEM
);
ctrl.receiveFees(fee_sats);
currencyFeeTally[code] += fee_sats;
totalFeesIn += fee_sats;
}
// Also mint some DRA each day. Adoption assumption: ~0.5% of family
// transaction volume converts to DRA mints (a realistic yield-seeking
// fraction for a fee-aggregating asset). This keeps DRA supply in
// proportion to family volume so per-DRA NAV doesn't inflate from
// sparse supply against rich fee inflow.
const family_tx = currencies.reduce((sum, [, p]) => sum + p.tx_per_day, 0);
const daily_dra_mints = Math.round(family_tx * 0.005);
ctrl.mint(daily_dra_mints, dra_rate);
}
const final_bch_eur = traj((total_days - 1) * 24, total_hours);
const final_nav_eur = ctrl.navInEur(final_bch_eur);
const final_nav_sats = ctrl.navPerDra();
console.log(` Combined family fee flow over 5y:`);
const totalFamilyFees_bch = totalFeesIn / 1e8;
for (const [code, sats] of Object.entries(currencyFeeTally)) {
const share = (sats / totalFeesIn * 100).toFixed(1);
console.log(` ${code}: ${(sats / 1e8).toFixed(2)} BCH (${share}%)`);
}
console.log(` ─────────────────────────`);
console.log(` Total: ${totalFamilyFees_bch.toFixed(2)} BCH → DRA pool over 5y`);
console.log(` Final state:`);
console.log(` DRA supply: ${fmt(ctrl.supply)} | catalysts: A=${ctrl.catalysts} | pool: ${(ctrl.pool_sats / 1e8).toFixed(2)} BCH`);
console.log(` Per-DRA NAV: ${final_nav_sats.toFixed(0)} sats = €${final_nav_eur.toFixed(2)}`);
console.log(` Multi-currency vs single-currency (med adoption) baseline: 5y=€35.67 (sustained debasement)`);
console.log(` Multi-currency lift: ${(final_nav_eur / 35.67).toFixed(2)}× over single-currency baseline`);
}
function T4_adversarial_trajectory() {
console.log('\n=== T4 — Adversarial trajectory (early EUR strengthening, then recovery; medium adoption) ===');
const r = runSim({
trajectoryName: 'adversarial_early_strengthen',
adoptionName: 'med',
total_days: 1825,
verbose: true,
});
printResult('adversarial', r);
// Show samples to see the drawdown shape
console.log(' Quarterly sample of NAV trajectory:');
for (let i = 0; i < r.samples.length; i += 3) {
const s = r.samples[i];
console.log(` day ${String(s.day).padStart(4)}: BCH €${String(s.bch_eur).padStart(5)} | NAV €${String(s.nav_per_dra_eur).padStart(8)} | A=${s.catalysts}`);
}
}
function T5_vintage_analysis() {
console.log('\n=== T5 — Vintage analysis: holder fairness at different mint vintages (sustained debasement, high adoption, 5y) ===');
// Run main sim, snapshot pool state at vintage points, compute what a 100-DRA holder would redeem for at end.
const ctrl = new DraController();
ctrl.mint(10_000, DRA_RATE_0); // genesis seed
const adoption = adoptionProfiles.high;
const traj = trajectories.sustained_debasement;
const total_days = 1825;
const total_hours = total_days * 24;
const vintages = [0, 365, 730, 1095, 1460]; // years 0,1,2,3,4
const vintageSnapshots = {};
for (let day = 0; day < total_days; day++) {
const hours = day * 24;
const bch_eur = traj(hours, total_hours);
const eur_rate_spot = 1e8 / bch_eur;
const eur_rate_avg = ctrl.observeEurRate(eur_rate_spot, hours);
const dra_rate = ctrl.draRate(eur_rate_avg);
if (vintages.includes(day)) {
// Pretend a holder mints 100 DRA today; record their entry NAV
vintageSnapshots[day] = {
entry_dra_rate: dra_rate,
entry_bch_eur: bch_eur,
entry_pool_nav: ctrl.navPerDra(),
};
}
ctrl.mint(adoption.mint_per_day, dra_rate);
const daily_notional_sats = (adoption.tx_per_day * adoption.notional_per_tx_eur / bch_eur) * 1e8;
ctrl.receiveFees(Math.round(0.8 * daily_notional_sats * FEE_SWAP + 0.2 * daily_notional_sats * FEE_MINT_REDEEM));
}
const final_nav_sats = ctrl.navPerDra();
const final_bch_eur = traj((total_days - 1) * 24, total_hours);
const final_nav_eur = final_nav_sats * final_bch_eur / 1e8;
console.log(` Final per-DRA NAV: €${final_nav_eur.toFixed(2)} | Final BCH/EUR: €${final_bch_eur.toFixed(0)}`);
console.log(' Per-vintage entry vs final-redemption (100 DRA cohort):');
for (const day of vintages) {
const v = vintageSnapshots[day];
const entry_cost_eur = (v.entry_dra_rate / 1e8) * v.entry_bch_eur * 100;
const final_value_eur = final_nav_eur * 100;
const multiple = final_value_eur / entry_cost_eur;
console.log(` vintage day ${String(day).padStart(4)} (BCH €${v.entry_bch_eur.toFixed(0)}): paid €${entry_cost_eur.toFixed(2)}, worth €${final_value_eur.toFixed(2)} → ${multiple.toFixed(2)}×`);
}
}
// ────────────────────────────────────────────────────────────────────────
// Main
// ────────────────────────────────────────────────────────────────────────
function main() {
console.log('═══════════════════════════════════════════════════════════════════');
console.log(' DRA controller simulator — asymmetric three-force model');
console.log(' Maturity target: 1 DRA = €357 (the opposite reverse)');
console.log('═══════════════════════════════════════════════════════════════════');
console.log(` Genesis: BCH €${BCH_EUR_GENESIS}, EUR_rate ${EUR_RATE_0} sats, DRA_rate ${DRA_RATE_0} sats`);
console.log(` Formula: DRA = ${DRA_RATE_0} − ${ALPHA} × (EUR − ${EUR_RATE_0}) / max(r × (1 + A × β), 1)`);
console.log(` Catalyst: every ${fmt(CATALYST_TRIGGER)} cumulative DRA mints; β = ${BETA} default`);
console.log(` Clamp: [${LOWER_CLAMP_0} − A×${LOWER_WIDEN_PER_CAT}, ${UPPER_CLAMP_0} + A×${UPPER_WIDEN_PER_CAT}] × DRA_rate_0`);
T1_fee_accrual_lift();
T2_reflective_asymmetric_response();
T3_catalyst_convergence_beta_sweep();
T4_adversarial_trajectory();
T5_vintage_analysis();
T6_long_horizon_trajectory();
T7_three_horizons();
T8_multicurrency_competition();
console.log('\n───────────────────────────────────────────────────────────────────');
console.log(' Done. Maturity target: 1 DRA ≥ €357 (multi-decade horizon by design).');
console.log(' ✓ = maturity reached; ✗ = on the long arc — any positive trajectory is on-design.');
console.log('───────────────────────────────────────────────────────────────────\n');
}
main();