Orgchain v1.6.1: under Last act, a timed beat goes to whoever acted last
A timed or manual beat used to pay the holding being worked in. Now it pays the holding behind the newest cued act or send; with nothing cued, Beat now still pays the one being worked in. At a timed tempo, holdings race for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H4upF5LVwmPDDqrhdUfnXh
This commit is contained in:
1 parent
2354947d16
commit
32e8131500
4 files changed
+31
-9
No files matched your search
@@ -14,8 +14,9 @@ chain itself records who did what, and when.
|
||||
stood at any beat.
|
||||
- **Money.** Test BTC lives at each node's SegWit address. **Mining** is a lab setting: under *Split* (the default)
|
||||
every active holding mines, and each beat's 50 test BTC splits pro rata by hashrate, a dial from 0 to 9 on each
|
||||
holding (1 unless set), one coinbase output per holding; under *Last act* the holding that acts takes the whole
|
||||
beat, and *Beat now* pays the one being worked in. **Send** moves test BTC from any active node to any other, in
|
||||
holding (1 unless set), one coinbase output per holding; under *Last act* the holding behind the latest act or send
|
||||
takes the whole beat (with nothing cued, *Beat now* pays the one being worked in), so at a timed tempo acts race
|
||||
for the next beat. **Send** moves test BTC from any active node to any other, in
|
||||
its holding or another, signed by the sender's BIP-84 key: at most two coins in, at most 50 test BTC out, and the
|
||||
toy chain's fixed 500-sat fee. Sends land like acts, at once or cued, and *Beat now* mines a beat even when nothing
|
||||
is cued. A graduation moves each re-keyed node's money to its new address along with its inscription. Balances
|
||||
|
||||
+5
-5
@@ -4,7 +4,7 @@ import './orgchain.css';
|
||||
import { MetadataIndex } from './adapters/metadata-index.js';
|
||||
import { census, lineageEvents, reduce, rows, slug, summary, trail } from './orgchain/lineage.js';
|
||||
import { LAB_KEY, beat, decodeLab, emptyLab, encodeLab, mutate, readLab, setRigs, setRule, setTempo, tick } from './orgchain/store.js';
|
||||
import { SAMPLE, act, gasCheck, gasCost, minerOf, send, seedSample } from './orgchain/actions.js';
|
||||
import { SAMPLE, act, gasCheck, gasCost, lastHolding, minerOf, send, seedSample } from './orgchain/actions.js';
|
||||
import { addressesOf, derivedSubtree, holderOf, keyOf, leavesOf, secretsOf } from './orgchain/keys.js';
|
||||
import { DUST, FEE, balances, brief, btc, coins, ledger, miners, most, owners, payerOf, rigsOf, satsOf, tankOf, tanks } from './orgchain/money.js';
|
||||
import { readEnvelope } from './adapters/inscription.js';
|
||||
@@ -143,7 +143,7 @@ function renderHoldings() {
|
||||
}));
|
||||
for (const b of document.querySelectorAll('[data-rule]')) b.setAttribute('aria-pressed', String((lab.mining?.rule ?? 'split') === b.dataset.rule));
|
||||
$('mining-note').textContent = split ? "Every active holding mines: each beat's 50 tBTC splits by hashrate, set on each holding (0 sits out)."
|
||||
: "The holding that acts takes the whole beat, and Beat now pays the one you're working in. Every act costs gas, so it's a race.";
|
||||
: "The holding behind the latest act or send takes the whole beat; with nothing cued, Beat now pays the one you're working in. At a timed tempo it's a race for the next beat.";
|
||||
$('holdings-empty').hidden = live.roots.length > 0;
|
||||
$('holdings-sub').textContent = live.roots.length ? String(live.roots.length) : '';
|
||||
$('sample').disabled = live.roots.some(r => r.name === SAMPLE && r.status === 'active');
|
||||
@@ -522,16 +522,16 @@ for (const b of document.querySelectorAll('[data-rule]')) b.addEventListener('cl
|
||||
// Time: the tempo, Beat now, and the clock that lands cued acts when their beat is due
|
||||
for (const b of document.querySelectorAll('[data-tempo]')) b.addEventListener('click', async () => {
|
||||
const value = b.dataset.tempo, tempo = /^\d+$/.test(value) ? Number(value) : value;
|
||||
if (!(await run(l => setTempo(l, tempo, Date.now(), minerOf(l, ui.holding))))) return;
|
||||
if (!(await run(l => setTempo(l, tempo, Date.now(), minerOf(l, lastHolding(l) ?? ui.holding))))) return;
|
||||
say(value === 'each' ? 'Each act is its own beat.' : value === 'manual' ? 'Acts are cued until you press Beat now.'
|
||||
: `Acts are cued and land together on the next beat, at most ${value} seconds after the first.`);
|
||||
});
|
||||
$('beat-now').addEventListener('click', () => landCue(l => { beat(l, minerOf(l, ui.holding)); }));
|
||||
$('beat-now').addEventListener('click', () => landCue(l => { beat(l, minerOf(l, lastHolding(l) ?? ui.holding)); }));
|
||||
setInterval(() => {
|
||||
renderClock();
|
||||
const { tempo, nextAt } = lab.clock;
|
||||
if (ui.busy || document.hidden || typeof tempo !== 'number' || !lab.chain.mempool.length || (nextAt && Date.now() < nextAt)) return;
|
||||
landCue(l => tick(l, Date.now(), minerOf(l, ui.holding)));
|
||||
landCue(l => tick(l, Date.now(), minerOf(l, lastHolding(l) ?? ui.holding)));
|
||||
}, 1000);
|
||||
addEventListener('storage', event => { if (event.key === LAB_KEY || event.key === null) load(); });
|
||||
|
||||
|
||||
+14
-1
@@ -11,7 +11,7 @@ import { newMnemonic } from '../adapters/lineage-keys.js';
|
||||
import { holderCoin, makeClaim, countersign } from '../adapters/handoff.js';
|
||||
import { PROTOCOL, lineageEvents, reduce } from './lineage.js';
|
||||
import { armClock, beat } from './store.js';
|
||||
import { derivedSubtree, emptyKeys, holderOf, keyOf, rootWallet } from './keys.js';
|
||||
import { derivedSubtree, emptyKeys, holderOf, keyOf, rootWallet, scriptHex } from './keys.js';
|
||||
import { DUST, SAT, canSpend, covers, pay, payouts, payerOf, spend, sweep } from './money.js';
|
||||
|
||||
const FIELDS = { found: ['name'], birth: ['name', 'kind'], award: ['to', 'skill', 'belt'], decide: ['title', 'why'], graduate: ['of'], close: [] };
|
||||
@@ -102,6 +102,19 @@ function inscribe(lab, wallet, op, by, metadata) {
|
||||
// What a beat pays (money.js payouts): every mining holding's share, or, under 'last', the holding that acted.
|
||||
const minerFor = (lab, id) => { const state = lineageState(lab); return payouts(lab, state, state.nodes.get(id)?.root ?? null); };
|
||||
export const minerOf = (lab, holdingId) => payouts(lab, lineageState(lab), holdingId);
|
||||
// The holding behind the newest cued act or send, or null: under 'last', a timed beat goes to whoever acted last.
|
||||
export function lastHolding(lab) {
|
||||
const state = lineageState(lab), byScript = new Map();
|
||||
for (const n of state.nodes.values()) { const w = payerOf(lab.keys, state, n); if (w) byScript.set(scriptHex(w.script), n.root); }
|
||||
for (const raw of [...lab.chain.mempool].reverse()) {
|
||||
const record = lab.chain.transactions.get(Tx.util.getTxid(raw));
|
||||
if (record?.inscription) { const root = state.log.find(e => e.id === record.inscription.id)?.root; if (root) return root; continue; }
|
||||
const input = record?.tx.vin[0], prev = input && lab.chain.transactions.get(input.txid)?.tx.vout[input.vout];
|
||||
const root = prev && byScript.get(scriptHex(prev.scriptPubKey));
|
||||
if (root) return root;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// Who signs for a node: whichever key holds its inscription.
|
||||
function signerFor(lab, state, id) {
|
||||
const holder = holderOf(lab, state, id);
|
||||
|
||||
+9
-1
@@ -8,7 +8,7 @@ import { getVin, getVout } from '../src/upstream/primitives.js';
|
||||
import { MetadataIndex } from '../src/adapters/metadata-index.js';
|
||||
import { lineageEvents, reduce } from '../src/orgchain/lineage.js';
|
||||
import { decodeLab, emptyLab, encodeLab, miningPreference, setRigs, setRule, setTempo, tick } from '../src/orgchain/store.js';
|
||||
import { act, gasCheck, lineageState, minerOf, seedSample, send } from '../src/orgchain/actions.js';
|
||||
import { act, gasCheck, lastHolding, lineageState, minerOf, seedSample, send } from '../src/orgchain/actions.js';
|
||||
import { FEE, PayWallet, REWARD, SAT, balances, brief, btc, ledger, most, owners, payerOf, satsOf, tankOf, tanks } from '../src/orgchain/money.js';
|
||||
import { keyOf } from '../src/orgchain/keys.js';
|
||||
|
||||
@@ -88,6 +88,14 @@ test('beats split across holdings by hashrate, or go whole to the last act', ()
|
||||
act(lab, 'decide', beta, { title: 'Clicked' });
|
||||
assert.deepEqual(mined(6), [['beta', REWARD]]);
|
||||
assert.equal(minerOf(lab, acme).length, 1);
|
||||
setTempo(lab, 30, 1000);
|
||||
act(lab, 'decide', acme, { title: 'First' }, 2000);
|
||||
act(lab, 'decide', beta, { title: 'Last in' }, 3000);
|
||||
assert.equal(lastHolding(lab), beta);
|
||||
assert.equal(tick(lab, 32000, minerOf(lab, lastHolding(lab))), true);
|
||||
assert.deepEqual(mined(7), [['beta', REWARD]]); // the race for a timed beat: whoever acted last takes it
|
||||
assert.equal(lastHolding(lab), null);
|
||||
setTempo(lab, 'each');
|
||||
assert.deepEqual(decodeLab(encodeLab(lab)).mining, { rule: 'last', rigs: { [acme]: 3 } });
|
||||
assert.throws(() => miningPreference({ rule: 'luck' }), /Invalid mining rule/);
|
||||
assert.throws(() => miningPreference({ rigs: { [acme]: 10 } }), /Invalid hashrate/);
|
||||
|
||||
Reference in new issue
Block a user