deviant/scripts/sim-fiatm-crash-bands.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

144 lines
5.8 KiB
JavaScript
Raw 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-crash-bands.mjs — shows crash-convergence behavior gradually
* by supply band (each 210k minted = next catalyst level).
*
* For each supply band, displays the controller state and how long the
* rate takes to converge (within 1% of target) after instant crashes of
* -10%, -20%, -30%, -40%, -50%, -60%.
*
* Run: node scripts/sim-fiatm-crash-bands.mjs
*/
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;
const ONE_DAY = 144;
function stateAtCatalyst(k) {
let N_up = 1, N_down = 1, d_up = 0.10, d_down = 0.10;
for (let i = 0; i < k; i++) {
N_down = Math.min(N_down + N_STEP, N_CEILING);
N_up = Math.max(N_up - N_STEP, N_FLOOR);
d_down = Math.max(d_down - D_STEP, D_FLOOR);
d_up = Math.min(d_up + D_STEP, D_CEILING);
}
return { N_up, N_down, d_up, d_down };
}
function oracleTarget(bch_usd) { return Math.round(100_000_000 / bch_usd); }
function crashConvergence(state, crashPct) {
let rate = oracleTarget(500);
const newPrice = 500 * (1 - crashPct / 100);
const target = oracleTarget(newPrice);
let lastRecal = 0;
const MAX = 365 * ONE_DAY * 3;
for (let b = 1; b <= MAX; b++) {
if (b - lastRecal >= state.N_down) {
const gap = target - rate;
const gapBps = Math.abs((gap / rate) * 10000);
if (gapBps >= DEADBAND_BPS) {
let delta = gap * state.d_down;
const max_delta = (rate * CAP_BPS) / 10000;
delta = Math.sign(delta) * Math.min(Math.abs(delta), max_delta);
rate = Math.round(rate + delta);
lastRecal = b;
}
}
const gapAbs = Math.abs(((rate - target) / target) * 100);
if (gapAbs < 1.0) return b;
}
return null;
}
function fmtBlocks(b) {
if (b === null) return '∞';
const min = b * 10;
if (min < 60) return `${min}m`;
if (min < 1440) return `${(min/60).toFixed(1)}h`;
if (min < 1440*30) return `${(min/1440).toFixed(1)}d`;
return `${(min/1440/30).toFixed(1)}mo`;
}
// Supply bands as the user specified — each band = controller state after K catalysts
const BANDS = [
{ range: '0 — 210k', catalyst: 0 },
{ range: '210k — 420k', catalyst: 1 },
{ range: '420k — 630k', catalyst: 2 },
{ range: '640k — 850k', catalyst: 3 },
{ range: '860k — 1.27M', catalyst: 4 },
{ range: '1.28M — 1.49M', catalyst: 6 },
{ range: '1.50M — 1.70M', catalyst: 7 },
];
const CRASHES = [10, 20, 30, 40, 50, 60];
console.log('\nFIATm crash convergence by supply band');
console.log('Each band shows the controller state during that supply range');
console.log('and how long the rate takes to converge within 1% of oracle target after an instant crash.\n');
console.log('Time-doubling held at epoch 0 (pure supply-axis view).\n');
console.log('═'.repeat(112));
console.log(` ${'supply band'.padEnd(20)} ${'cat'.padStart(4)} ${'N↓'.padStart(4)} ${'d↓'.padStart(6)} ${'d↑'.padStart(6)} | ${'-10%'.padStart(9)} ${'-20%'.padStart(9)} ${'-30%'.padStart(9)} ${'-40%'.padStart(9)} ${'-50%'.padStart(9)} ${'-60%'.padStart(9)}`);
console.log('═'.repeat(112));
for (const band of BANDS) {
const s = stateAtCatalyst(band.catalyst);
const times = CRASHES.map(c => fmtBlocks(crashConvergence(s, c)));
console.log(
` ${band.range.padEnd(20)} ${String(band.catalyst).padStart(4)} ${String(s.N_down).padStart(4)} ${(s.d_down*100).toFixed(1).padStart(5)}% ${(s.d_up*100).toFixed(1).padStart(5)}% | ${times.map(t => t.padStart(9)).join(' ')}`
);
}
console.log('═'.repeat(112));
console.log('\n PER-BAND DETAIL\n');
for (const band of BANDS) {
const s = stateAtCatalyst(band.catalyst);
console.log(` ── ${band.range} (after ${band.catalyst} catalyst${band.catalyst === 1 ? '' : 's'}) ──`);
console.log(` Controller: N_down=${s.N_down} (${fmtBlocks(s.N_down)} between updates), d_down=${(s.d_down*100).toFixed(1)}% step size`);
for (const c of CRASHES) {
const t = crashConvergence(s, c);
const blocks = t || 'no converge';
const naturalLanguage = describeOutcome(t, c, s);
console.log(` -${c}% crash: ${fmtBlocks(t).padEnd(8)} (${blocks} blocks) ${naturalLanguage}`);
}
console.log('');
}
function describeOutcome(blocks, crashPct, state) {
if (blocks === null) return '';
const min = blocks * 10;
const days = min / 1440;
if (state.N_down === 1) return '— controller tracks crash in near real-time';
if (days < 1) return '— still subhourly tracking; minter discount window is brief';
if (days < 7) return '— multi-day mint discount window; arbitrageurs can enter';
if (days < 30) return '— extended bargain window for vintage selection';
return '— deep, sustained discount; full §12b BCH price-support feedback active';
}
console.log('═'.repeat(112));
console.log(`
STRUCTURAL OBSERVATIONS
1. The first crossing (0 → 210k minted) doubles the down-cadence and barely changes d_down
(10% → 9.5%). But even a -30% crash already takes 2× longer to converge: 5.5h → 11.7h.
2. Larger crashes converge SLOWER per catalyst increment because each step reduces the
remaining gap by d_down (a fraction). Bigger initial gaps require more steps.
3. By catalyst 4 (~860k minted), the rate-cadence asymmetry begins compounding noticeably:
-50% crash now takes ~1.7 days at catalyst 4 vs 6.3h at genesis.
4. The convergence time scales roughly as N_down × (steps needed) × 10 min, where steps
needed grows logarithmically with crash depth. So doubling crash depth adds only one
or two extra steps, but doubling N_down doubles total time directly.
5. For users: at low supply (under 1M minted), even -60% crashes converge in ~2 days max.
The "bargain window" is brief and modest. Mature controllers (later snapshots) will
show much wider windows for the same shock.
`);