deviant/scripts/sim-fiatm-adaptive-comparison.mjs
walerikus 599202a721 chore: checkpoint working tree — shell Tier-B retirement + accumulated WIP
Snapshot of accumulated work-in-progress on top of the baseline commit.

This session — retire Tier B from the wallet shell (single-tier model, glossary
"Tiers & trust"):
- SendFlow.tsx: BCH-only; removed the non-functional Tier-B send path, the D-4/D-5
  raw-BCH co-display, and the ADR-0013 friction ladder.
- ArtemisionScreen.tsx: Create wizard collapsed to a single 5-step on-chain genesis
  flow; tier-selection step + Tier-B branch (prepareTierBImport) deleted.
- QuickActions.tsx / SettingsScreen.tsx: receive note reworded; "Send friction
  thresholds" + "Tier explainer" rows removed; registry-trust note reworded.
- Verified: tsc clean, vitest 74/74, full preview walkthrough, no console errors.

Pre-existing uncommitted work also folded in:
- USDm -> FIATm family rename/reorg (Parameters/FIATm/, design records)
- ~130 chipnet + sim-fiatm scripts (catalytic-converter, hyperinflation backtests)
- snapshots 0153-0168; wiki updates (glossary, decisions, index); ADR-0044/0045
- mockups + additional shell screens (Agora, Apps, MainScreen, AssetDetail, ...)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 01:01:37 +02:00

232 lines
8.8 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-fiatm-adaptive-comparison.mjs — compares baseline §12 controller
* vs proposed adaptive overlay (price × flow 4-condition matrix) on a
* fresh-launched country currency.
*
* Two scenarios:
* A. Crash + active mints → adaptive slows down crash-side response
* (wider bargain window, more BCH absorbed)
* B. Rally + active redeems → adaptive speeds up rally-side response
* (premium window closes, no sustained extraction)
*
* Outputs JSON to stdout for the visualization.
*
* Run: node scripts/sim-fiatm-adaptive-comparison.mjs > adaptive-data.json
*/
// ── Controller constants (§12) ──────────────────────────────────────────
const BLOCK_MIN = 10;
const RETARGET_BLOCKS = 2016;
const SUPPLY_CATALYST = 210_000;
const N_FLOOR = 1, N_CEILING = 144;
const D_FLOOR = 0.05, D_CEILING = 0.20;
const N_STEP = 1, D_STEP = 0.005;
const DEADBAND_BPS = 50;
const CAP_BPS = 1000;
// ── Adaptive layer parameters ───────────────────────────────────────────
const EWMA_HALF_LIFE = 144; // 1 day
const EWMA_ALPHA = 1 - Math.pow(0.5, 1 / EWMA_HALF_LIFE);
const ADAPTIVE_FLOW_THRESHOLD = 1.5; // ratio mint/redeem to be "active"
const ADAPTIVE_DORMANT_THRESHOLD = 0.5; // total flow below this fraction of baseline = dormant
const ADAPTIVE_SLOWDOWN = 2.0; // 2x slower
const ADAPTIVE_SPEEDUP = 0.5; // 2x faster
class Controller {
constructor(adaptive = false, initialBchPrice = 200) {
// Fresh-launch state — say a new country currency, 3 catalysts in
this.N_up = 1;
this.N_down = 4; // already 3 catalysts past genesis
this.d_up = 0.115; // 10% + 3×0.5%
this.d_down = 0.085; // 10% - 3×0.5%
this.doubling_epoch = 0;
this.catalyst_count = 3;
this.cumulative_minted = SUPPLY_CATALYST * 3; // 630k
this.last_recal_block = 0;
this.last_retarget_block = 0;
this.window_mints = 0;
this.window_redeems = 0;
this.window_rate_moves = 0;
this.window_supply_start = this.cumulative_minted;
this.prev_window_growth = 0;
// EWMA for adaptive layer
this.mint_ewma = 0;
this.redeem_ewma = 0;
this.baseline_flow = 1.5; // baseline per-block flow
this.adaptive = adaptive;
// Start at oracle target for initial price (gap=0)
this.rate = Math.round(100_000_000 / initialBchPrice);
}
effectiveCadence(direction) {
// Existing §12 calc
const N = direction === 'down' ? this.N_down : this.N_up;
const baseCadence = N * Math.pow(2, this.doubling_epoch);
if (!this.adaptive) return baseCadence;
// Adaptive overlay
const totalFlowEwma = this.mint_ewma + this.redeem_ewma;
if (totalFlowEwma < this.baseline_flow * ADAPTIVE_DORMANT_THRESHOLD) return baseCadence;
const mintRatio = this.mint_ewma / Math.max(this.redeem_ewma, 0.01);
const isMintHeavy = mintRatio > ADAPTIVE_FLOW_THRESHOLD;
const isRedeemHeavy = mintRatio < 1 / ADAPTIVE_FLOW_THRESHOLD;
let mult = 1.0;
if (direction === 'down') {
// crash-side cadence
if (isMintHeavy) mult = ADAPTIVE_SLOWDOWN; // slow → wider window
else if (isRedeemHeavy) mult = ADAPTIVE_SPEEDUP; // speed → close window
} else {
// rally-side cadence
if (isRedeemHeavy) mult = ADAPTIVE_SPEEDUP; // speed → fair conversion
else if (isMintHeavy) mult = ADAPTIVE_SLOWDOWN; // slow → preserve premium
}
return Math.max(1, Math.round(baseCadence * mult));
}
step(block, bch_price, mints, redeems) {
this.cumulative_minted += mints;
this.window_mints += mints;
this.window_redeems += redeems;
// EWMA update (per-block)
this.mint_ewma += EWMA_ALPHA * (mints - this.mint_ewma);
this.redeem_ewma += EWMA_ALPHA * (redeems - this.redeem_ewma);
// Catalyst trigger (unchanged)
const expected_catalysts = Math.floor(this.cumulative_minted / SUPPLY_CATALYST);
while (expected_catalysts > this.catalyst_count) {
this.catalyst_count++;
this.N_down = Math.min(this.N_down + N_STEP, N_CEILING);
this.N_up = Math.max(this.N_up - N_STEP, N_FLOOR);
this.d_down = Math.max(this.d_down - D_STEP, D_FLOOR);
this.d_up = Math.min(this.d_up + D_STEP, D_CEILING);
}
// DART time trigger (unchanged)
if (block - this.last_retarget_block >= RETARGET_BLOCKS) {
this.last_retarget_block = block;
this.window_mints = 0; this.window_redeems = 0; this.window_rate_moves = 0;
this.prev_window_growth = this.cumulative_minted - this.window_supply_start;
this.window_supply_start = this.cumulative_minted;
}
// Rate adjustment with adaptive cadence
const target = Math.round(100_000_000 / bch_price);
const direction = target > this.rate ? 'down' : 'up';
const cadence = this.effectiveCadence(direction);
if (block - this.last_recal_block >= cadence) {
const gap = target - this.rate;
const gapBps = Math.abs((gap / this.rate) * 10000);
if (gapBps >= DEADBAND_BPS) {
const d = direction === 'down' ? this.d_down : this.d_up;
let delta = gap * d;
const max_delta = (this.rate * CAP_BPS) / 10000;
delta = Math.sign(delta) * Math.min(Math.abs(delta), max_delta);
this.rate = Math.round(this.rate + delta);
this.last_recal_block = block;
this.window_rate_moves += direction === 'down' ? 1 : -1;
}
}
}
gap(bch_price) {
const target = 100_000_000 / bch_price;
return ((this.rate - target) / target) * 100;
}
}
// ── Scenarios ────────────────────────────────────────────────────────────
const HOURS = 144; // blocks (one day)
const SIM_BLOCKS = 6 * HOURS; // 6 days = 864 blocks
function runScenario(scenarioName, priceFn, mintFn, redeemFn) {
const initialPrice = priceFn(0);
const baseline = new Controller(false, initialPrice);
const adaptive = new Controller(true, initialPrice);
const data = [];
for (let block = 0; block < SIM_BLOCKS; block++) {
const price = priceFn(block);
const mints = mintFn(block);
const redeems = redeemFn(block);
baseline.step(block, price, mints, redeems);
adaptive.step(block, price, mints, redeems);
// Sample every 12 blocks (2 hours) for chart clarity
if (block % 12 === 0) {
data.push({
hours: (block * BLOCK_MIN) / 60,
block,
bch_price: price,
oracle_target_sats_per_usd: Math.round(100_000_000 / price),
baseline_rate: baseline.rate,
adaptive_rate: adaptive.rate,
baseline_gap_pct: baseline.gap(price),
adaptive_gap_pct: adaptive.gap(price),
baseline_cadence_down: baseline.effectiveCadence('down'),
adaptive_cadence_down: adaptive.effectiveCadence('down'),
baseline_cadence_up: baseline.effectiveCadence('up'),
adaptive_cadence_up: adaptive.effectiveCadence('up'),
mint_ewma: adaptive.mint_ewma.toFixed(2),
redeem_ewma: adaptive.redeem_ewma.toFixed(2),
});
}
}
return { name: scenarioName, data };
}
// Scenario A: Crash + active mints
// BCH crashes from $200 to $150 (-25%) over 18 hours then stabilizes
// Mint activity ramps to 5x baseline during the dip
const scenarioA = runScenario(
'Crash + active mints',
(block) => {
const t = block / SIM_BLOCKS;
if (block < 18 * 6) return 200 - 50 * (block / (18 * 6));
return 150;
},
(block) => {
// Active counter-cyclical minting during the crash
if (block < 18 * 6) return 1.5 + 6 * (block / (18 * 6)); // ramp from 1.5 to 7.5
if (block < 36 * 6) return 7.5 - 4 * ((block - 18 * 6) / (18 * 6)); // peak then decline
return 3.5;
},
() => 1.0 // baseline redeem activity
);
// Scenario B: Rally + active redeems
// BCH rallies from $200 to $250 (+25%) over 18 hours then stabilizes
// Redeem activity ramps to 5x baseline as holders cash in premium
const scenarioB = runScenario(
'Rally + active redeems',
(block) => {
if (block < 18 * 6) return 200 + 50 * (block / (18 * 6));
return 250;
},
() => 1.0, // baseline mint activity
(block) => {
if (block < 18 * 6) return 1.5 + 6 * (block / (18 * 6));
if (block < 36 * 6) return 7.5 - 4 * ((block - 18 * 6) / (18 * 6));
return 3.5;
}
);
console.log(JSON.stringify({
meta: {
currency: 'A new country currency (fresh launch, 3 catalysts in)',
initial_state: { N_up: 1, N_down: 4, d_up: '11.5%', d_down: '8.5%', catalysts: 3 },
adaptive_params: {
ewma_half_life: '1 day',
flow_threshold: '1.5x mint/redeem ratio',
dormant_threshold: '50% of baseline flow',
slowdown_multiplier: '2.0x',
speedup_multiplier: '0.5x',
},
},
scenarios: [scenarioA, scenarioB],
}, null, 2));