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>
144 lines
5.8 KiB
JavaScript
144 lines
5.8 KiB
JavaScript
#!/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.
|
||
`);
|