tools/launch/income-tiers.mjs renders docs/analysis/income-tiers.md (public) from tools/launch/income-tiers.json: eleven measured cards (the 6 October rented-card rows, the 9070 XT telemetry run, the M5 Max Metal bench, each with its source), IGN a day at 1, 10 and 100 GH/s after the ramp on EmissionSchedule::TESTNET_1 (100 IGN a block, 90-day ramp from 10 percent, monthly 2^(-1/24), the 80 percent producer share), electricity a day and per mined IGN at USD 0.005, 0.02 and 0.10 a kWh, a rig and a pool-user line, the consequences per tier, the owed rows; --check fails CI when the page and its inputs disagree; the test pins the arithmetic (6,912 IGN a day for 100 MH/s on 100 GH/s at the launch rate, day 1 at 10 percent, one step at 2^(-1/24)). tools/observer/hash-origin.mjs: once a day from the observer's tables, who found the blocks: keys with a block and above dust, attested pools against shared payout addresses (a multi-key machine is not a pool until its operator attests), the project fleet's share from the intake identity lines plus the fleet registry's key file, the ten largest keys, the 2x step since yesterday, the community gates (first 100 keys, first outside block, first attested outside pool at 10 percent for 7 days; the X5 count stays an upper bound until the two observer columns exist). --dry reads only; --write keeps hash_origin_days and hash_origin_reports and live_state.hash_origin; --post goes through the Discord poster's guard. Fixture, a known-finished and a known-failed day in the test. Ran read-only on the live devnet today: 113 keys, 0.76 GH/s, the fleet share wrong until the key file exists. tools/ci/launch-gates-check.mjs (in pre-push.sh with the two test lines): every row of the Launch gates table in testnet-go.md has a check and every backticked path exists; the site-lane handoff and the income table carry no served-page or export pattern and no em dash; the eight regulatory sentences are present in order under the label. Self-test fires on each. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
35 lines
2.2 KiB
JavaScript
35 lines
2.2 KiB
JavaScript
// node --test tools/launch/income-tiers.test.mjs
|
|
// The schedule arithmetic against the fork's TESTNET_1 constants (one known-good case per rule, and one known-failed
|
|
// case: a changed input must change the rendered table, so --check cannot pass on a stale file).
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { ignPerDay, producerIgnPerBlock, rampFactor, rateAtSeconds, render, compute, loadInputs, TESTNET_1 } from './income-tiers.mjs';
|
|
|
|
test('100 MH/s on 100 GH/s after the ramp mines 6,912 IGN a day (docs/analysis/horizon-2026-10.md, section 1)', () => {
|
|
// the horizon figure was taken at the launch rate before any glide step; at day 90 two monthly steps have passed
|
|
const atLaunchRate = (100 / 100_000) * 86_400 * 100 * 0.8;
|
|
assert.equal(Math.round(atLaunchRate), 6912);
|
|
const day90 = ignPerDay(100, 100, 90 * 86_400);
|
|
assert.ok(day90 < atLaunchRate && day90 > atLaunchRate * 0.94, `day 90 is two glide steps under the launch figure: ${day90}`);
|
|
});
|
|
|
|
test('day 1 pays 10 percent, day 90 the whole rate, one month step pays 2^(-1/24) of the one before', () => {
|
|
assert.equal(rampFactor(0), 0.1);
|
|
assert.equal(rampFactor(TESTNET_1.ramp_seconds), 1);
|
|
assert.equal(rampFactor(TESTNET_1.ramp_seconds * 10), 1);
|
|
assert.equal(producerIgnPerBlock(0), 100 * 0.1 * 0.8);
|
|
const step1 = rateAtSeconds(TESTNET_1.step_seconds);
|
|
assert.ok(Math.abs(step1 - 100 * 2 ** (-1 / 24)) < 1e-6, `one step: ${step1}`);
|
|
assert.ok(Math.abs(rateAtSeconds(24 * TESTNET_1.step_seconds) - 50) < 1e-4, 'twenty-four steps halve the rate');
|
|
});
|
|
|
|
test('every input row renders, and a changed rate changes the table (the stale-file case)', () => {
|
|
const inputs = loadInputs();
|
|
const text = render(inputs);
|
|
for (const r of inputs.rows) assert.ok(text.includes(`| ${r.tier} | ${r.card} |`), `${r.card} is in the table`);
|
|
const c = compute(inputs);
|
|
for (const r of c.rows) { assert.ok(r.ign_per_day_day90[10] > 0); if (r.watts === null) assert.equal(r.electricity_usd_per_day[0.1], null); }
|
|
const changed = JSON.parse(JSON.stringify(inputs)); changed.rows[0].mh_s += 1;
|
|
assert.notEqual(render(changed), text);
|
|
assert.ok(!/\bPC [12]\b|the project lead|Hetzner|\/Users\//.test(text), 'the public table carries no identity pattern');
|
|
});
|