diff --git a/.github/workflows/client.yml b/.github/workflows/client.yml index 08a0e0d..685ae56 100644 --- a/.github/workflows/client.yml +++ b/.github/workflows/client.yml @@ -35,6 +35,9 @@ jobs: - name: Typecheck working-directory: client run: bun run typecheck + - name: Test + working-directory: client + run: bun test - name: Build working-directory: client run: bun run build @@ -43,3 +46,8 @@ jobs: run: | bun run golden 2>/dev/null > /tmp/golden.txt diff /tmp/golden.txt ../contracts/deepmark-contract/tests/golden.txt + - name: Kit difficulty table is current + working-directory: client + run: | + bun run kits 2>/dev/null > /tmp/kits.ts + diff /tmp/kits.ts src/game/kitDifficulty.generated.ts diff --git a/client/bun.lock b/client/bun.lock index ce14e6d..39014e9 100644 --- a/client/bun.lock +++ b/client/bun.lock @@ -8,6 +8,7 @@ "@stellar/stellar-sdk": "^17.2.0", }, "devDependencies": { + "@types/bun": "^1.4.2", "puppeteer-core": "^25.12.0", "typescript": "^5.6.0", "vite": "^5.4.0", @@ -125,10 +126,14 @@ "@stellar/stellar-sdk": ["@stellar/stellar-sdk@17.2.0", "", { "dependencies": { "@exodus/bytes": "^1.15.1", "@noble/ed25519": "^3.1.0", "@noble/hashes": "^2.2.0", "@stellar/js-xdr": "^5.0.0", "@types/json-schema": "^7.0.15", "axios": "1.20.0", "bignumber.js": "^11.1.4", "commander": "^14.0.3", "eventsource": "^4.1.0", "feaxios": "^0.0.23", "smol-toml": "^1.6.1", "uint8array-extras": "^1.5.0" }, "bin": { "stellar-contract-bindings-typescript": "bin/stellar-contract-bindings-typescript", "stellar-js": "bin/stellar-js", "stellar-sdk": "bin/stellar-js" } }, "sha512-hGdbvhnOMFwAe+QOAUVPrtLf8U+arO2XG+0osEeY87Ti5scj1R52YSw8HW828gf6UG4PW/jtAlfnyaGCj8t6UA=="], + "@types/bun": ["@types/bun@1.4.2", "", { "dependencies": { "bun-types": "1.4.2" } }, "sha512-GimotNn7+ZV0uVArItBbriZsR1oNf0+WTzPkdcFrzShI7k2norL0uzEaJT8T33dWr7O/c9ZDuAFQrctKCi72oQ=="], + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + "@types/node": ["@types/node@26.6.5", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg=="], + "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], "ansi-regex": ["ansi-regex@6.4.0", "", {}, "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw=="], @@ -141,6 +146,8 @@ "bignumber.js": ["bignumber.js@11.1.5", "", {}, "sha512-6WmzCNtUnfKpbozq+hOgWaZMMzORmYBwF1xZScyoIX3QRYWeKTtxxwDOW5tIz7C9BdjkIYHGTcelCLkXg0mndw=="], + "bun-types": ["bun-types@1.4.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-bxV1FgK7yBIzjRe5zBozIM4Bem11ZJcCXSrjWRG3YWLt8yFDePu4cLjpebO8OvPeIE9trbyPF4fuj3Cia4Fj3w=="], + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], "chromium-bidi": ["chromium-bidi@17.0.2", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-5v9GQFhTktFvotn/OFNJBmKLKRAb6n9r0bVCwf7sHgWc3/JryK0bj1nn93L3pHFrfgcsu6Be6EWsDi+1XHTGDg=="], @@ -245,6 +252,8 @@ "uint8array-extras": ["uint8array-extras@1.6.0", "", {}, "sha512-8iAasVS4wUx0gPLjH8Xtz2PeDzTSiy8QiLGu2DT3X5GU+egFEQhpnbtkehbAwjvDcxIERmCYcysiODFmE3Ud0Q=="], + "undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="], + "vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": "bin/vite.js" }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="], "webdriver-bidi-protocol": ["webdriver-bidi-protocol@0.4.3", "", {}, "sha512-uuN0goWfxP22B7J/uAgBpOYNPttC+XVseYE+rSY5+rQ+YBeVz/VORw8WbmLVcqW78zNg5A4qnjNXYUWR3il2ig=="], diff --git a/client/package.json b/client/package.json index 079bda8..f635961 100644 --- a/client/package.json +++ b/client/package.json @@ -13,9 +13,12 @@ "preview": "vite preview", "typecheck": "tsc --noEmit", "golden": "bun run scripts/golden-vectors.ts", + "kits": "bun run scripts/kit-difficulty.ts", + "test": "bun test", "perf": "bun run scripts/perf.ts" }, "devDependencies": { + "@types/bun": "^1.4.2", "puppeteer-core": "^25.12.0", "typescript": "^5.6.0", "vite": "^5.4.0" diff --git a/client/scripts/chain-smoke.ts b/client/scripts/chain-smoke.ts index 9767e4c..c19d0a2 100644 --- a/client/scripts/chain-smoke.ts +++ b/client/scripts/chain-smoke.ts @@ -60,7 +60,7 @@ async function clearOnce(choice: HeroChoice): Promise<{ heroId: number | undefin lap(`attempt ${attempt} seed ${ticket.seed} rank ${ticket.rank}: ${cleared ? 'cleared' : state.resultKind} (${state.actions.length} actions)`); await sleep(Math.ceil(state.actions.length / 16) * 5000 + 6000); const { outcome, hash } = await finishRun(keypair, state.actions); - lap(`finish_run ${hash.slice(0, 10)} extracted=${outcome.extracted} floor=${outcome.floor_reached} score=${outcome.score} hero=${outcome.hero_id ?? 'none'}`); + lap(`finish_run ${hash} extracted=${outcome.extracted} floor=${outcome.floor_reached} score=${outcome.score} hero=${outcome.hero_id ?? 'none'}`); if (cleared) return { heroId: outcome.hero_id, score: outcome.score }; } throw new Error('no clear in 12 attempts'); diff --git a/client/scripts/golden-vectors.ts b/client/scripts/golden-vectors.ts index a5be613..67bbccb 100644 --- a/client/scripts/golden-vectors.ts +++ b/client/scripts/golden-vectors.ts @@ -137,13 +137,6 @@ function playOne(g: Rng): { line: string } | null { } function main(): void { - // The game persists progress through localStorage, which does not exist here. - const store = new Map(); - (globalThis as { localStorage?: unknown }).localStorage = { - getItem: (k: string) => store.get(k) ?? null, - setItem: (k: string, v: string) => void store.set(k, v), - }; - // Touch the floors so a layout change fails loudly instead of silently. for (let n = 1; n <= 3; n++) buildFloor(n); diff --git a/client/scripts/kit-difficulty.ts b/client/scripts/kit-difficulty.ts new file mode 100644 index 0000000..d947f8b --- /dev/null +++ b/client/scripts/kit-difficulty.ts @@ -0,0 +1,48 @@ +/** + * Measures how often each starting kit clears all three floors and writes the + * table the creator reads its difficulty hints from. + * + * bun run kits > src/game/kitDifficulty.generated.ts + * + * Every fresh delver is rank 0, played by the fixed balanced policy over the + * same seeds for every kit. The output is deterministic, and CI regenerates it + * and fails on any difference, so a change to the simulation, the floors or the + * items cannot leave the hints saying something the game no longer does. + */ +import { CHARMS, WEAPONS } from '../src/game/items'; +import { HERO_ORDER } from '../src/game/heroes'; +import { mulberry32 } from '../src/game/rng'; +import { newRun } from '../src/game/state'; +import { playBalanced } from './policy'; + +const RUNS_PER_KIT = 2000; +const SEED_STREAM = 0x4b17_d1ff; + +function clearPercent(base: number, weapon: number, charm: number, seeds: readonly number[]): number { + let cleared = 0; + for (const seed of seeds) { + const finished = playBalanced(newRun({ base, weapon, charm, rank: 0 }, seed)); + if (finished.resultKind === 'extracted') cleared += 1; + } + return Math.round((cleared / seeds.length) * 100); +} + +function main(): void { + const stream = mulberry32(SEED_STREAM); + const seeds = Array.from({ length: RUNS_PER_KIT }, () => Math.floor(stream() * 0xffffffff)); + const table = HERO_ORDER.map((_, base) => + WEAPONS.map((_, weapon) => CHARMS.map((_, charm) => clearPercent(base, weapon, charm, seeds))) + ); + const rows = table.map((weapons) => ` [${weapons.map((charms) => `[${charms.join(', ')}]`).join(', ')}],`); + console.log( + [ + '// Generated by client/scripts/kit-difficulty.ts. Do not edit by hand: run `bun run kits`.', + `// Full clear rate in percent at rank 0, ${RUNS_PER_KIT} runs per kit, indexed [base][weapon][charm].`, + 'export const KIT_CLEAR_PERCENT: readonly (readonly (readonly number[])[])[] = [', + ...rows, + '];', + ].join('\n') + ); +} + +main(); diff --git a/client/scripts/policy.ts b/client/scripts/policy.ts index 43d669c..f02d7b5 100644 --- a/client/scripts/policy.ts +++ b/client/scripts/policy.ts @@ -36,9 +36,9 @@ function stepToward(state: RunState, goal: Point, avoid: boolean): ActionCode | return null; } -/** Plays a run to its end with the balanced policy and returns the state. */ -export function playBalanced(state: RunState): RunState { - for (let step = 0; step < 800 && state.mode !== 'result'; step++) { +/** Plays a run with the balanced policy until it ends, or until it holds `maxActions` actions, and returns the state. */ +export function playBalanced(state: RunState, maxActions: number = Infinity): RunState { + for (let step = 0; step < 800 && state.mode !== 'result' && state.actions.length < maxActions; step++) { if (state.mode === 'combat') applyAction(state, ActionCode.Attack); else if (state.mode === 'choice') applyAction(state, ActionCode.Descend); else { diff --git a/client/src/game-app.ts b/client/src/game-app.ts index 1a18ca5..e52086a 100644 --- a/client/src/game-app.ts +++ b/client/src/game-app.ts @@ -1,5 +1,6 @@ import { ActionCode, applyAction, drainEvents, newRun, type GameEvent, type RunState } from './game/state'; -import { loadProgress, type Progress } from './game/progress'; +import { loadProgress, recordRun, type Progress } from './game/progress'; +import { clearSavedRun, loadSavedRun, planResume, saveRun, type ResumePlan } from './game/savedRun'; import { onAction, type Action } from './input/keyboard'; import { drawWorld, face } from './render/canvas'; import { playAttack, resetMotion } from './render/motion'; @@ -48,7 +49,7 @@ import type { DescentOptions, DescentStage } from './render/descent'; import { rankForClears, type Loadout, type Look } from './game/loadout'; import { chain, warmChain } from './stellar/lazy'; import { CONTRACT_ERROR, freshChoice, isContractError, veteranChoice } from './stellar/choice'; -import type { FinishedRun, Hero, HeroChoice, PlayerRecord, TopEntry } from './stellar/contract'; +import type { FinishedRun, Hero, HeroChoice, PlayerRecord, RunTicket, TopEntry } from './stellar/contract'; import { fetchRelicBalances, type RelicBalances } from './stellar/relics'; const canvas = document.getElementById('game') as HTMLCanvasElement; @@ -163,7 +164,7 @@ async function refreshOnchain(): Promise { } // Load the Stellar chunk when idle, then read the player's record. The title never waits for either. -warmChain(() => void refreshOnchain()); +warmChain(() => void refreshOnchain().then(resumeOpenRun)); function onchainSnapshot(): OnchainSnapshot | null { if (!onchainRecord) return null; @@ -309,6 +310,7 @@ async function beginRun(plan: DelverPlan): Promise { const loadout: Loadout = { base: ticket.base, weapon: ticket.weapon, charm: ticket.charm, rank: ticket.rank }; run = newRun(loadout, ticket.seed, ticket.look); practice = false; + saveRun(run); } catch (err) { console.warn('Deepmark: could not open a verified run, starting a practice run', err); await sleep(Math.max(0, MIN_SCENE_MS - (performance.now() - openedAt))); @@ -382,11 +384,14 @@ async function verifyOnChain(finished: RunState): Promise { verifyState = 'failed'; canRetry = !isPermanentFailure(err); failedRun = finished; + // A refusal that waiting cannot fix means the run is gone for good, so there is nothing left to resume. + if (isPermanentFailure(err)) clearSavedRun(); return; } const { hash, outcome } = settled; failedRun = null; + clearSavedRun(); chainTx = hash; chainHeroId = outcome.hero_id ?? null; if (chainHeroId !== null) vaultHeroes = null; @@ -424,6 +429,7 @@ function endExhaustedRun(state: RunState): void { state.relicsThisRun = 0; state.scoreThisRun = 0; practice = true; + clearSavedRun(); chainStatus = 'This run grew too long to be verified.'; } @@ -434,6 +440,74 @@ const DIRS: Partial> = { right: [1, 0], }; +/** Books a finished run into this browser's own progress. The simulation stays free of storage so a log can be replayed safely. */ +function settleLocalProgress(finished: RunState): void { + const extracted = finished.resultKind === 'extracted'; + const result = recordRun({ + floorReached: finished.floorNumber, + relicsKept: finished.relicsThisRun, + score: finished.scoreThisRun, + extracted, + heroId: finished.heroId, + }); + progress = result.progress; + finished.newBest = result.isNewBest && extracted; +} + +/** Puts a run rebuilt from its saved log back on screen, at the point it was left. */ +function presentResumedRun(plan: Exclude, ticket: RunTicket): void { + const resumed = plan.run; + resetMotion(); + resetRelicTravel(); + canvas.classList.remove('board-sunk'); + chainStatus = ''; + chainTx = ''; + chainRank = null; + chainHeroId = null; + canRetry = false; + practice = false; + runWasVeteran = typeof ticket.veteran === 'number'; + displayedRelics = resumed.relicsThisRun; + run = resumed; + if (plan.kind === 'continue') { + startAmbience(resumed.floorNumber); + showBanner('Run restored', FLOOR_NAMES[resumed.floorNumber] ?? ''); + return; + } + // The run had already ended when the page went away: show its result and send the log. + runId += 1; + if (resumed.resultKind === 'dead') canvas.classList.add('board-sunk'); + else preloadShareAssets(HEROES[resumed.heroId].veteranArt, resumed.loadout); + void verifyOnChain(resumed); +} + +let resumeChecked = false; + +/** + * If this browser holds a saved run, asks the contract whether that very run is + * still open and, if so, restores it. A saved run the contract no longer knows + * is dropped. If Stellar cannot be read the save is kept for the next visit. + */ +async function resumeOpenRun(): Promise { + if (resumeChecked || chainRead !== 'ready') return; + resumeChecked = true; + const saved = loadSavedRun(); + if (!saved) return; + try { + const api = await chain(); + const ticket = await api.fetchOpenRun(playerAddress); + if (run || starting) return; + const plan = planResume(saved, ticket); + if (plan.kind === 'discard' || !ticket) { + clearSavedRun(); + return; + } + presentResumedRun(plan, ticket); + } catch (err) { + console.warn('Deepmark: could not check for an open run', err); + } +} + function handleEvent(event: GameEvent): void { switch (event.type) { case 'enemyHit': { @@ -522,7 +596,7 @@ function handleEvent(event: GameEvent): void { case 'extracted': stopAmbience(); sfxExtracted(); - progress = loadProgress(); + if (run) settleLocalProgress(run); runId += 1; if (run) { const heroId = HERO_ORDER[run.loadout.base] ?? 'lantern'; @@ -535,7 +609,7 @@ function handleEvent(event: GameEvent): void { sfxDead(); triggerShake(250, 10); canvas.classList.add('board-sunk'); - progress = loadProgress(); + if (run) settleLocalProgress(run); runId += 1; if (run && !practice) void verifyOnChain(run); break; @@ -719,6 +793,7 @@ onAction((action) => { return; } + const actionsBefore = run.actions.length; if (run.mode === 'combat') { if (action === 'confirm') applyAction(run, ActionCode.Attack); if (action === 'cancel') applyAction(run, ActionCode.Retreat); @@ -738,6 +813,7 @@ onAction((action) => { } if (run.exhausted) endExhaustedRun(run); + if (!practice && run.actions.length !== actionsBefore) saveRun(run); for (const event of drainEvents(run)) handleEvent(event); }); diff --git a/client/src/game/draft.ts b/client/src/game/draft.ts index 3429879..c8b58f6 100644 --- a/client/src/game/draft.ts +++ b/client/src/game/draft.ts @@ -1,5 +1,6 @@ import { CHARMS, WEAPONS } from './items'; import { HERO_ORDER } from './heroes'; +import { RECOMMENDED_KIT } from './kitDifficulty'; import { CLOAK_COUNT, EMBLEM_COUNT, isValidName, type Loadout, type Look } from './loadout'; /** A delver being built in the creator, kept in this browser until a run starts. */ @@ -20,7 +21,7 @@ export function randomName(): string { export function defaultDraft(): Draft { // The kit that clears most often in the balance simulator, so a first descent is a fair one. - return { look: { name: randomName(), emblem: 0, cloak: 0 }, base: 1, weapon: 1, charm: 2 }; + return { look: { name: randomName(), emblem: 0, cloak: 0 }, ...RECOMMENDED_KIT }; } function inRange(value: unknown, count: number): value is number { diff --git a/client/src/game/enemies.ts b/client/src/game/enemies.ts index 16d32cc..fbe98cc 100644 --- a/client/src/game/enemies.ts +++ b/client/src/game/enemies.ts @@ -18,7 +18,7 @@ export const ENEMIES: Record = { name: 'Warden', maxHp: 6, dmgMin: 1, - dmgMax: 3, + dmgMax: 2, color: '#ef5350', size: 40, shape: 'square', diff --git a/client/src/game/items.ts b/client/src/game/items.ts index 7ecacad..d509313 100644 --- a/client/src/game/items.ts +++ b/client/src/game/items.ts @@ -22,7 +22,7 @@ export interface Item extends Modifier { export const WEAPONS: readonly Item[] = [ { id: 0, name: 'Plumb Blade', icon: 'plumb-blade.webp', blurb: 'A sharper floor on every strike, at the cost of a thinner coat.', hp: -2, dmgMin: 1, dmgMax: 0 }, - { id: 1, name: 'Reaver Hook', icon: 'reaver-hook.webp', blurb: 'Wild swings that can end a fight early.', hp: -2, dmgMin: 0, dmgMax: 2 }, + { id: 1, name: 'Reaver Hook', icon: 'reaver-hook.webp', blurb: 'Wild swings that can end a fight early.', hp: -2, dmgMin: 0, dmgMax: 1 }, { id: 2, name: 'Bulwark Pike', icon: 'bulwark-pike.webp', blurb: 'A reach that keeps you standing, with softer blows.', hp: 1, dmgMin: 0, dmgMax: -1 }, ]; diff --git a/client/src/game/kitDifficulty.generated.ts b/client/src/game/kitDifficulty.generated.ts new file mode 100644 index 0000000..10afa16 --- /dev/null +++ b/client/src/game/kitDifficulty.generated.ts @@ -0,0 +1,6 @@ +// Generated by client/scripts/kit-difficulty.ts. Do not edit by hand: run `bun run kits`. +// Full clear rate in percent at rank 0, 2000 runs per kit, indexed [base][weapon][charm]. +export const KIT_CLEAR_PERCENT: readonly (readonly (readonly number[])[])[] = [ + [[43, 32, 44], [43, 32, 43], [31, 47, 29]], + [[47, 33, 50], [46, 28, 47], [44, 44, 42]], +]; diff --git a/client/src/game/kitDifficulty.ts b/client/src/game/kitDifficulty.ts new file mode 100644 index 0000000..a5bde88 --- /dev/null +++ b/client/src/game/kitDifficulty.ts @@ -0,0 +1,54 @@ +import { KIT_CLEAR_PERCENT } from './kitDifficulty.generated'; + +export type Difficulty = 'forgiving' | 'balanced' | 'risky'; + +export interface Kit { + base: number; + weapon: number; + charm: number; +} + +/** A kit that clears at least this often (percent) reads as forgiving. */ +export const FORGIVING_FROM_PERCENT = 45; +/** A kit that clears less often than this (percent) reads as risky. */ +export const RISKY_BELOW_PERCENT = 36; + +export const DIFFICULTY_LABEL: Record = { + forgiving: 'Forgiving', + balanced: 'Balanced', + risky: 'Risky', +}; + +/** How often a fresh delver with this kit clears all three floors, in percent, or null for an unknown kit. */ +export function clearPercent(kit: Kit): number | null { + return KIT_CLEAR_PERCENT[kit.base]?.[kit.weapon]?.[kit.charm] ?? null; +} + +export function difficultyOf(percent: number): Difficulty { + if (percent >= FORGIVING_FROM_PERCENT) return 'forgiving'; + if (percent < RISKY_BELOW_PERCENT) return 'risky'; + return 'balanced'; +} + +function bestKit(): Kit { + let best: Kit = { base: 0, weapon: 0, charm: 0 }; + let bestPercent = -1; + KIT_CLEAR_PERCENT.forEach((weapons, base) => + weapons.forEach((charms, weapon) => + charms.forEach((percent, charm) => { + if (percent > bestPercent) { + best = { base, weapon, charm }; + bestPercent = percent; + } + }) + ) + ); + return best; +} + +/** The kit that clears most often, so a first descent is a fair one. The first of any tie wins. */ +export const RECOMMENDED_KIT: Kit = bestKit(); + +export function isRecommended(kit: Kit): boolean { + return kit.base === RECOMMENDED_KIT.base && kit.weapon === RECOMMENDED_KIT.weapon && kit.charm === RECOMMENDED_KIT.charm; +} diff --git a/client/src/game/savedRun.ts b/client/src/game/savedRun.ts new file mode 100644 index 0000000..b8df5f8 --- /dev/null +++ b/client/src/game/savedRun.ts @@ -0,0 +1,161 @@ +import { isValidLoadout, type Loadout, type Look } from './loadout'; +import { ActionCode, applyAction, drainEvents, MAX_ACTIONS, newRun, type RunState } from './state'; + +/** + * A run the contract has opened but not yet settled, kept in this browser so a + * reload or a closed tab cannot lose a genuine result. It holds exactly what + * the contract needs to replay the run: the seed it issued, the delver and the + * action log. Everything else (board, hit points, relics) is rebuilt by + * replaying the log through the same simulation the contract mirrors. + */ +export interface SavedRun { + seed: number; + loadout: Loadout; + actions: number[]; +} + +/** The part of the contract's open run that a saved run must agree with. */ +export interface OpenTicket { + seed: number; + base: number; + weapon: number; + charm: number; + rank: number; + look: Look; +} + +export type ResumePlan = + | { kind: 'discard' } + /** The run is still being played: carry on from the rebuilt board. */ + | { kind: 'continue'; run: RunState } + /** The run had already ended: its log still has to reach the contract. */ + | { kind: 'submit'; run: RunState }; + +const STORAGE_KEY = 'deepmark:open-run:v1'; +const FORMAT_VERSION = 1; +const SEED_LIMIT = 0x1_0000_0000; +/** Room for the 2,048 action digits plus the delver, with margin. Anything larger is not ours. */ +export const MAX_SAVED_BYTES = 4096; +const ACTION_CODES = Object.values(ActionCode); + +interface StoredRun { + v: number; + seed: number; + base: number; + weapon: number; + charm: number; + rank: number; + /** One digit per action, the same encoding the golden vectors use. */ + actions: string; +} + +function encodeActions(actions: readonly number[]): string { + return actions.join(''); +} + +function decodeActions(text: string): number[] | null { + if (text.length > MAX_ACTIONS) return null; + const actions: number[] = []; + for (const char of text) { + const code = Number(char); + if (!ACTION_CODES.some((valid) => valid === code)) return null; + actions.push(code); + } + return actions; +} + +function isCount(value: unknown): value is number { + return typeof value === 'number' && Number.isInteger(value) && value >= 0; +} + +/** Parses saved text into a run, or null when it is malformed, oversized or from another format. */ +export function parseSavedRun(raw: string): SavedRun | null { + if (raw.length > MAX_SAVED_BYTES) return null; + let value: unknown; + try { + value = JSON.parse(raw); + } catch { + return null; + } + if (!value || typeof value !== 'object') return null; + const stored = value as Partial>; + if (stored.v !== FORMAT_VERSION || typeof stored.actions !== 'string') return null; + const { seed, base, weapon, charm, rank } = stored; + if (!isCount(seed) || seed >= SEED_LIMIT) return null; + if (!isCount(base) || !isCount(weapon) || !isCount(charm) || !isCount(rank)) return null; + const loadout: Loadout = { base, weapon, charm, rank }; + if (!isValidLoadout(loadout)) return null; + const actions = decodeActions(stored.actions); + if (!actions) return null; + return { seed, loadout, actions }; +} + +export function serializeSavedRun(run: RunState): string { + const { base, weapon, charm, rank } = run.loadout; + const stored: StoredRun = { v: FORMAT_VERSION, seed: run.seed, base, weapon, charm, rank, actions: encodeActions(run.actions) }; + return JSON.stringify(stored); +} + +export function saveRun(run: RunState): void { + try { + localStorage.setItem(STORAGE_KEY, serializeSavedRun(run)); + } catch { + // Storage is unavailable (private mode, quota): the run still plays, it just cannot resume after a reload. + } +} + +export function loadSavedRun(): SavedRun | null { + try { + const raw = localStorage.getItem(STORAGE_KEY); + return raw ? parseSavedRun(raw) : null; + } catch { + return null; + } +} + +export function clearSavedRun(): void { + try { + localStorage.removeItem(STORAGE_KEY); + } catch { + // Nothing was stored, or storage is unavailable. Either way there is nothing to clear. + } +} + +/** + * Rebuilds a run by replaying its log from the start, exactly as the contract + * will. Returns null if any action does not apply, which means the saved data + * is not a log this simulation could have produced. + */ +export function replayRun(loadout: Loadout, seed: number, look: Look, actions: readonly number[]): RunState | null { + const state = newRun(loadout, seed, look); + for (const code of actions) { + if (!applyAction(state, code as ActionCode)) return null; + } + // Replaying raises the same events as live play. They describe moments that already + // happened, so they must not reach the sounds, particles or the leaderboard. + drainEvents(state); + return state; +} + +function matchesTicket(saved: SavedRun, ticket: OpenTicket): boolean { + const { loadout } = saved; + return ( + saved.seed === ticket.seed && + loadout.base === ticket.base && + loadout.weapon === ticket.weapon && + loadout.charm === ticket.charm && + loadout.rank === ticket.rank + ); +} + +/** + * Decides what a saved run means now that the contract has been asked which + * run it still holds open. The contract is the authority: a saved run is only + * ever resumed when it describes that very run. + */ +export function planResume(saved: SavedRun | null, ticket: OpenTicket | null): ResumePlan { + if (!saved || !ticket || !matchesTicket(saved, ticket)) return { kind: 'discard' }; + const run = replayRun(saved.loadout, saved.seed, ticket.look, saved.actions); + if (!run) return { kind: 'discard' }; + return { kind: run.mode === 'result' ? 'submit' : 'continue', run }; +} diff --git a/client/src/game/state.ts b/client/src/game/state.ts index 397b810..529e9e7 100644 --- a/client/src/game/state.ts +++ b/client/src/game/state.ts @@ -3,7 +3,6 @@ import { ENEMIES, type EnemyKind } from './enemies'; import { buildFloor, isWalkable, MAX_FLOOR, type FloorLayout, type RelicKind } from './floor'; import { HERO_ORDER, HEROES, type HeroId } from './heroes'; import { statsFor, type Look, type Loadout, type Stats } from './loadout'; -import { recordRun } from './progress'; import { mulberry32, freshSeed, type Rng } from './rng'; export type Mode = 'title' | 'explore' | 'combat' | 'choice' | 'result'; @@ -80,6 +79,7 @@ export interface RunState { actions: number[]; /** True when the run hit `MAX_ACTIONS` and can no longer be verified on chain. */ exhausted: boolean; + /** Set by the app once the local progress is recorded, never by the simulation. */ newBest: boolean; log: string[]; events: GameEvent[]; @@ -156,20 +156,11 @@ function aliveEnemyIndexAt(state: RunState, x: number, y: number): number { function finishRun(state: RunState, kind: ResultKind): void { state.mode = 'result'; state.resultKind = kind; - const relicsKept = kind === 'dead' ? 0 : state.relicsThisRun; if (kind === 'dead') { state.relicsThisRun = 0; state.relicsByKind = { common: 0, rare: 0, core: 0 }; state.scoreThisRun = 0; } - const { isNewBest } = recordRun({ - floorReached: state.floorNumber, - relicsKept, - score: state.scoreThisRun, - extracted: kind === 'extracted', - heroId: state.heroId, - }); - state.newBest = isNewBest && kind === 'extracted'; pushEvent(state, kind === 'extracted' ? { type: 'extracted' } : { type: 'dead' }); } diff --git a/client/src/render/creator.ts b/client/src/render/creator.ts index d65f4fb..59a3f60 100644 --- a/client/src/render/creator.ts +++ b/client/src/render/creator.ts @@ -2,6 +2,7 @@ import { HEROES, HERO_ORDER } from '../game/heroes'; import { CHARMS, WEAPONS, type Item } from '../game/items'; import { isValidName, NAME_MAX, NAME_MIN, statsFor, EMBLEM_COUNT } from '../game/loadout'; import { loadoutOf, randomName, saveDraft, type Draft } from '../game/draft'; +import { clearPercent, difficultyOf, DIFFICULTY_LABEL, isRecommended, RECOMMENDED_KIT, type Difficulty } from '../game/kitDifficulty'; import { cloakCount, cloakFilter, cloakName, cloakSwatch, EMBLEM_NAMES, emblemIcon } from './emblems'; export interface CreatorHooks { @@ -57,11 +58,24 @@ export function mountCreator(host: HTMLElement, initial: Draft, hooks: CreatorHo const previewCopy = el('div', 'creator-preview-copy'); const previewName = el('p', 'creator-preview-name'); const previewStats = el('p', 'creator-preview-stats'); - previewCopy.append(previewName, previewStats); + const previewKit = el('p', 'creator-preview-kit'); + const kitTier = el('span', 'kit-tier'); + const kitOdds = el('span', 'kit-odds'); + previewKit.append(kitTier, kitOdds); + previewCopy.append(previewName, previewStats, previewKit); const previewEmblem = el('span', 'creator-preview-emblem'); preview.append(previewPortrait, previewCopy, previewEmblem); root.append(preview); + // Starter kit guidance: which kit is the fair first descent, and a way back to it. + const kitRow = el('div', 'kit-row'); + const kitNote = el('span', 'field-hint'); + kitNote.setAttribute('role', 'status'); + const useRecommended = el('button', 'btn btn-mini', 'Use the recommended kit'); + useRecommended.type = 'button'; + kitRow.append(kitNote, useRecommended); + root.append(kitRow); + // Name const nameField = el('label', 'field'); nameField.append(el('span', 'field-label', 'Name')); @@ -226,6 +240,18 @@ export function mountCreator(host: HTMLElement, initial: Draft, hooks: CreatorHo previewName.textContent = valid ? name : 'Unnamed delver'; previewStats.textContent = `${stats.maxHp} HP ยท strikes for ${stats.dmgMin} to ${stats.dmgMax}`; previewEmblem.replaceChildren(emblemIcon(draft.look.emblem, 'emblem emblem-large')); + showKitGuidance(); + } + + function showKitGuidance(): void { + const percent = clearPercent(draft); + const tier: Difficulty | null = percent === null ? null : difficultyOf(percent); + kitTier.className = `kit-tier${tier ? ` is-${tier}` : ''}`; + kitTier.textContent = tier ? DIFFICULTY_LABEL[tier] : ''; + kitOdds.textContent = percent === null ? '' : `Clears about ${percent} in 100 descents`; + const recommended = isRecommended(draft); + kitNote.textContent = recommended ? 'The recommended kit for a first descent.' : 'Not sure? One kit clears more often than the rest.'; + useRecommended.hidden = recommended; } nameInput.addEventListener('input', () => { @@ -237,6 +263,10 @@ export function mountCreator(host: HTMLElement, initial: Draft, hooks: CreatorHo nameInput.value = draft.look.name; refresh(); }); + useRecommended.addEventListener('click', () => { + Object.assign(draft, RECOMMENDED_KIT); + refresh(); + }); back.addEventListener('click', () => hooks.onBack()); root.addEventListener('submit', (event) => { event.preventDefault(); diff --git a/client/src/stellar/config.ts b/client/src/stellar/config.ts index 223f6c9..305e58f 100644 --- a/client/src/stellar/config.ts +++ b/client/src/stellar/config.ts @@ -4,23 +4,24 @@ export const STELLAR_CONFIG = { horizonUrl: 'https://horizon-testnet.stellar.org', friendbotUrl: 'https://friendbot.stellar.org', explorerUrl: 'https://stellar.expert/explorer/testnet', - contractId: 'CDL7OO7MGCNMHZBF5QPJCMBLLY3ORR44TJ54ON44M25YUX6UZ7L46UZW', + contractId: 'CDEAFSBG7JBN6XMP4HRL7LFBL7L67K4RJK3CIRBV23W3TJQCEADZ7R6N', }; export type RelicKind = 'common' | 'rare' | 'core'; /** * The three relic assets. Each is a classic Stellar asset wrapped by a Stellar - * Asset Contract whose admin is the game contract, and the issuer is locked, - * so only a verified extraction can mint one. + * Asset Contract whose admin is the game contract. The issuer account is locked + * (master weight zero, no other signer), so only a verified extraction can + * mint one. Verified on Horizon when this deployment was made. */ export const RELIC_ASSETS: Record = { - common: { code: 'DMCOMMON', contractId: 'CCLLVBSEXPARVM6MP5HAXOSM3FGTUR3NHZSSRFRPNJSO4KWAPTMDZS7B' }, - rare: { code: 'DMRARE', contractId: 'CCMDNYSZMHBDV3XBSGSQ5QVSNCVYMAGGPF6SAK3QDARIBT246FFYOIBT' }, - core: { code: 'DMCORE', contractId: 'CDIYYZSMMSDXTTA5OA2C2QUJGMJRX4UP2UBR3QCUV44GJZTJBIS4BPDR' }, + common: { code: 'DMCOMMON', contractId: 'CAAFYL2QZU274NLA2IFDB4L6SUNVQKT6OLB6UXGL5L5LMQCFJXSUYFO3' }, + rare: { code: 'DMRARE', contractId: 'CC4RQ4HLTPTXMCZ3OKDL546XXJGOQ5PI2ET6U33C2XTO4BTS6AJV4ANA' }, + core: { code: 'DMCORE', contractId: 'CC27JVKNDSUM5FDGP4JZDZTKGHJ5RJV7XHJGITTUGOUFE4DVPN6PGLMI' }, }; -export const RELIC_ISSUER = 'GAH27KP7NDO7HSYGIUFT7O5ONGW7M6RKCXRXIHCTLWKBM2PNU6336YMP'; +export const RELIC_ISSUER = 'GAD752QFRTNEMQW2SELGHQ5VAMIV3O7H4XRLJN2K2TQFGGXKNFG7CQUK'; /** Classic assets carry seven decimals. The contract mints whole relics. */ export const RELIC_UNIT = 10_000_000n; diff --git a/client/src/stellar/contract.ts b/client/src/stellar/contract.ts index f9870b8..b075b77 100644 --- a/client/src/stellar/contract.ts +++ b/client/src/stellar/contract.ts @@ -194,6 +194,13 @@ export async function fetchRecord(address: string): Promise return assembled.result ?? null; } +/** The run the contract still holds open for a player, if any. */ +export async function fetchOpenRun(address: string): Promise { + const client = await readClient(); + const assembled = await client.get_open_run({ player: address }); + return assembled.result ?? null; +} + export async function fetchTopFresh(): Promise { const client = await readClient(); const assembled = await client.top_fresh(); diff --git a/client/src/styles.css b/client/src/styles.css index 5b95ce5..0d7a2fe 100644 --- a/client/src/styles.css +++ b/client/src/styles.css @@ -667,6 +667,14 @@ body:has(#overlay:not([hidden])) .touch { .creator-preview-name { margin: 0; font-family: Palatino, "Iowan Old Style", Georgia, serif; font-size: 1.2rem; } .creator-preview-stats { margin: 2px 0 0; color: var(--muted); font-size: 0.86rem; } .creator-preview-emblem { color: var(--gold); } +.creator-preview-kit { margin: 4px 0 0; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; font-size: 0.8rem; } +.kit-tier { padding: 1px 10px; border-radius: 99px; border: 1px solid currentColor; font-size: 0.74rem; letter-spacing: 0.08em; text-transform: uppercase; } +.kit-tier:empty { display: none; } +.kit-tier.is-forgiving { color: var(--safe); } +.kit-tier.is-balanced { color: var(--gold); } +.kit-tier.is-risky { color: var(--danger); } +.kit-odds { color: var(--muted); } +.kit-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; } @media (max-width: 700px) { .pick-grid, .pick-grid-2 { grid-template-columns: 1fr; } diff --git a/client/tests/kitDifficulty.test.ts b/client/tests/kitDifficulty.test.ts new file mode 100644 index 0000000..4024658 --- /dev/null +++ b/client/tests/kitDifficulty.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, test } from 'bun:test'; +import { HERO_ORDER } from '../src/game/heroes'; +import { CHARMS, WEAPONS } from '../src/game/items'; +import { KIT_CLEAR_PERCENT } from '../src/game/kitDifficulty.generated'; +import { + clearPercent, + difficultyOf, + FORGIVING_FROM_PERCENT, + isRecommended, + RECOMMENDED_KIT, + RISKY_BELOW_PERCENT, + type Difficulty, +} from '../src/game/kitDifficulty'; + +/** The band every kit must stay inside at rank 0, so no first descent is hopeless or free. */ +const MIN_CLEAR_PERCENT = 25; +const MAX_CLEAR_PERCENT = 60; + +function allKits(): { base: number; weapon: number; charm: number; percent: number }[] { + return HERO_ORDER.flatMap((_, base) => + WEAPONS.flatMap((_, weapon) => CHARMS.map((_, charm) => ({ base, weapon, charm, percent: clearPercent({ base, weapon, charm }) ?? NaN }))) + ); +} + +describe('kit difficulty table', () => { + test('has a measurement for every kit and none for kits that do not exist', () => { + expect(allKits().every((kit) => Number.isFinite(kit.percent))).toBe(true); + expect(clearPercent({ base: HERO_ORDER.length, weapon: 0, charm: 0 })).toBeNull(); + expect(clearPercent({ base: 0, weapon: WEAPONS.length, charm: 0 })).toBeNull(); + expect(clearPercent({ base: 0, weapon: 0, charm: CHARMS.length })).toBeNull(); + expect(KIT_CLEAR_PERCENT.length).toBe(HERO_ORDER.length); + }); + + test('keeps every kit between 25 and 60 percent full clears at rank 0', () => { + for (const kit of allKits()) { + expect(kit.percent).toBeGreaterThanOrEqual(MIN_CLEAR_PERCENT); + expect(kit.percent).toBeLessThanOrEqual(MAX_CLEAR_PERCENT); + } + }); + + test('recommends the kit that clears most often', () => { + const best = Math.max(...allKits().map((kit) => kit.percent)); + expect(clearPercent(RECOMMENDED_KIT)).toBe(best); + expect(isRecommended(RECOMMENDED_KIT)).toBe(true); + }); + + test('uses all three labels, split at the documented thresholds', () => { + const tiers = new Set(allKits().map((kit) => difficultyOf(kit.percent))); + expect(tiers).toEqual(new Set(['forgiving', 'balanced', 'risky'])); + expect(difficultyOf(FORGIVING_FROM_PERCENT)).toBe('forgiving'); + expect(difficultyOf(FORGIVING_FROM_PERCENT - 1)).toBe('balanced'); + expect(difficultyOf(RISKY_BELOW_PERCENT)).toBe('balanced'); + expect(difficultyOf(RISKY_BELOW_PERCENT - 1)).toBe('risky'); + }); +}); diff --git a/client/tests/savedRun.test.ts b/client/tests/savedRun.test.ts new file mode 100644 index 0000000..98250ce --- /dev/null +++ b/client/tests/savedRun.test.ts @@ -0,0 +1,194 @@ +import { beforeEach, describe, expect, test } from 'bun:test'; +import { playBalanced } from '../scripts/policy'; +import type { Loadout } from '../src/game/loadout'; +import { + clearSavedRun, + loadSavedRun, + MAX_SAVED_BYTES, + parseSavedRun, + planResume, + replayRun, + saveRun, + serializeSavedRun, + type OpenTicket, + type SavedRun, +} from '../src/game/savedRun'; +import { ActionCode, MAX_ACTIONS, newRun, type RunState } from '../src/game/state'; +import { installMemoryStorage } from './storage'; + +const LOADOUT: Loadout = { base: 1, weapon: 0, charm: 2, rank: 0 }; +const LOOK = { name: 'Ash', emblem: 1, cloak: 2 }; +const SEED = 20_261_005; +const MID_RUN_ACTIONS = 40; + +function ticketFor(run: RunState): OpenTicket { + const { base, weapon, charm, rank } = run.loadout; + return { seed: run.seed, base, weapon, charm, rank, look: LOOK }; +} + +/** A fresh run played by the balanced policy until it holds `maxActions` actions (or ends). */ +function played(seed: number, maxActions: number): RunState { + return playBalanced(newRun(LOADOUT, seed, LOOK), maxActions); +} + +/** Everything a player can see or that decides what happens next, including the next dice roll. */ +function snapshot(run: RunState): unknown { + const { rng, log, events, ...rest } = run; + return { ...rest, log, nextRoll: rng() }; +} + +function winningSeed(): number { + for (let seed = 1; seed < 500; seed += 1) { + if (played(seed, Infinity).resultKind === 'extracted') return seed; + } + throw new Error('no winning seed found in range'); +} + +beforeEach(() => { + installMemoryStorage(); +}); + +describe('replaying a saved run', () => { + test('rebuilds the exact board, hit points, relics and dice of a run left mid descent', () => { + const live = played(SEED, MID_RUN_ACTIONS); + expect(live.mode).not.toBe('result'); + expect(live.actions.length).toBe(MID_RUN_ACTIONS); + + saveRun(live); + const saved = loadSavedRun(); + expect(saved).not.toBeNull(); + const rebuilt = replayRun(saved!.loadout, saved!.seed, LOOK, saved!.actions); + + expect(rebuilt).not.toBeNull(); + expect(snapshot(rebuilt!)).toEqual(snapshot(live)); + }); + + test('rebuilds a run from every point of a full descent', () => { + const full = played(SEED, Infinity); + for (let length = 0; length <= full.actions.length; length += 1) { + const live = played(SEED, length); + const rebuilt = replayRun(LOADOUT, SEED, LOOK, live.actions); + expect(snapshot(rebuilt!)).toEqual(snapshot(live)); + } + }); + + test('raises no events, so a restored run makes no sound and books no progress', () => { + const live = played(winningSeed(), Infinity); + const rebuilt = replayRun(live.loadout, live.seed, LOOK, live.actions); + expect(rebuilt!.events).toEqual([]); + }); + + test('rejects a log containing an action the simulation would refuse', () => { + expect(replayRun(LOADOUT, SEED, LOOK, [ActionCode.Descend])).toBeNull(); + expect(replayRun(LOADOUT, SEED, LOOK, [ActionCode.Attack])).toBeNull(); + }); +}); + +describe('saved data', () => { + test('stays far below the size cap even for the longest run the contract accepts', () => { + const longest: SavedRun = { seed: 0xffff_ffff, loadout: { base: 1, weapon: 2, charm: 2, rank: 3 }, actions: Array(MAX_ACTIONS).fill(ActionCode.Retreat) }; + const run = newRun(longest.loadout, longest.seed, LOOK); + run.actions = longest.actions; + expect(serializeSavedRun(run).length).toBeLessThan(MAX_SAVED_BYTES); + expect(parseSavedRun(serializeSavedRun(run))).not.toBeNull(); + }); + + test('is refused above the size cap, past the action cap, or with unknown action codes', () => { + const good = JSON.parse(serializeSavedRun(played(SEED, MID_RUN_ACTIONS))) as Record; + expect(parseSavedRun(JSON.stringify({ ...good, actions: '0'.repeat(MAX_ACTIONS + 1) }))).toBeNull(); + expect(parseSavedRun(JSON.stringify({ ...good, actions: '0189' }))).toBeNull(); + expect(parseSavedRun(JSON.stringify({ ...good, padding: 'x'.repeat(MAX_SAVED_BYTES) }))).toBeNull(); + }); + + test('is refused when malformed, from another format, or describing an impossible delver', () => { + const good = JSON.parse(serializeSavedRun(played(SEED, MID_RUN_ACTIONS))) as Record; + expect(parseSavedRun('not json')).toBeNull(); + expect(parseSavedRun('null')).toBeNull(); + expect(parseSavedRun(JSON.stringify({ ...good, v: 2 }))).toBeNull(); + expect(parseSavedRun(JSON.stringify({ ...good, seed: -1 }))).toBeNull(); + expect(parseSavedRun(JSON.stringify({ ...good, seed: 2 ** 32 }))).toBeNull(); + expect(parseSavedRun(JSON.stringify({ ...good, seed: 1.5 }))).toBeNull(); + expect(parseSavedRun(JSON.stringify({ ...good, base: 9 }))).toBeNull(); + expect(parseSavedRun(JSON.stringify({ ...good, rank: 4 }))).toBeNull(); + expect(parseSavedRun(JSON.stringify({ ...good, actions: 12 }))).toBeNull(); + }); + + test('round trips through storage and can be cleared', () => { + const live = played(SEED, MID_RUN_ACTIONS); + saveRun(live); + expect(loadSavedRun()).toEqual({ seed: SEED, loadout: LOADOUT, actions: live.actions }); + clearSavedRun(); + expect(loadSavedRun()).toBeNull(); + }); + + test('is simply absent when storage is unavailable', () => { + (globalThis as { localStorage?: unknown }).localStorage = { + getItem: () => { + throw new Error('blocked'); + }, + setItem: () => { + throw new Error('blocked'); + }, + removeItem: () => { + throw new Error('blocked'); + }, + }; + expect(() => saveRun(played(SEED, 5))).not.toThrow(); + expect(loadSavedRun()).toBeNull(); + expect(() => clearSavedRun()).not.toThrow(); + }); +}); + +describe('deciding what to do with a saved run', () => { + test('continues a run the contract still holds open', () => { + const live = played(SEED, MID_RUN_ACTIONS); + const plan = planResume(loadAfterSave(live), ticketFor(live)); + expect(plan.kind).toBe('continue'); + if (plan.kind === 'continue') expect(snapshot(plan.run)).toEqual(snapshot(live)); + }); + + test('submits the log of a run that had already been won when the tab closed', () => { + const won = played(winningSeed(), Infinity); + expect(won.resultKind).toBe('extracted'); + const plan = planResume(loadAfterSave(won), ticketFor(won)); + expect(plan.kind).toBe('submit'); + if (plan.kind === 'submit') { + expect(plan.run.resultKind).toBe('extracted'); + expect(plan.run.actions).toEqual(won.actions); + expect(plan.run.scoreThisRun).toBe(won.scoreThisRun); + } + }); + + test('also submits a run that ended in a defeat, so the contract can record it', () => { + let seed = 1; + while (played(seed, Infinity).resultKind !== 'dead') seed += 1; + const lost = played(seed, Infinity); + expect(planResume(loadAfterSave(lost), ticketFor(lost)).kind).toBe('submit'); + }); + + test('discards a saved run when the contract holds no open run', () => { + expect(planResume(loadAfterSave(played(SEED, MID_RUN_ACTIONS)), null).kind).toBe('discard'); + }); + + test('discards a saved run for another seed', () => { + const live = played(SEED, MID_RUN_ACTIONS); + expect(planResume(loadAfterSave(live), { ...ticketFor(live), seed: SEED + 1 }).kind).toBe('discard'); + }); + + test('discards a saved run for another delver', () => { + const live = played(SEED, MID_RUN_ACTIONS); + const ticket = ticketFor(live); + for (const other of [{ base: 0 }, { weapon: 1 }, { charm: 0 }, { rank: 2 }]) { + expect(planResume(loadAfterSave(live), { ...ticket, ...other }).kind).toBe('discard'); + } + }); + + test('discards when there is nothing saved', () => { + expect(planResume(null, { seed: SEED, ...LOADOUT, look: LOOK }).kind).toBe('discard'); + }); +}); + +function loadAfterSave(run: RunState): SavedRun | null { + saveRun(run); + return loadSavedRun(); +} diff --git a/client/tests/storage.ts b/client/tests/storage.ts new file mode 100644 index 0000000..cafcbae --- /dev/null +++ b/client/tests/storage.ts @@ -0,0 +1,10 @@ +/** An in-memory stand-in for `localStorage`, so storage code runs under `bun test`. */ +export function installMemoryStorage(): Map { + const store = new Map(); + (globalThis as { localStorage?: unknown }).localStorage = { + getItem: (key: string): string | null => store.get(key) ?? null, + setItem: (key: string, value: string): void => void store.set(key, value), + removeItem: (key: string): void => void store.delete(key), + }; + return store; +} diff --git a/client/tsconfig.json b/client/tsconfig.json index 0c1ca67..fd02ba0 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -13,5 +13,5 @@ "isolatedModules": true, "esModuleInterop": true }, - "include": ["src"] + "include": ["src", "tests"] } diff --git a/contracts/deepmark-contract/README.md b/contracts/deepmark-contract/README.md index ad929fb..691ea45 100644 --- a/contracts/deepmark-contract/README.md +++ b/contracts/deepmark-contract/README.md @@ -8,7 +8,7 @@ Scaffolded with `cougr new --template starter` for the crate layout, `wasm32v1-n | Function | Parameters | Returns | Description | | --- | --- | --- | --- | -| `__constructor` | `relic_common`, `relic_rare`, `relic_core`: `Address` | none | Sets the three relic Stellar Asset Contracts once. No admin, no upgrade. | +| `__constructor` | `admin`, `relic_common`, `relic_rare`, `relic_core`: `Address` | none | Sets the admin and the three relic Stellar Asset Contracts once. The admin must sign the deployment. | | `start_run` | `player: Address`, `choice: HeroChoice` | `Result` | Opens a run, issues its seed on chain and fixes the delver: `Fresh(look, base, weapon, charm)` or `Veteran(hero_id)` that the player owns. Starting while a run is open forfeits it. Cooldown between starts. Requires the player's signature. | | `finish_run` | `player: Address`, `actions: Bytes` | `Result` | Replays the action log, derives the result, updates the profile and the right leaderboard, mints banked relics, and after a full clear earns the delver (fresh) or adds a victory (veteran). Consumes the open run. Requires the player's signature. | | `verify_run` | `base`, `weapon`, `charm`, `rank`, `seed`: `u32`, `actions: Bytes` | `Result` | Pure replay with no writes. Anyone can audit a run. | @@ -19,6 +19,11 @@ Scaffolded with `cougr new --template starter` for the crate layout, `wasm32v1-n | `player_count` | none | `u32` | Distinct players that ever opened a run. Not capped. | | `players` | `start: u32`, `limit: u32` | `Vec` | Profiles in first-seen order, at most 50 per call. | | `relic_tokens` | none | `RelicTokens` | The three relic asset contracts. | +| `admin` | none | `Option
` | Who can upgrade the code, or `None` once the role was renounced. | +| `version` | none | `u32` | The contract release, bumped whenever behavior changes. | +| `upgrade` | `new_wasm_hash: BytesN<32>` | `Result<(), Error>` | Replaces the contract code with an uploaded wasm. Storage is kept. Admin only. Emits `upgraded`. | +| `set_admin` | `new_admin: Address` | `Result<(), Error>` | Hands the admin role over. Both the current and the new admin must sign, so the role cannot go to a mistyped address. Emits `admin_changed`. | +| `renounce_admin` | none | `Result<(), Error>` | Gives the role up for good. The code can never change afterwards. Emits `admin_renounced`. | | `hero` | `id: u32` | `Option` | A delver: look, kit, victories, and the verified first clear that earned it. | | `owner_of` | `id: u32` | `Option
` | Who owns a delver. | | `heroes_of` | `owner: Address` | `Vec` | Every delver an address owns, at most 64. | @@ -26,7 +31,7 @@ Scaffolded with `cougr new --template starter` for the crate layout, `wasm32v1-n | `hero_count` | none | `u32` | How many delvers have ever been earned. | | `transfer` | `from`, `to: Address`, `id: u32` | `Result<(), Error>` | Give a delver away. Requires the owner's signature. | -Errors: `InvalidHero` 1, `RunNotStarted` 2, `TooManyActions` 3, `InvalidAction` 4, `RunIncomplete` 5, `TrailingActions` 6, `TooFast` 7, `StartCooldown` 8, `InvalidName` 9, `HeroNotFound` 10, `NotHeroOwner` 11. +Errors: `InvalidHero` 1, `RunNotStarted` 2, `TooManyActions` 3, `InvalidAction` 4, `RunIncomplete` 5, `TrailingActions` 6, `TooFast` 7, `StartCooldown` 8, `InvalidName` 9, `HeroNotFound` 10, `NotHeroOwner` 11, `Frozen` 12 (the admin role was renounced). ## Delvers and ownership @@ -42,6 +47,7 @@ One byte per action: `0` up, `1` down, `2` left, `3` right, `4` attack, `5` retr ``` lib.rs entrypoints: require_auth, replay, persist, mint, emit events + admin.rs admin role: upgrade, hand over, renounce, with their events components.rs data shapes and limits rng.rs Mulberry32, bit exact port of client/src/game/rng.ts world.rs delvers, enemies and the three floors @@ -56,14 +62,18 @@ tests/golden.rs replays 400 runs played by the real TypeScript game - **Seed.** Issued on chain from ledger data, the player's run counter and address. - **Replay protection.** Finishing consumes the open run. - **Authorization.** Both writing entrypoints require the player's own signature. -- **Relics.** Minted through Stellar Asset Contracts whose admin is this contract. The issuer account is locked after deployment, so nothing else can mint. A mint uses `try_mint`, so a player without a trustline never loses a verified result. -- **No admin and no upgrade path.** There is nothing valuable to protect and a key would only add attack surface. +- **Relics.** Minted through Stellar Asset Contracts whose admin is this contract. A release deployment locks the issuer account (master weight zero, no other signer), which `deploy-testnet.sh --lock` verifies on Horizon, including that the issuer's own key can no longer sign. A mint uses `try_mint`, so a player without a trustline never loses a verified result. +- **Admin and upgrade path.** The deployer is the admin and can replace the code with `upgrade`. This is a real trust assumption: an admin could upgrade to code that mints relics without a replay, rewrites the leaderboard or moves delvers. Replay verification protects players from forged logs, not from the admin while the role exists. The role is meant to be temporary: + - `renounce_admin` removes it for good, after which `upgrade` and `set_admin` fail with `Frozen` and the deployed code is final. + - Before a mainnet release, either renounce, or `set_admin` to a multisig account so no single key can change the code. + - `set_admin` needs both signatures so the role cannot be lost to a mistyped address. + - Storage layout is not versioned: an upgrade must keep every `DataKey` and stored struct readable. `version()` tells a client which release runs. -Not covered: seed rerolling is limited by a cooldown and a visible abandoned run counter, not eliminated, and a solver can still play a legal perfect run. There is no third party audit. Review that did happen: 63 unit and entrypoint tests, 600 cross-language vectors with random kits, a mutation check that the vectors catch RNG drift, and live testnet attempts to replay a log, forge a floor 3 run and restart too soon, all rejected. +Not covered: seed rerolling is limited by a cooldown and a visible abandoned run counter, not eliminated, and a solver can still play a legal perfect run. There is no third party audit. Review that did happen: 71 unit and entrypoint tests (including the admin paths), 600 cross-language vectors with random kits, a mutation check that the vectors catch RNG drift, and live testnet attempts to replay a log, forge a floor 3 run and restart too soon, all rejected. ## Storage -Persistent storage for profiles, the player index and the top list, each with its TTL extended on write. The open run lives in temporary storage and expires on its own after two days. The number of players is not capped because each player is an independent entry. +Persistent storage for profiles, the player index and the top list, each with its TTL extended on write. Instance storage holds the admin and the relic tokens, and its TTL is extended on every `start_run` and `finish_run`, which keeps the contract instance and its code alive while the game is played. The open run lives in temporary storage and expires on its own after two days. The number of players is not capped because each player is an independent entry. ## Build and test diff --git a/contracts/deepmark-contract/src/admin.rs b/contracts/deepmark-contract/src/admin.rs new file mode 100644 index 0000000..209eec6 --- /dev/null +++ b/contracts/deepmark-contract/src/admin.rs @@ -0,0 +1,285 @@ +//! Administration: who may replace the contract code, and how that power is +//! given up for good. +//! +//! The admin is set once at deployment and kept in instance storage. It can +//! upgrade the code, hand the role to another address, or renounce it. After a +//! renounce there is no admin and the code can never change again. + +use crate::components::DataKey; +use crate::Error; +use soroban_sdk::{contractevent, Address, BytesN, Env}; + +/// Topic: ("upgraded", admin). The contract code was replaced. +#[contractevent] +pub struct Upgraded { + #[topic] + pub admin: Address, + pub wasm_hash: BytesN<32>, +} + +/// Topic: ("admin_changed", previous). The admin role moved to another address. +#[contractevent] +pub struct AdminChanged { + #[topic] + pub previous: Address, + pub next: Address, +} + +/// Topic: ("admin_renounced", previous). The contract is now frozen for good. +#[contractevent] +pub struct AdminRenounced { + #[topic] + pub previous: Address, +} + +/// The current admin, or `None` once the role was renounced. +pub fn current(env: &Env) -> Option
{ + env.storage().instance().get(&DataKey::Admin) +} + +/// Record the first admin. Only the constructor calls this. +pub fn init(env: &Env, admin: &Address) { + env.storage().instance().set(&DataKey::Admin, admin); +} + +/// Authenticate the admin, or fail with `Frozen` if there is none. +pub fn require(env: &Env) -> Result { + let admin = current(env).ok_or(Error::Frozen)?; + admin.require_auth(); + Ok(admin) +} + +/// Move the role. Both the current and the new admin must sign, so the role +/// can never be handed to a mistyped address nobody controls. +pub fn transfer(env: &Env, next: &Address) -> Result<(), Error> { + let previous = require(env)?; + next.require_auth(); + init(env, next); + AdminChanged { + previous, + next: next.clone(), + } + .publish(env); + Ok(()) +} + +/// Give the role up. There is no way back. +pub fn renounce(env: &Env) -> Result<(), Error> { + let previous = require(env)?; + env.storage().instance().remove(&DataKey::Admin); + AdminRenounced { previous }.publish(env); + Ok(()) +} + +/// Replace the contract code with an uploaded wasm. Storage is kept as is. +pub fn upgrade(env: &Env, wasm_hash: &BytesN<32>) -> Result<(), Error> { + let admin = require(env)?; + env.deployer() + .update_current_contract_wasm(wasm_hash.clone()); + Upgraded { + admin, + wasm_hash: wasm_hash.clone(), + } + .publish(env); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::{AdminChanged, AdminRenounced, Upgraded}; + use crate::components::CONTRACT_VERSION; + use crate::{DeepmarkContract, DeepmarkContractClient, Error}; + use soroban_sdk::{ + testutils::{Address as _, Events as _}, + Address, BytesN, Env, Event as _, + }; + use std::panic::{catch_unwind, AssertUnwindSafe}; + + struct Deployed<'a> { + env: Env, + client: DeepmarkContractClient<'a>, + admin: Address, + } + + /// Deploys with the constructor signature mocked, then drops every mocked + /// authorization so each test states exactly who signs what. + fn deployed<'a>() -> Deployed<'a> { + let env = Env::default(); + let admin = Address::generate(&env); + let relics = [ + Address::generate(&env), + Address::generate(&env), + Address::generate(&env), + ]; + env.mock_all_auths(); + let id = env.register( + DeepmarkContract, + ( + admin.clone(), + relics[0].clone(), + relics[1].clone(), + relics[2].clone(), + ), + ); + env.set_auths(&[]); + let client = DeepmarkContractClient::new(&env, &id); + Deployed { env, client, admin } + } + + fn unknown_wasm(env: &Env) -> BytesN<32> { + BytesN::from_array(env, &[7u8; 32]) + } + + /// The smallest module the host accepts as a contract: the wasm header and + /// one custom section naming the environment interface it targets. It has + /// no functions, which is enough to exercise the upgrade path. + fn minimal_contract_wasm() -> std::vec::Vec { + const WASM_HEADER: [u8; 8] = *b"\0asm\x01\0\0\0"; + const CUSTOM_SECTION_ID: u8 = 0; + const ENV_META_NAME: &[u8] = b"contractenvmetav0"; + const INTERFACE_VERSION_ENTRY: u32 = 0; + const PROTOCOL: u32 = 25; + const PRE_RELEASE: u32 = 0; + + let mut payload = std::vec![ENV_META_NAME.len() as u8]; + payload.extend_from_slice(ENV_META_NAME); + for word in [INTERFACE_VERSION_ENTRY, PROTOCOL, PRE_RELEASE] { + payload.extend_from_slice(&word.to_be_bytes()); + } + let mut wasm = WASM_HEADER.to_vec(); + wasm.push(CUSTOM_SECTION_ID); + wasm.push(payload.len() as u8); + wasm.extend_from_slice(&payload); + wasm + } + + #[test] + fn the_constructor_records_the_admin_and_the_release() { + let d = deployed(); + assert_eq!(d.client.admin(), Some(d.admin.clone())); + assert_eq!(d.client.version(), CONTRACT_VERSION); + } + + #[test] + fn deploying_needs_the_admin_signature() { + let env = Env::default(); + let admin = Address::generate(&env); + let relic = Address::generate(&env); + let result = catch_unwind(AssertUnwindSafe(|| { + env.register( + DeepmarkContract, + (admin.clone(), relic.clone(), relic.clone(), relic.clone()), + ); + })); + assert!(result.is_err()); + } + + #[test] + fn nobody_without_the_admin_signature_can_change_anything() { + let d = deployed(); + let stranger = Address::generate(&d.env); + + assert!(d.client.try_upgrade(&unknown_wasm(&d.env)).is_err()); + assert!(d.client.try_set_admin(&stranger).is_err()); + assert!(d.client.try_renounce_admin().is_err()); + assert_eq!(d.client.admin(), Some(d.admin.clone())); + } + + #[test] + fn the_admin_can_upgrade_and_the_swap_is_announced() { + let d = deployed(); + let hash = d + .env + .deployer() + .upload_contract_wasm(minimal_contract_wasm().as_slice()); + d.env.mock_all_auths(); + + d.client.upgrade(&hash); + + let auths = d.env.auths(); + assert_eq!(auths.len(), 1); + assert_eq!(auths[0].0, d.admin); + assert_eq!( + d.env.events().all(), + [Upgraded { + admin: d.admin.clone(), + wasm_hash: hash, + } + .to_xdr(&d.env, &d.client.address)] + ); + } + + #[test] + fn an_upgrade_to_code_that_was_never_uploaded_is_refused() { + let d = deployed(); + d.env.mock_all_auths(); + assert!(d.client.try_upgrade(&unknown_wasm(&d.env)).is_err()); + } + + #[test] + fn handing_over_the_role_needs_both_signatures() { + let d = deployed(); + let next = Address::generate(&d.env); + + d.env.mock_all_auths(); + d.client.set_admin(&next); + + let signers: std::vec::Vec
= d.env.auths().into_iter().map(|a| a.0).collect(); + assert!(signers.contains(&d.admin)); + assert!(signers.contains(&next)); + assert_eq!( + d.env.events().all(), + [AdminChanged { + previous: d.admin.clone(), + next: next.clone(), + } + .to_xdr(&d.env, &d.client.address)] + ); + assert_eq!(d.client.admin(), Some(next.clone())); + + // The previous admin lost the role. + d.env.set_auths(&[]); + assert!(d.client.try_renounce_admin().is_err()); + } + + #[test] + fn a_new_admin_that_did_not_sign_cannot_be_installed() { + let d = deployed(); + let next = Address::generate(&d.env); + d.env.mock_auths(&[soroban_sdk::testutils::MockAuth { + address: &d.admin, + invoke: &soroban_sdk::testutils::MockAuthInvoke { + contract: &d.client.address, + fn_name: "set_admin", + args: soroban_sdk::vec![&d.env, soroban_sdk::IntoVal::into_val(&next, &d.env)], + sub_invokes: &[], + }, + }]); + + assert!(d.client.try_set_admin(&next).is_err()); + assert_eq!(d.client.admin(), Some(d.admin.clone())); + } + + #[test] + fn renouncing_freezes_the_contract_for_good() { + let d = deployed(); + d.env.mock_all_auths(); + d.client.renounce_admin(); + + assert_eq!( + d.env.events().all(), + [AdminRenounced { + previous: d.admin.clone(), + } + .to_xdr(&d.env, &d.client.address)] + ); + assert_eq!(d.client.admin(), None); + let stranger = Address::generate(&d.env); + assert_eq!( + d.client.try_upgrade(&unknown_wasm(&d.env)), + Err(Ok(Error::Frozen)) + ); + assert_eq!(d.client.try_set_admin(&stranger), Err(Ok(Error::Frozen))); + assert_eq!(d.client.try_renounce_admin(), Err(Ok(Error::Frozen))); + } +} diff --git a/contracts/deepmark-contract/src/components.rs b/contracts/deepmark-contract/src/components.rs index e9993c0..de3de63 100644 --- a/contracts/deepmark-contract/src/components.rs +++ b/contracts/deepmark-contract/src/components.rs @@ -161,6 +161,7 @@ pub struct RelicTokens { #[contracttype] pub enum DataKey { Relics, + Admin, PlayerCount, PlayerAt(u32), Record(Address), @@ -176,6 +177,10 @@ pub enum DataKey { /// so a wallet shows `1.0000000 DMCOMMON`, not a dust amount. pub const RELIC_UNIT: i128 = 10_000_000; +/// Bumped by hand whenever a release changes behavior, so a client can tell +/// which code is running after an upgrade. +pub const CONTRACT_VERSION: u32 = 1; + pub const DAY_IN_LEDGERS: u32 = 17_280; pub const TTL_THRESHOLD: u32 = 30 * DAY_IN_LEDGERS; pub const TTL_EXTEND_TO: u32 = 120 * DAY_IN_LEDGERS; diff --git a/contracts/deepmark-contract/src/lib.rs b/contracts/deepmark-contract/src/lib.rs index 473b71a..263023a 100644 --- a/contracts/deepmark-contract/src/lib.rs +++ b/contracts/deepmark-contract/src/lib.rs @@ -18,6 +18,7 @@ #[cfg(test)] extern crate std; +pub mod admin; pub mod components; pub mod rng; pub mod sim; @@ -28,13 +29,14 @@ pub mod world; use components::{ DataKey, FreshHero, Hero, HeroChoice, Look, PlayerRecord, PlayerSummary, RelicTokens, - RunOutcome, RunTicket, TopEntry, ACTIONS_PER_LEDGER, MAX_ACTIONS, MAX_HEROES_PER_OWNER, - MAX_PAGE, RELIC_UNIT, RUN_TTL, START_COOLDOWN_LEDGERS, TOP_SIZE, TTL_EXTEND_TO, TTL_THRESHOLD, + RunOutcome, RunTicket, TopEntry, ACTIONS_PER_LEDGER, CONTRACT_VERSION, MAX_ACTIONS, + MAX_HEROES_PER_OWNER, MAX_PAGE, RELIC_UNIT, RUN_TTL, START_COOLDOWN_LEDGERS, TOP_SIZE, + TTL_EXTEND_TO, TTL_THRESHOLD, }; use sim::{Ending, Outcome, SimError}; use soroban_sdk::{ contract, contracterror, contractevent, contractimpl, token::StellarAssetClient, xdr::ToXdr, - Address, Bytes, Env, String, Vec, + Address, Bytes, BytesN, Env, String, Vec, }; use systems::{apply_outcome, rank_key}; use world::{Loadout, MAX_FLOOR, MAX_RANK}; @@ -54,6 +56,8 @@ pub enum Error { InvalidName = 9, HeroNotFound = 10, NotHeroOwner = 11, + /// The admin role was renounced, so the contract can no longer change. + Frozen = 12, } impl From for Error { @@ -122,14 +126,18 @@ pub struct DeepmarkContract; #[contractimpl] impl DeepmarkContract { - /// Sets the three relic Stellar Asset Contracts once. There is no admin - /// and no upgrade path: what is deployed is what runs. + /// Sets the admin and the three relic Stellar Asset Contracts once. The + /// admin must sign the deployment, so nobody can name an address they do + /// not control. The admin can upgrade the code until it renounces. pub fn __constructor( env: Env, + admin: Address, relic_common: Address, relic_rare: Address, relic_core: Address, ) { + admin.require_auth(); + admin::init(&env, &admin); let tokens = RelicTokens { common: relic_common, rare: relic_rare, @@ -143,6 +151,7 @@ impl DeepmarkContract { /// profile. A veteran must be owned by `player`. pub fn start_run(env: Env, player: Address, choice: HeroChoice) -> Result { player.require_auth(); + extend_instance(&env); let (loadout, look, veteran) = match choice { HeroChoice::Fresh(fresh) => { @@ -227,6 +236,7 @@ impl DeepmarkContract { /// log cannot be claimed twice. pub fn finish_run(env: Env, player: Address, actions: Bytes) -> Result { player.require_auth(); + extend_instance(&env); if actions.len() > MAX_ACTIONS { return Err(Error::TooManyActions); } @@ -391,6 +401,32 @@ impl DeepmarkContract { out } + /// Who can upgrade the code, or `None` once the role was renounced. + pub fn admin(env: Env) -> Option
{ + admin::current(&env) + } + + /// The contract release, bumped whenever behavior changes. + pub fn version(_env: Env) -> u32 { + CONTRACT_VERSION + } + + /// Replace the contract code with an uploaded wasm. Admin only. Storage + /// is kept, so players, delvers and the leaderboard survive. + pub fn upgrade(env: Env, new_wasm_hash: BytesN<32>) -> Result<(), Error> { + admin::upgrade(&env, &new_wasm_hash) + } + + /// Hand the admin role to `new_admin`. Both must sign. + pub fn set_admin(env: Env, new_admin: Address) -> Result<(), Error> { + admin::transfer(&env, &new_admin) + } + + /// Give the admin role up for good. The code can never change afterwards. + pub fn renounce_admin(env: Env) -> Result<(), Error> { + admin::renounce(&env) + } + /// The Stellar Asset Contracts that hold relics. pub fn relic_tokens(env: Env) -> RelicTokens { env.storage().instance().get(&DataKey::Relics).unwrap() @@ -477,6 +513,14 @@ impl DeepmarkContract { } } +/// Keep the contract instance (admin, relic tokens, and with it the code) alive +/// while the game is being played. +fn extend_instance(env: &Env) { + env.storage() + .instance() + .extend_ttl(TTL_THRESHOLD, TTL_EXTEND_TO); +} + fn name_is_valid(name: &String) -> bool { let len = name.len() as usize; if !(world::NAME_MIN..=world::NAME_MAX).contains(&len) { diff --git a/contracts/deepmark-contract/src/test.rs b/contracts/deepmark-contract/src/test.rs index dd5d80e..29e0adc 100644 --- a/contracts/deepmark-contract/src/test.rs +++ b/contracts/deepmark-contract/src/test.rs @@ -38,6 +38,7 @@ fn setup<'a>() -> Fixture<'a> { env.mock_all_auths(); set_ledger(&env, 100); + let admin = Address::generate(&env); let issuer = Address::generate(&env); let common = env .register_stellar_asset_contract_v2(issuer.clone()) @@ -49,7 +50,7 @@ fn setup<'a>() -> Fixture<'a> { let contract_id = env.register( DeepmarkContract, - (common.clone(), rare.clone(), core.clone()), + (admin, common.clone(), rare.clone(), core.clone()), ); for token in [&common, &rare, &core] { StellarAssetClient::new(&env, token).set_admin(&contract_id); @@ -496,7 +497,12 @@ fn a_failed_mint_never_loses_the_verified_run() { ]; let id = env.register( DeepmarkContract, - (bogus[0].clone(), bogus[1].clone(), bogus[2].clone()), + ( + Address::generate(&env), + bogus[0].clone(), + bogus[1].clone(), + bogus[2].clone(), + ), ); let client = DeepmarkContractClient::new(&env, &id); let player = Address::generate(&env); @@ -666,7 +672,14 @@ fn nobody_can_act_for_an_address_that_did_not_sign() { let env = Env::default(); set_ledger(&env, 100); let a = Address::generate(&env); - let id = env.register(DeepmarkContract, (a.clone(), a.clone(), a.clone())); + // The constructor needs the admin's signature, so allow it for deployment + // only and then drop every mocked authorization. + env.mock_all_auths(); + let id = env.register( + DeepmarkContract, + (a.clone(), a.clone(), a.clone(), a.clone()), + ); + env.set_auths(&[]); let client = DeepmarkContractClient::new(&env, &id); let victim = Address::generate(&env); diff --git a/contracts/deepmark-contract/src/world.rs b/contracts/deepmark-contract/src/world.rs index bcb9cfe..989bfa7 100644 --- a/contracts/deepmark-contract/src/world.rs +++ b/contracts/deepmark-contract/src/world.rs @@ -50,7 +50,7 @@ const WEAPONS: [Modifier; 3] = [ Modifier { hp: -2, dmg_min: 0, - dmg_max: 2, + dmg_max: 1, }, Modifier { hp: 1, @@ -156,7 +156,7 @@ pub fn archetype(kind: EnemyKind) -> Archetype { EnemyKind::Warden => Archetype { max_hp: 6, dmg_min: 1, - dmg_max: 3, + dmg_max: 2, }, EnemyKind::Skitter => Archetype { max_hp: 4, diff --git a/contracts/deepmark-contract/tests/golden.txt b/contracts/deepmark-contract/tests/golden.txt index c060026..2ba1b16 100644 --- a/contracts/deepmark-contract/tests/golden.txt +++ b/contracts/deepmark-contract/tests/golden.txt @@ -1,8 +1,8 @@ # Golden vectors for the Deepmark replay. Generated by client/scripts/golden-vectors.ts. # base weapon charm rank seed actions ending(E|D) floor common rare core score hp action_count -1 2 0 0 1599650756 3323335344534332200033131313633334443322000353534331313137 E 2 1 1 0 3 2 58 +1 2 0 0 1599650756 3323335344534332200033131313633334443322000353534331313137 E 2 1 1 0 3 3 58 0 2 0 3 2962134526 000333331313136000333353534433131313633334444333333 E 3 0 0 1 3 7 51 -0 0 0 1 3442392912 3333440011332201000331313137 E 1 1 0 0 1 7 28 +0 0 0 1 3442392912 3333440011332201000331313137 E 1 1 0 0 1 8 28 1 1 1 2 4035330916 000333331313317 E 1 0 0 0 0 6 15 0 0 2 3 2875746298 33334535343322000331313137 E 1 1 0 0 1 10 26 0 2 2 0 2893880300 00032333332313131360001303334331313137 E 2 0 0 0 0 12 38 @@ -13,23 +13,22 @@ 0 0 1 3 2853219197 00033333131313630003334233313131363300113344333333 E 3 0 0 1 3 6 50 1 0 0 1 733908112 000333331313136000333343313131363333444333333 E 3 0 0 1 3 4 45 0 1 2 0 3242652345 33301353443322010003313131363333534443322000343313131363330134 D 3 0 0 0 0 0 62 -1 0 0 0 2326245706 33335344332200033311313633335344 D 2 0 0 0 0 0 32 -0 2 2 3 3449897085 0003333313131360003233323233443313131363333445344333333 E 3 0 0 1 3 3 55 -1 1 0 0 2577845444 3333443322000331313137 E 1 1 0 0 1 5 22 -0 0 1 0 2096457081 333343322000331313137 E 1 1 0 0 1 5 21 -1 0 1 1 2212626527 333343322000331313137 E 1 1 0 0 1 5 21 -1 2 0 1 2712249086 333344332200033131313633335353444332200034331313137 E 2 1 1 0 3 5 51 -1 0 1 3 3866290670 3333443322000331313136333344332200035353433131331633335344333333 E 3 1 1 1 6 1 64 +1 0 0 0 2326245706 333353443322000333113136333353444332200034 D 2 0 0 0 0 0 42 +1 0 0 2 1640394412 0003333313132313600203333344331313137 E 2 0 0 0 0 9 37 +0 0 0 0 768033551 00033333131313600033334331313137 E 2 0 0 0 0 9 32 +0 0 0 2 1258036288 33334433220003313131363333444332200034331313136333344 D 3 0 0 0 0 0 53 +0 2 2 2 701371759 3333534443322000331313136333353444332200034331313137 E 2 1 1 0 3 4 52 +1 0 1 3 3866290670 3333443322000331313136333344332200035353433131331633335344333333 E 3 1 1 1 6 2 64 1 0 1 0 1068570052 33334332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 50 0 0 0 2 66232412 000333331313137 E 1 0 0 0 0 11 15 -1 1 0 1 390266192 333344534332200033131313633334433220003433130113137 E 2 1 1 0 3 1 51 +1 1 0 1 390266192 333344534332200033131313633334433220003433130113137 E 2 1 1 0 3 2 51 0 0 1 2 3444994676 3333433220003313131363333443322000343313011313633334 D 3 0 0 0 0 0 52 0 2 2 3 3324469839 3333443322000331313136333344433220003535353433131313623333353535345344 D 3 0 0 0 0 0 70 0 1 1 3 414976794 333344332201000331313137 E 1 1 0 0 1 6 24 1 0 2 3 3181737148 333344332200033131313631033344332200034331313137 E 2 1 1 0 3 6 48 -0 2 2 0 2384229034 33334443322000331313137 E 1 1 0 0 1 8 23 +0 2 2 0 2384229034 33334443322000331313137 E 1 1 0 0 1 9 23 1 2 0 0 2558426581 0003333313131360003333433131313633334444 D 3 0 0 0 0 0 40 -1 0 2 0 407957595 333353443322000331313137 E 1 1 0 0 1 5 24 +1 0 2 0 407957595 333353443322000331313137 E 1 1 0 0 1 6 24 1 2 1 1 1949467647 3333433220003323131313633334534332200034331313137 E 2 1 1 0 3 5 49 0 0 2 1 1073711131 00033333131313600033334331313137 E 2 0 0 0 0 10 32 0 1 2 1 3390343738 33334433220003313131363333444332200034331313137 E 2 1 1 0 3 3 47 @@ -38,61 +37,57 @@ 0 2 0 2 1306468264 000333331313137 E 1 0 0 0 0 14 15 1 2 0 3 326214090 3331034433220003313131363333534443322000344331313136333344 D 3 0 0 0 0 0 58 0 1 1 1 197183724 00033333131313600033334331313137 E 2 0 0 0 0 6 32 -1 2 0 0 933303058 3333443322000331313137 E 1 1 0 0 1 9 22 +1 2 0 0 933303058 3333443322000331313137 E 1 1 0 0 1 10 22 0 0 0 1 1879179966 33334332200033132313136333353534453433220003443313131363333534433103333 E 3 1 1 1 6 1 71 1 1 1 2 2654818028 00303310331313137 E 1 0 0 0 0 6 17 -0 1 2 3 184904862 010003333313131360003333443313131363333444333333 E 3 0 0 1 3 2 48 -0 0 0 2 3525140881 333353433220003313131363333444332200034331313136333344 D 3 0 0 0 0 0 54 -0 2 0 2 4187233236 0003333313131360003310334433131313633334444 D 3 0 0 0 0 0 43 -1 0 0 0 1862205615 3333453534332322000332313131363323334443322000343313130113633334 D 3 0 0 0 0 0 64 -0 1 0 3 1359964281 32233333444332220003331313137 E 1 1 0 0 1 7 29 -1 2 1 0 2267509283 3333443322000331313136333344332200034331313136333353534 D 3 0 0 0 0 0 55 -1 1 0 3 2584958311 00033333131313600033335344331313137 E 2 0 0 0 0 9 35 -1 0 1 2 4210074816 0003333313131360003333534331313136333344333333 E 3 0 0 1 3 2 46 -1 2 2 2 2159343131 33334433220100033131313633334453410332200034534331313136333344 D 3 0 0 0 0 0 62 -1 2 0 2 2317239789 3333534443322000331313136333353444332200034433131313633335344 D 3 0 0 0 0 0 61 -0 1 2 0 3256336124 3333453433220003313131363333445343322000343313131363333534 D 3 0 0 0 0 0 58 -1 0 1 2 1720345412 333343322000331313137 E 1 1 0 0 1 6 21 -1 1 2 0 693371672 301333443322000331313136333344332200034331313137 E 2 1 1 0 3 3 48 -0 1 1 2 447033776 33334433220003313131363333534433220003433131313633334 D 3 0 0 0 0 0 53 -0 2 0 2 681093901 01333344443322000331313136333353443322000344331313137 E 2 1 1 0 3 3 53 -1 2 0 1 1463724163 000333331313136000333343313131363333444333333 E 3 0 0 1 3 5 45 -0 0 2 1 1591274868 3333443322000331313137 E 1 1 0 0 1 7 22 -1 2 2 3 204999665 3333443322000331313137 E 1 1 0 0 1 13 22 -0 1 1 3 506480734 200303333313131360003333534331313136333344333333 E 3 0 0 1 3 6 48 -0 2 1 0 1906366215 33334433220003313131363333534433220003433131313633334 D 3 0 0 0 0 0 53 -0 0 0 0 243534180 33334443322000331313136333344 D 2 0 0 0 0 0 29 -1 1 2 1 2566232902 000333331313137 E 1 0 0 0 0 9 15 -1 2 2 0 2616091947 33334433220003313131103633334535344332200034433131313633334 D 3 0 0 0 0 0 59 -0 1 0 2 2873389962 3333534443322000331313137 E 1 1 0 0 1 8 25 -1 2 1 0 82335121 000333331313136000333353534331313137 E 2 0 0 0 0 7 36 -0 0 0 0 380099250 000333331313136000333343313131363333444333333 E 3 0 0 1 3 3 45 -0 1 0 2 2829899731 002303333313131360003333433131013137 E 2 0 0 0 0 11 36 -1 1 2 0 1623295888 000333331301131362030033334331313137 E 2 0 0 0 0 8 36 -0 1 2 1 981382362 3333443322000331313136333344433220003433131313633335344 D 3 0 0 0 0 0 55 -0 2 1 2 1982049789 0003333313131360003333534331313137 E 2 0 0 0 0 10 34 -1 0 2 1 2907024922 00033333131313600033334331313136333344433103333 E 3 0 0 1 3 2 47 -1 0 2 2 3112743936 000333331313136000333343313131363333444333333 E 3 0 0 1 3 2 45 -0 2 0 0 3448900613 000333331313136000333344433131313633334444333333 E 3 0 0 1 3 2 48 -0 0 0 1 3965773581 3333443322000331313136333344534332200034 D 2 0 0 0 0 0 40 -0 2 2 2 1592202689 333344332200033131313633334443322000344331313136333344 D 3 0 0 0 0 0 54 -1 2 2 2 1018583138 33335344332200033131331633334443322000344331313137 E 2 1 1 0 3 5 50 -1 2 0 1 4266635072 3333535345343322000331313136333353444332200034534331313137 E 2 1 1 0 3 4 58 -0 0 1 2 3309024009 333343322000331313137 E 1 1 0 0 1 7 21 -0 1 1 3 2767306451 000333331313137 E 1 0 0 0 0 8 15 -0 1 0 1 3492411350 00033333130113137 E 1 0 0 0 0 10 17 +0 1 2 3 184904862 010003333313131360003333443313131363333444 D 3 0 0 0 0 0 42 +0 0 1 3 4031060862 333344332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 51 +1 1 1 0 2130840409 333344332200033131313633334 D 2 0 0 0 0 0 27 +1 1 2 1 1965075896 333353443322000331313136333344332200034331313136333344 D 3 0 0 0 0 0 54 +0 1 0 3 1778495733 333233443322010003313131100137 E 1 1 0 0 1 10 30 +1 1 2 3 2757844302 333344332200033131313632333344332200034331313137 E 2 1 1 0 3 6 48 +1 2 0 1 3363907596 00033333131313600033334433131313633335353444333333 E 3 0 0 1 3 4 50 +1 1 1 2 2803368293 00033333131313600033334331313137 E 2 0 0 0 0 6 32 +1 1 1 2 314706555 33334332200033131313633323344332200034331313136333344 D 3 0 0 0 0 0 53 +1 2 0 2 4251745359 33335344433220003313131363333445343322000343313131363333534 D 3 0 0 0 0 0 59 +1 0 1 2 1989227306 33334433223200033131313633334 D 2 0 0 0 0 0 29 +0 2 1 0 1113178625 3333443322000331313136333344433220003433131313633334 D 3 0 0 0 0 0 52 +1 1 0 2 122127711 333343322000331313137 E 1 1 0 0 1 10 21 +0 1 0 2 2975640588 33334443322000331313136333345353453534332200034331313137 E 2 1 1 0 3 3 56 +1 1 1 0 2877460120 000333331313136000333353433131313633334 D 3 0 0 0 0 0 39 +0 0 2 1 760184663 00033333131313600033334331313137 E 2 0 0 0 0 10 32 +0 0 2 1 4269205138 3333534433220003313131363333534534433220003433131313633334 D 3 0 0 0 0 0 58 +0 2 2 2 270860155 333344332200033131313633334443322000344331313136333345344 D 3 0 0 0 0 0 57 +0 0 2 2 2477240023 333344332200033133113633334443322000343313131363323334 D 3 0 0 0 0 0 54 +0 1 2 0 2713183020 0003333313131360003333443313131363333444 D 3 0 0 0 0 0 40 +1 2 1 2 2202773186 333344332200033131313633334433220003433131311036333344 D 3 0 0 0 0 0 54 +1 2 0 1 990957767 333353443322000331313136333345344332200034331313136333350134 D 3 0 0 0 0 0 60 +1 0 1 0 3072441907 3333433220003313131363333443322000353534331313137 E 2 1 1 0 3 1 49 +0 2 1 3 2977372939 3333534433220003313131363333444332200034331313136333344 D 3 0 0 0 0 0 55 +1 1 2 2 3518306314 333345343322000331313136333344332200034331313136333344 D 3 0 0 0 0 0 54 +1 0 1 3 198500473 333343322000331313137 E 1 1 0 0 1 7 21 +0 2 1 0 265796827 3013334534332200033131313633334433222000335343313131363333534 D 3 0 0 0 0 0 61 +0 0 2 0 4201428187 33335013443322000331313137 E 1 1 0 0 1 8 26 +0 0 0 2 853016926 3333443322000331313136333103443322000344331313136333344 D 3 0 0 0 0 0 55 +1 0 0 2 43703804 3333433220003313131363333445343322000343313131363333534534 D 3 0 0 0 0 0 58 +0 1 0 1 1394740784 33334332200033131313633334534433220003433131313633334 D 3 0 0 0 0 0 53 +0 1 0 1 1255221656 333353443322000331313137 E 1 1 0 0 1 8 24 +1 1 1 1 1873640312 3333534332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 52 +0 1 1 3 3535209603 00033333131313600033335343313131363333444333333 E 3 0 0 1 3 3 47 +1 0 2 3 3128683142 333344332200033131313633334433220003432331313136333344 D 3 0 0 0 0 0 54 +1 0 1 0 3881906692 00033333131313600033334331313137 E 2 0 0 0 0 4 32 +1 2 1 3 3458904551 0003333313133160003333433130113137 E 2 0 0 0 0 10 34 0 0 2 0 307963904 3333443322000331313137 E 1 1 0 0 1 7 22 -1 1 2 3 1589320798 323333443322000331313136303133443322000343313131363333444333333 E 3 1 1 1 6 1 63 -0 2 1 1 22178957 0003333323131313600033334433131313633013344 D 3 0 0 0 0 0 43 -1 0 1 1 1173459290 3333433220003313130113633334433220003433131313633334 D 3 0 0 0 0 0 52 -1 2 2 2 195052196 3333443322000331313136333353444332200034 D 2 0 0 0 0 0 40 -0 1 2 1 636841512 33334332200033131313633334433220003534331313137 E 2 1 1 0 3 7 47 -0 0 2 2 1927390139 000333331313136000333344331313136333345344333333 E 3 0 0 1 3 4 48 -1 2 2 1 1559909540 000333331313137 E 1 0 0 0 0 12 15 -1 2 0 1 1095763112 3333443322000331313137 E 1 1 0 0 1 9 22 -1 1 1 2 1082989784 33334332200033131313633335344332200034331313137 E 2 1 1 0 3 2 47 -1 2 0 0 3223660201 00033333131313600033103343313131363333444333333 E 3 0 0 1 3 3 47 -1 2 1 3 2652461848 0003333313131360003333433131313633335344534333333 E 3 0 0 1 3 3 49 +1 1 2 3 1589320798 32333344332200033131313630313344433220020334423331313137 E 2 1 1 0 3 2 56 +1 2 1 2 3610771810 3333453534332200033131313633334534332200034331313137 E 2 1 1 0 3 6 52 +0 1 1 1 2416286760 333344332200033101313136333344 D 2 0 0 0 0 0 30 +1 2 2 1 2860265364 3333443322020023333131313633335344432332200034331313137 E 2 1 1 0 3 4 55 +0 0 0 0 238935760 23333344332220003331313136301332335344332200034331313136333344 D 3 0 0 0 0 0 62 +1 2 2 2 2464569099 33334433220003313131363333444332200034331313136333344 D 3 0 0 0 0 0 53 +1 0 0 0 1389449589 333345343322000331313137 E 1 1 0 0 1 6 24 +1 2 2 0 2250421935 3333443322000331313136333353444332200034433131313633334 D 3 0 0 0 0 0 55 +0 0 0 1 2617701667 00033333131313600033334331313136333353444333333 E 3 0 0 1 3 4 47 +1 1 0 3 629801714 333344332322000331313137 E 1 1 0 0 1 9 24 0 1 1 1 3219433880 33334332200033131313633334433220003433311313633334 D 3 0 0 0 0 0 50 1 1 2 2 774645378 333343322000331313136333344332200034333113136333344 D 3 0 0 0 0 0 51 1 2 2 2 3451586841 000333331313137 E 1 0 0 0 0 13 15 @@ -104,108 +99,109 @@ 0 2 1 3 931551509 3333443322000331313137 E 1 1 0 0 1 10 22 1 0 0 1 901915561 000333233313131360003333433131313633301353444333333 E 3 0 0 1 3 1 51 1 1 2 1 3382312545 3333443322020033313131363333444332200035342333131313633334 D 3 0 0 0 0 0 58 -0 1 0 3 3364221760 333343322000331313136333344332200034534331313136333345344 D 3 0 0 0 0 0 57 +0 1 0 3 3364221760 333343322000331313136333344433220003534331313136333345344 D 3 0 0 0 0 0 57 0 2 0 3 3295661036 3333444332200033131313633335344433220003534433131313633334 D 3 0 0 0 0 0 58 -1 0 2 1 336272570 3333443322000331313137 E 1 1 0 0 1 6 22 +1 0 2 1 336272570 3333443322000331313137 E 1 1 0 0 1 7 22 1 1 0 3 4274940607 0003333313131360003333443313131363333444333333 E 3 0 0 1 3 4 46 1 1 0 1 2625355992 3333433220003313131363333453433220003534331313137 E 2 1 1 0 3 7 49 0 0 0 0 475025660 000333331313137 E 1 0 0 0 0 9 15 0 1 0 2 2063194350 3333433220003313131363333443322000344331313136333344 D 3 0 0 0 0 0 52 1 1 2 0 2701141641 3333534433220003313131363103334433220003433131313633334 D 3 0 0 0 0 0 55 -1 1 2 2 98708192 33335343322000331313136333345343233220200334103313131363333453451034 D 3 0 0 0 0 0 68 -0 2 0 3 3971365009 333344332200033131313633334444332200034333113136333344 D 3 0 0 0 0 0 54 -0 0 1 0 1486597627 000333331313136100003331034331313136333344 D 3 0 0 0 0 0 42 -0 0 1 2 3080310641 0003323333131313600033335343313131363333444333333 E 3 0 0 1 3 1 49 -0 0 0 3 1830995094 020033333313131360003333443313131363333444 D 3 0 0 0 0 0 42 -1 1 0 1 1358124368 000333331313136000333343313131363333444333333 E 3 0 0 1 3 4 45 -1 1 1 2 3374839716 333343322000331313136333344332200034331313137 E 2 1 1 0 3 3 45 -1 1 2 1 413887990 33233345343322000331313137 E 1 1 0 0 1 6 26 +1 1 2 2 98708192 33335345343322000331313137 E 1 1 0 0 1 8 26 +1 0 1 0 215622422 3333433220003313131363333534 D 2 0 0 0 0 0 28 +0 2 0 0 1721003602 000333331313137 E 1 0 0 0 0 12 15 +0 2 2 0 309494283 3333444332200010331313137 E 1 1 0 0 1 9 25 +1 1 1 3 2550603844 000333331313137 E 1 0 0 0 0 7 15 +0 0 2 3 2527329874 3333443322000331313137 E 1 1 0 0 1 11 22 +1 0 0 0 3892055899 010003333313131360003333443313131363333444333333 E 3 0 0 1 3 1 48 +1 1 0 3 3306069928 000333331313136000333344331313137 E 2 0 0 0 0 9 33 +1 1 1 0 1834227712 000333331313137 E 1 0 0 0 0 4 15 +1 1 0 2 3970647135 000333331313136300033343313131363333444333333 E 3 0 0 1 3 3 45 +1 1 0 0 1174847554 33334535343322000331313136333344332200035353443313131363333534 D 3 0 0 0 0 0 62 +1 1 2 1 413887990 33233345343322000331313137 E 1 1 0 0 1 7 26 1 0 2 1 3773924420 33334433220003313131363333443322000343313131363333444333333 E 3 1 1 1 6 2 59 -0 0 0 1 3648336477 333344323322000331313137 E 1 1 0 0 1 8 24 +0 0 0 1 3648336477 333344323322000331313137 E 1 1 0 0 1 9 24 0 2 2 0 1623731715 00033333130113136000333344331313137 E 2 0 0 0 0 11 35 1 1 0 3 2962841658 0003233333131313600033334534331313137 E 2 0 0 0 0 9 37 1 0 1 1 2372172696 33334332200033131313633334534332200034331313137 E 2 1 1 0 3 3 47 0 0 0 2 3942976803 333343322000331313137 E 1 1 0 0 1 11 21 -0 2 2 3 905794523 33334433220003313131363333444433220003433131313633334 D 3 0 0 0 0 0 53 -1 1 0 1 3971148143 000331033313131360003333433131313633334444333333 E 3 0 0 1 3 1 48 -1 0 0 0 1453413847 3333443322000331313136333344332200034 D 2 0 0 0 0 0 37 -1 2 0 2 429004515 0003333313131360003333453433131313633334444333333 E 3 0 0 1 3 3 49 -0 2 2 2 1863965917 3333443322000331313136333344443322000344331313137 E 2 1 1 0 3 5 49 -1 0 2 0 402537348 333344332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 51 -0 2 2 2 3445636150 3333443322000331313137 E 1 1 0 0 1 12 22 -1 0 2 3 2736252695 3333443322000331323131363333534433220200334331313137 E 2 1 1 0 3 4 52 -0 2 2 3 2046574436 3333444332200033131313633334443322000344331313137 E 2 1 1 0 3 4 49 -1 0 0 1 1807512407 000333331313136000333344331313137 E 2 0 0 0 0 8 33 -1 0 2 1 2527338855 0003333313131360003333423331313137 E 2 0 0 0 0 9 34 -1 0 2 0 380167723 33334433220003313131363333443322020033433131313633233344 D 3 0 0 0 0 0 56 -0 1 2 0 2106800790 333344332200033131313633334534332200034331313136333344 D 3 0 0 0 0 0 54 -1 2 0 0 3296935477 00032333331313137 E 1 0 0 0 0 11 17 -0 0 1 3 1873542490 3333443322000331313137 E 1 1 0 0 1 5 22 -1 0 0 3 3282618598 33334535343322000331313137 E 1 1 0 0 1 9 26 -0 1 2 0 536808431 00033333131313600033334331313137 E 2 0 0 0 0 9 32 -0 1 1 1 3108375132 33334332200033131313633334443322000353433131313633334 D 3 0 0 0 0 0 53 -1 0 2 2 1825138142 333344332200033131313633334433220200334331313137 E 2 1 1 0 3 4 48 -1 2 1 3 811215723 100003333313131360003333534331313137 E 2 0 0 0 0 10 36 -1 1 2 2 3132735008 00033333131313600033334331313136333345344333333 E 3 0 0 1 3 3 47 -1 0 1 2 922740812 000333331313136000333343313131363333444333333 E 3 0 0 1 3 1 45 -1 1 1 1 2448491694 33334332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 50 -1 0 2 2 4121410855 000333331313137 E 1 0 0 0 0 10 15 -0 1 2 2 3463640886 333344332200010331313136301333444433220003433131313633334 D 3 0 0 0 0 0 57 -1 1 0 1 150890816 33334443322000331313137 E 1 1 0 0 1 5 23 -1 0 0 1 1778185017 00033333131313600033334331313136333344534333333 E 3 0 0 1 3 3 47 -1 1 0 1 828185810 000333331313137 E 1 0 0 0 0 9 15 -0 1 0 1 1543834701 333344332200033231313136332333453443322000343313131363333534 D 3 0 0 0 0 0 60 -1 2 0 2 2060118767 33334433220003313131363333443322000344331313136301333444 D 3 0 0 0 0 0 56 -1 1 2 2 1124870295 3333443322000331313137 E 1 1 0 0 1 8 22 -0 2 1 2 657281398 000333331313137 E 1 0 0 0 0 10 15 -0 0 1 0 1168455092 0003333313131103600033334331313136333344 D 3 0 0 0 0 0 40 -0 0 1 1 2074600543 00033333131313600033334331313137 E 2 0 0 0 0 6 32 -1 1 1 1 2342020278 333344332200033131313633334 D 2 0 0 0 0 0 27 -1 2 2 1 3110127103 3333441033220003313131363333444332200034433131313633334534 D 3 0 0 0 0 0 58 -1 2 1 3 1002630923 3333433220003313131363333443322000353535343313131363333444333333 E 3 1 1 1 6 1 64 -1 2 1 3 3840508568 00033333131310136000333343313131363333444333333 E 3 0 0 1 3 5 47 -1 0 2 1 1806872997 00031033331313137 E 1 0 0 0 0 9 17 -1 1 2 3 1194234357 333343322000331313137 E 1 1 0 0 1 11 21 -1 2 0 3 40373733 00023333331313101360003333433131313633334444333333 E 3 0 0 1 3 5 50 -1 1 2 1 2759760068 33334433220003313231313633334433220003443313133163333534 D 3 0 0 0 0 0 56 -1 0 2 3 815120345 3333534534332200033131313633334534332200034331313137 E 2 1 1 0 3 6 52 -1 1 0 0 4074539664 000333331313136000333343313131363333444333333 E 3 0 0 1 3 2 45 -0 1 0 1 1882173317 000333331313137 E 1 0 0 0 0 10 15 -1 2 2 3 723162300 3021333345343322000331313136333345353443322000344331313136333344 D 3 0 0 0 0 0 64 -0 0 1 2 643603829 3333443322000331313137 E 1 1 0 0 1 5 22 -1 0 2 2 2788145262 00033333131313600033334331313136332333444333333 E 3 0 0 1 3 5 47 -0 0 2 0 911557292 333344332200033131313633310344332200034331313136333344 D 3 0 0 0 0 0 54 -0 2 2 3 3210297530 000333331313137 E 1 0 0 0 0 15 15 -0 0 1 3 1186704626 333343322000331313136333344332200034331313137 E 2 1 1 0 3 5 45 -1 1 1 3 1729435983 33335343322000331313136333344332200034331313137 E 2 1 1 0 3 5 47 -0 1 2 3 4108631323 0003333313131360003333433131313633334444333333 E 3 0 0 1 3 4 46 -0 1 0 2 581284955 33334433220003313131363333443322000344331313136333344 D 3 0 0 0 0 0 53 -1 2 0 1 709827983 000333331313137 E 1 0 0 0 0 12 15 -0 0 2 0 2877166332 333344332200033131313633334433220003534331313136333344 D 3 0 0 0 0 0 54 -0 1 1 1 254807042 333013453433220003313131363333453433220003433131313633334 D 3 0 0 0 0 0 57 -1 0 0 2 2673720655 33334433220003313131363333444332200034331313137 E 2 1 1 0 3 1 47 -0 0 1 2 1374977445 103333443322000331313137 E 1 1 0 0 1 4 24 -1 2 2 3 2961812140 000333331313136000333343313131363333444534333333 E 3 0 0 1 3 5 48 -1 1 2 3 2239406458 000333331313137 E 1 0 0 0 0 11 15 -0 0 2 0 4164714509 33334433220003313131363323335344332200034331313316333344 D 3 0 0 0 0 0 56 -1 1 0 1 2641449604 0003333313131360020323333343313131360133335353534433330133 E 3 0 0 1 3 6 58 -0 0 1 0 489878748 00033333131313600033334331313136333344 D 3 0 0 0 0 0 38 -0 1 0 2 471347625 333344332200033131313633334534332200034331313137 E 2 1 1 0 3 6 48 -0 1 2 1 610929292 000333331313137 E 1 0 0 0 0 10 15 -0 2 2 3 2763534311 33334423332200033131313633334443322000343313131363333444 D 3 0 0 0 0 0 56 -1 1 1 0 2633388559 0003333313131360003333433131313633334 D 3 0 0 0 0 0 37 +0 2 2 3 905794523 333344332200033131313633334444332200034331313136333344 D 3 0 0 0 0 0 54 +0 1 2 2 3394177955 333343322000331313137 E 1 1 0 0 1 11 21 +0 2 2 3 1243249521 000333331313137 E 1 0 0 0 0 15 15 +1 2 0 0 3321228174 000333331313136000333344331313136333345344 D 3 0 0 0 0 0 42 +1 0 2 0 4228251039 333353443322000331313137 E 1 1 0 0 1 7 24 +1 0 0 3 4029199554 3333433220003313131363333444332200034331313136333344 D 3 0 0 0 0 0 52 +0 1 1 2 3954065351 333344332200033131313610333353443322000343313131363333534433233333 E 3 1 1 1 6 1 66 +0 2 1 3 4066712365 000333331313136000333343313131363333534535344333333 E 3 0 0 1 3 6 51 +0 1 0 3 1766604213 0003333313131360003333433131313633334444333333 E 3 0 0 1 3 3 46 +0 0 1 1 1766979233 000333331313136000333343313131363333444333333 E 3 0 0 1 3 1 45 +1 2 0 0 1723189149 3333443322000331313137 E 1 1 0 0 1 9 22 +0 2 1 2 2871395870 3333443322000331313136333344433220003433131301137 E 2 1 1 0 3 4 49 +0 0 1 3 2622747529 333353433220003313131362333334433220003534331313137 E 2 1 1 0 3 5 51 +0 1 1 3 1549326534 3333535343322000331313137 E 1 1 0 0 1 8 25 +0 0 0 2 1505044624 0003333313131360003333453433131313633334444333333 E 3 0 0 1 3 2 49 +1 0 0 3 198518410 00033333131313600033334331313136333233444333333 E 3 0 0 1 3 5 47 +1 0 2 2 4234891417 3333443322000331313136333344433220003433131313633334 D 3 0 0 0 0 0 52 +1 2 2 0 2750805584 0003333313131360200333310343313131363333444333333 E 3 0 0 1 3 7 49 +1 2 0 2 1716367342 00033333131323136000333344331313136333344433103333 E 3 0 0 1 3 5 50 +1 1 2 1 460805808 3333443322000331313136333344433220003433131313633334 D 3 0 0 0 0 0 52 +0 1 0 1 2259940778 00033233331313137 E 1 0 0 0 0 10 17 +1 2 1 2 1328818012 00033333131313600033334331313137 E 2 0 0 0 0 9 32 +1 1 0 2 616781100 3333443322000331313136333344332200034331313137 E 2 1 1 0 3 4 46 +1 2 2 0 3510415805 33334433220003313131363333534534433220003443313131013633334 D 3 0 0 0 0 0 59 +1 1 1 1 2066736952 3333443322000331331137 E 1 1 0 0 1 4 22 +1 2 1 1 3150630857 33334433220003313131363333443322000343313131013633334 D 3 0 0 0 0 0 53 +1 2 1 3 2717293558 00023333331313137 E 1 0 0 0 0 10 17 +0 2 0 1 825472581 333344332200033131313633334534332200034331301131363333444 D 3 0 0 0 0 0 57 +1 1 0 2 874792263 3333443322000331313101363333444332200034 D 2 0 0 0 0 0 40 +0 0 2 2 3190018497 33334433220003313131363333534443322000344331313136333344 D 3 0 0 0 0 0 56 +0 2 0 3 1536958709 333344332200033131313633334433220003443313131363333444333333 E 3 1 1 1 6 2 60 +0 2 1 1 1098639712 333353453433220003313131363333444332202003343313131360133334 D 3 0 0 0 0 0 60 +0 2 1 1 1314632851 3333453433221000033131313633334433220003534433312313137 E 2 1 1 0 3 3 55 +1 1 0 1 954655876 000333331313137 E 1 0 0 0 0 9 15 +1 1 0 2 618375156 33334433220003313131363333444332200034433131313633334 D 3 0 0 0 0 0 53 +1 0 0 2 3324777709 0003333313101313600033335343313131363333444 D 3 0 0 0 0 0 43 +1 2 0 0 1830468211 3333443322000331313317 E 1 1 0 0 1 9 22 +0 2 0 2 3990241812 333353534501343322000331313136333353534444 D 2 0 0 0 0 0 42 +1 1 1 0 2405730534 3333433220003313131361033334534323322000353433131313633334 D 3 0 0 0 0 0 58 +1 1 0 0 209830897 3333535343322000331313137 E 1 1 0 0 1 8 25 +0 2 0 0 654840116 3333453444332201000331313136333345353433220003534433131313633334 D 3 0 0 0 0 0 64 +0 2 0 0 1790283503 333345343322020033313130113633335353443322000343313131363333444 D 3 0 0 0 0 0 63 +1 0 0 1 2176187469 0003333313131360003333534331313136333344534333333 E 3 0 0 1 3 3 49 +0 2 2 0 187745103 33334443322000331313136333344 D 2 0 0 0 0 0 29 +1 2 2 2 1267428491 3333453433220200333131313633334443322000345353433131313633335344 D 3 0 0 0 0 0 64 +0 2 0 0 2729594627 3333444332200033131313633334441033221000034433131313633334 D 3 0 0 0 0 0 58 +0 2 1 0 4262212268 3333443322000331313136333344 D 2 0 0 0 0 0 28 +1 0 1 3 2621370457 333343322000331313137 E 1 1 0 0 1 7 21 +1 2 2 0 713231414 3333443322000331313136333345344332200034433131313633334 D 3 0 0 0 0 0 55 +1 0 0 3 1355348990 333344332232000331313137 E 1 1 0 0 1 10 24 +1 2 0 2 1232343262 000333331313137 E 1 0 0 0 0 13 15 +0 0 0 3 2896787777 0020333333131313600033334331323131363333444333333 E 3 0 0 1 3 5 49 +0 2 0 1 364129607 3333534443322000331313136333344332200034331313136333344534 D 3 0 0 0 0 0 58 +0 0 2 0 218751522 333345343322000331313136333344433220003433131313633334 D 3 0 0 0 0 0 54 +0 2 0 0 1977088188 333344443322000331313137 E 1 1 0 0 1 9 24 +1 0 0 0 2979455391 00033333131313600033334331313136333345344 D 3 0 0 0 0 0 41 +0 0 0 2 2098797935 333344332200010331313137 E 1 1 0 0 1 10 24 +1 1 2 1 3215251839 3333443322000331313136333344332200034331313137 E 2 1 1 0 3 6 46 +0 0 2 0 447103879 0003333313131360030333535343313131363333444333333 E 3 0 0 1 3 3 49 +0 2 2 0 1484474852 333344332200033131313633334444332200034 D 2 0 0 0 0 0 39 +0 1 0 0 1936876890 3301334535343322000333113136333345353433220003453433131313633335344 D 3 0 0 0 0 0 67 +1 0 0 2 11101914 33334332200033131313633334534534332200034331313137 E 2 1 1 0 3 6 50 +0 0 1 3 2071769426 3333443322000331313137 E 1 1 0 0 1 6 22 +0 0 0 3 1750591016 33334534332200033131313633335344443322000353534433131313633334 D 3 0 0 0 0 0 62 0 0 1 3 2296597707 000333331313137 E 1 0 0 0 0 8 15 0 2 2 2 2035026977 333344332200033131313633335353444433220003535343313131363333534534 D 3 0 0 0 0 0 66 0 0 1 0 3615435868 3333453535343322000331313137 E 1 1 0 0 1 4 28 0 1 0 1 286433463 333343322000331313136333344433222000334433131313633334 D 3 0 0 0 0 0 54 -0 0 0 1 641128981 3333443322000331313136333344534332200034 D 2 0 0 0 0 0 40 -1 1 0 0 4016704642 000333331313136000333343313131363333444333333 E 3 0 0 1 3 3 45 -0 1 0 1 301282722 000333331313136000333343313131363333444333333 E 3 0 0 1 3 4 45 -0 0 0 2 1248405243 3333433220003313131363333444332200034331313136333344 D 3 0 0 0 0 0 52 -0 1 2 3 2776930897 00033103331313136000333343313132313633334443323332333 E 3 0 0 1 3 8 53 -1 0 2 2 4257616918 33335344332200033131313633334433220003433131313633334534 D 3 0 0 0 0 0 56 -1 0 1 0 657871540 000333331313136000333343313131363301334534333333 E 3 0 0 1 3 2 48 -1 1 1 1 3097493803 333343322000331313137 E 1 1 0 0 1 5 21 +0 0 0 1 641128981 3333443322000331313136333344534332200034433131313633334 D 3 0 0 0 0 0 55 +0 0 0 0 3717267715 3333443322000331313136333344332200034331313136333344 D 3 0 0 0 0 0 52 +1 1 2 0 3859883344 3333453433220003313131363333443322000344331313137 E 2 1 1 0 3 1 49 +0 1 0 2 2065592554 3333443322000331313136333344332200034331313136333344 D 3 0 0 0 0 0 52 +0 0 2 3 1099100378 0003333313131360003103334331313137 E 2 0 0 0 0 12 34 +0 0 2 1 195957222 3103334433220003313131363333453534 D 2 0 0 0 0 0 34 +1 2 2 0 2302176333 3333534433220003313131363331034534433220003433131313633334 D 3 0 0 0 0 0 58 +0 2 1 0 800046096 3333443322000331313137 E 1 1 0 0 1 7 22 +0 1 0 0 81595652 00033333101313137 E 1 0 0 0 0 9 17 1 2 2 2 4172224340 3333443322000331313136333353534453534332200034534331313136333344 D 3 0 0 0 0 0 64 0 2 2 3 1430994539 33335344332200033131313633334444433220003534 D 2 0 0 0 0 0 44 1 2 0 3 3350206943 3331034433220003313131363333445343322000344331313137 E 2 1 1 0 3 5 52 @@ -214,106 +210,100 @@ 0 0 1 2 3871652445 333343322000331313136333344332200034331313136333344333333 E 3 1 1 1 6 2 57 1 1 0 3 2270890876 000333331313136000333344331313137 E 2 0 0 0 0 10 33 1 0 0 1 3648759909 333344332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 51 -1 0 0 2 875085073 33334433220003313131363323334433220200334331313136333344333333 E 3 1 1 1 6 1 62 +1 0 0 2 875085073 33334433220003313131363323334433220200334331313136333344333333 E 3 1 1 1 6 2 62 0 0 1 1 534751733 00033333131313600033334331313136333344333333 E 3 0 0 1 3 2 44 0 0 0 2 59344083 000333331313137 E 1 0 0 0 0 11 15 1 0 2 2 1487257976 0003333313130113600033335343313131363333444333333 E 3 0 0 1 3 4 49 0 0 2 0 4218397840 0003333323130113137 E 1 0 0 0 0 9 19 -0 0 0 2 2544713693 33334453534332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 56 -1 1 0 2 599333083 333344332200033131311037 E 1 1 0 0 1 8 24 -0 2 2 3 1944252414 0003333313131360100033334433131313633334444333333 E 3 0 0 1 3 4 49 -1 1 0 3 1199767161 003033331313137 E 1 0 0 0 0 11 15 -0 1 2 0 886767707 000333331313136000333343313131363333444333333 E 3 0 0 1 3 1 45 -1 1 1 3 2190458670 33334433220010033131313633334433220003534331313137 E 2 1 1 0 3 3 50 -1 0 1 3 2793212662 3323335103433220003323131313633335344332200034331313137 E 2 1 1 0 3 4 55 -1 1 0 0 3404533955 00033333131313600033334433132313137 E 2 0 0 0 0 7 35 -1 0 1 2 754554479 00033333131313600033334331313137 E 2 0 0 0 0 6 32 -1 2 0 3 980050321 333344332200033131313633334443322000344331313136333344 D 3 0 0 0 0 0 54 -0 1 2 0 1857036310 3013233353443322000331313136333353443322000353433131313633335344333333 E 3 1 1 1 6 2 70 -0 0 0 2 2245820579 3333443322000331313137 E 1 1 0 0 1 10 22 -0 0 2 1 2034750781 00033333131313600033334331313137 E 2 0 0 0 0 10 32 -0 1 0 3 2061253154 33310343322000331313136333353443322000353433131313633334444333333 E 3 1 1 1 6 3 65 -1 1 0 3 1155067768 3333443322000331313137 E 1 1 0 0 1 8 22 -1 0 2 3 2011683368 332333443322000331313136333353443322000343313131363333534535344333333 E 3 1 1 1 6 1 69 -1 1 2 1 53982478 00033333131313600033335344331313136333344 D 3 0 0 0 0 0 41 -1 0 0 1 3967395923 333343322000331313136333344433220100034331313137 E 2 1 1 0 3 1 48 -0 2 2 0 4230099671 01000333331313137 E 1 0 0 0 0 12 17 -0 0 1 0 2046987004 333343322000331313137 E 1 1 0 0 1 5 21 -0 2 1 0 889685460 333341033220003313131013633334534433222000334 D 2 0 0 0 0 0 45 -1 0 2 2 2944593465 00033333131301136000333343313131363333444333333 E 3 0 0 1 3 4 47 -0 1 0 0 2382806395 000333331313137 E 1 0 0 0 0 9 15 -0 2 2 0 1101085436 000333333113137 E 1 0 0 0 0 12 15 -1 0 2 2 3915264515 000333331313137 E 1 0 0 0 0 10 15 -0 1 2 0 2948176981 33334433220003313131363333444332200034331313137 E 2 1 1 0 3 1 47 -1 1 2 1 26425844 000333331313136000333344331313137 E 2 0 0 0 0 8 33 -0 2 0 3 1793844775 00033333131331600033334331313136333344534333333 E 3 0 0 1 3 9 47 -0 2 0 1 3314848752 3331034433220003313131363333443322000344331313137 E 2 1 1 0 3 7 49 -1 2 2 3 3586399360 333344332200033131313633334443322000343313131363333444 D 3 0 0 0 0 0 54 -1 0 2 0 3628204451 3233334433220003313131363333443322000343313131363310334 D 3 0 0 0 0 0 55 -1 2 0 3 1147725570 33334433220003313131363333444332200034433131313633334 D 3 0 0 0 0 0 53 -1 1 1 2 737818949 00033333131313600033334331313136333353443323103333 E 3 0 0 1 3 3 50 -0 2 2 1 2579418550 3333443322000331313136233333444013322000344331313137 E 2 1 1 0 3 6 52 -1 2 0 1 2386227057 3333443322000331313137 E 1 1 0 0 1 10 22 -0 0 1 2 284753632 3333443322000331313136333344332200034331313137 E 2 1 1 0 3 1 46 -0 1 1 3 1361332821 030033331313136000333344331313137 E 2 0 0 0 0 6 33 -0 0 2 2 2320076078 000333331313137 E 1 0 0 0 0 11 15 -0 2 2 0 2597787706 000333331313137 E 1 0 0 0 0 12 15 -0 1 2 2 3858102701 3333443322000331313137 E 1 1 0 0 1 9 22 -1 2 1 2 1364688641 00033333131313600023333343313131363333452334534333333 E 3 0 0 1 3 4 53 -1 0 1 3 3593950391 333353534534332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 57 -1 2 0 0 2099450216 01000333331313136000333343313131363333444333333 E 3 0 0 1 3 4 47 -0 1 2 0 1735219752 333013433220003313131363333443322000343313131363233334510353534 D 3 0 0 0 0 0 63 -0 2 2 1 151040933 3333443322000331313137 E 1 1 0 0 1 11 22 -0 0 1 0 821532816 000333331313137 E 1 0 0 0 0 5 15 -0 2 0 0 3329844092 3333443322000331313137 E 1 1 0 0 1 11 22 -0 2 1 0 2396378161 000333331313137 E 1 0 0 0 0 8 15 -0 0 0 1 4038570805 000333331313137 E 1 0 0 0 0 10 15 -0 0 2 0 1141074303 00033333131313600033334331313136323333444333333 E 3 0 0 1 3 2 47 -0 1 1 3 2124922312 33334332200033131313633334534332200034331313137 E 2 1 1 0 3 6 47 -0 0 0 0 1118932200 2333334433220003313131363333444332200034 D 2 0 0 0 0 0 40 -0 0 2 1 3887932980 33335344332200033131313633335353534443322000343313131363333534 D 3 0 0 0 0 0 62 -1 2 2 2 1771380285 3333443322000331313137 E 1 1 0 0 1 12 22 -1 1 2 1 2783607580 3333443322000331313137 E 1 1 0 0 1 6 22 -1 2 2 3 1462851884 01000333331313137 E 1 0 0 0 0 14 17 -1 1 0 0 417604573 00033333131313600033334331313136333344333333 E 3 0 0 1 3 5 44 -0 2 1 2 3362094274 333343322000331313136333344332200034331313136333344 D 3 0 0 0 0 0 51 -1 0 0 1 3589003823 000333331313136000333344331313137 E 2 0 0 0 0 7 33 -0 2 1 1 4283752071 333345343322000331313137 E 1 1 0 0 1 8 24 -1 2 2 1 2779677982 3333443322000331313317 E 1 1 0 0 1 9 22 -1 2 2 1 2836949200 3333443322020033233131313633334443322000344331313136333344 D 3 0 0 0 0 0 58 -1 0 1 0 1075253080 333343322000331313136331033453433220003433131313633334 D 3 0 0 0 0 0 54 -0 0 1 2 4235568433 33334410332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 53 -0 1 0 0 2899854893 333343322000331313136333344332200034331313136333344333333 E 3 1 1 1 6 2 57 -0 1 0 1 2035736867 3333443322000331313137 E 1 1 0 0 1 7 22 -1 2 0 2 3715487847 3333443322000332313131363333444332200034432331313136333344 D 3 0 0 0 0 0 58 -0 1 2 3 302026676 00033333231313137 E 1 0 0 0 0 12 17 -1 2 1 2 2062334141 0003333313131360003333534331313136333345344333333 E 3 0 0 1 3 3 49 -0 1 2 1 876106233 3332335343322000331313137 E 1 1 0 0 1 10 25 -1 1 0 1 3109015875 333344332200033131313633334433220003433131313633334534 D 3 0 0 0 0 0 54 -0 2 1 3 3569026936 3333443322000331313136333344 D 2 0 0 0 0 0 28 -1 0 1 0 1480150056 333343322000331313137 E 1 1 0 0 1 4 21 -1 0 0 1 2062498283 3333443322000331313137 E 1 1 0 0 1 8 22 -0 0 1 2 4039307955 33334332200033131313633335344332200034331313136333344333333 E 3 1 1 1 6 1 59 -0 0 2 1 1742127529 00033333131313600033335343313133163333444534333333 E 3 0 0 1 3 1 50 -1 2 1 1 2494248584 3333433220003313131363333443322000343313131363333534534534333333 E 3 1 1 1 6 1 64 -0 2 2 1 2396071890 333344103322000331313137 E 1 1 0 0 1 12 24 -0 2 2 0 4214587991 33335344332200033131313633334443322000344331313137 E 2 1 1 0 3 1 50 -0 2 2 0 2519736379 3333443322000331313137 E 1 1 0 0 1 11 22 -1 0 2 1 1025284425 31033344332200033131311036333344332200034331313137 E 2 1 1 0 3 5 50 -0 0 0 0 1388058167 00033333131313600033335345353433131313633335344333333 E 3 0 0 1 3 3 53 -1 2 2 2 1389211939 33334433220003313131363333444332200034331313136333344534 D 3 0 0 0 0 0 56 -0 0 2 1 2389465817 100003333313131360003333433131313633334444333333 E 3 0 0 1 3 2 48 -1 2 0 1 335418413 3333444332201000331313136333344 D 2 0 0 0 0 0 31 -1 1 0 0 1084199948 333343322000331313137 E 1 1 0 0 1 8 21 -1 1 1 1 1671807927 33334332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 50 -0 2 2 3 1913765664 333345343322000331331137 E 1 1 0 0 1 12 24 -0 2 2 2 7220761 3333443322000331313136333344443322000344331313137 E 2 1 1 0 3 3 49 -1 2 1 2 238122383 000333331313136030033343313131363333444333333 E 3 0 0 1 3 1 45 -0 1 2 2 2670202754 333344233322000331313136333344332200034331313136333344333333 E 3 1 1 1 6 5 60 -1 0 2 1 1002433885 3333443322000331313137 E 1 1 0 0 1 7 22 -0 2 1 3 3498176890 0003333313131360003333433131313633334444333333 E 3 0 0 1 3 3 46 -0 0 2 2 3692812392 3333443322000331313136333344433220003433131313633334 D 3 0 0 0 0 0 52 -0 0 1 2 1492060310 333353443322000331313137 E 1 1 0 0 1 4 24 +0 0 0 2 2544713693 333344535343322000331313136333344332200034331313136333344333333 E 3 1 1 1 6 2 63 +1 0 0 1 2246099078 33334332200033131313633334433220003534331313136333344534333333 E 3 1 1 1 6 1 62 +1 0 1 1 1075618202 333353433220003313131363333443322001003433131313633334 D 3 0 0 0 0 0 54 +1 1 0 1 142531092 000333331313137 E 1 0 0 0 0 9 15 +0 0 2 2 3577824566 3333535345343322000331313137 E 1 1 0 0 1 9 28 +1 1 2 2 3968295866 3103334433220003313131363310335344432332200035353534331313137 E 2 1 1 0 3 4 61 +1 0 2 2 677294027 3333443322000331313136333344332200034233313131363333444333333 E 3 1 1 1 6 1 61 +0 1 2 2 2816151472 333343322000331313137 E 1 1 0 0 1 11 21 +0 2 1 3 2274506847 0003333310131313600033334433131313633335344534333333 E 3 0 0 1 3 4 52 +0 0 0 3 2565863185 33334443322000331313137 E 1 1 0 0 1 10 23 +0 1 0 1 2696712167 0003333313131360003333444331313136333344534333333 E 3 0 0 1 3 1 49 +1 0 2 1 3739889397 3333443322000331313137 E 1 1 0 0 1 8 22 +0 1 2 2 699344802 3333443322000331313136333353444 D 2 0 0 0 0 0 31 +0 2 2 2 2016764750 333344534332200033130113136333345344332200034433131313633335344 D 3 0 0 0 0 0 63 +0 2 0 1 375882252 333344332200033131313633334534433220003433131313633335344 D 3 0 0 0 0 0 57 +0 1 2 0 1219755789 003033331313137 E 1 0 0 0 0 9 15 +1 0 0 0 3975991997 233333433220003313131363333443322000344331313137 E 2 1 1 0 3 3 48 +0 0 1 1 4283414327 0003333313131360003333433133113633334433331033 E 3 0 0 1 3 4 46 +0 2 0 0 237763086 33323345343322100003331131363333444534 D 2 0 0 0 0 0 38 +1 2 2 1 601950984 3333443322000331310131363333444013323220003433131323137 E 2 1 1 0 3 4 55 +1 1 2 0 3769542690 000333331313136000103333433131301136333345344333333 E 3 0 0 1 3 2 51 +0 0 1 3 3620382444 333343322000331313136333344332200034331313136333344 D 3 0 0 0 0 0 51 +1 1 2 1 1996498373 0003333310131313600033334331313137 E 2 0 0 0 0 9 34 +1 2 0 2 81580160 000333331313137 E 1 0 0 0 0 13 15 +0 2 2 2 48826217 0003333313131360003333433131313633334444333333 E 3 0 0 1 3 4 46 +0 2 2 1 2423360543 3333445343322000331313137 E 1 1 0 0 1 10 25 +0 1 0 1 245350259 33334433220003313131363333444 D 2 0 0 0 0 0 29 +1 0 1 1 3953627133 33334433220003313131363333534 D 2 0 0 0 0 0 29 +1 1 1 1 369183382 000333331313137 E 1 0 0 0 0 5 15 +1 2 2 0 3357606057 3333535344332200203331313137 E 1 1 0 0 1 9 28 +1 0 2 3 30151500 3333443322000331313136333344332200034331313137 E 2 1 1 0 3 6 46 +0 2 0 0 216915040 33233344433220003313131363333453443322002033443313131363333453534 D 3 0 0 0 0 0 65 +0 0 2 3 1340921706 33334433223200033131313633334433220003443313131363333444 D 3 0 0 0 0 0 56 +0 1 0 1 2796264591 333352334433220003313131363333443322000343310131313633334534 D 3 0 0 0 0 0 60 +0 1 2 1 3719777914 3333443322000331313137 E 1 1 0 0 1 9 22 +1 0 2 0 877783907 333353443322000331331137 E 1 1 0 0 1 7 24 +0 0 1 3 2904861255 00033333131301136000333343313131363333444333333 E 3 0 0 1 3 3 47 +1 1 2 0 4105291231 00033333231313137 E 1 0 0 0 0 8 17 +1 0 0 2 2802141170 333344332200033131313633334443322000233433131313633334 D 3 0 0 0 0 0 54 +0 0 2 3 1837757901 333344332200033131313633301344332200034331313136333344 D 3 0 0 0 0 0 54 +0 2 0 0 140704207 00033333131313600033334433131313633334444333333 E 3 0 0 1 3 2 47 +0 1 2 2 1227410020 3333443322000331313137 E 1 1 0 0 1 9 22 +1 0 2 3 111323328 3333534433220003313131363333535344332220003343313131363333535344 D 3 0 0 0 0 0 64 +0 0 1 3 3643877492 333344332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 51 +1 2 2 1 830995905 33103344332200033131313633334443322000343313131363333444 D 3 0 0 0 0 0 56 +1 0 0 1 228433604 000333331313136000333343313311363333444333333 E 3 0 0 1 3 5 45 +0 0 1 2 1222744074 33334332200033131313633334433220003433131013137 E 2 1 1 0 3 4 47 +0 1 2 1 3022446966 333343322000331313136333344332200034331313137 E 2 1 1 0 3 6 45 +0 0 2 0 688961175 103333443322001003313131363333453443322000353534 D 2 0 0 0 0 0 48 +1 1 0 3 2818488289 3333443322000331313136333344332200034331313137 E 2 1 1 0 3 8 46 +1 2 2 3 1005422555 333345343322000331313136333345344332200034433131313633334 D 3 0 0 0 0 0 57 +1 0 0 1 872059498 000333331313137 E 1 0 0 0 0 9 15 +0 0 2 1 874421550 000333331313137 E 1 0 0 0 0 10 15 +1 0 0 3 1168112829 000333331313136000333344331313137 E 2 0 0 0 0 10 33 +0 1 0 3 2161155864 33335353534433220003313131363333453443322000103433131313633334535344 D 3 0 0 0 0 0 68 +0 0 0 1 2069169452 2333334534332200033131313633335344433220003433131313633334 D 3 0 0 0 0 0 58 +1 0 0 2 663181144 3333443322000331313136333353443322000343313131363333444333333 E 3 1 1 1 6 2 61 +1 1 0 0 1456726871 000333331313137 E 1 0 0 0 0 8 15 +0 0 0 0 1477286974 33013345353534332200033132313137 E 1 1 0 0 1 8 32 +1 1 0 0 857219414 000333331313136000333344331313136333344 D 3 0 0 0 0 0 39 +0 0 1 0 359354242 333344332200033101313137 E 1 1 0 0 1 4 24 +0 1 2 2 1068864933 00033333131313600033334331313137 E 2 0 0 0 0 11 32 +1 2 1 2 2884804118 000333331313136000333343313131363333444333333 E 3 0 0 1 3 5 45 +1 1 2 2 3820849284 3333433220003313131363333534535353534332200034331313136333345344333333 E 3 1 1 1 6 3 70 +0 2 0 0 2055967025 3333534443322000331313136333345344 D 2 0 0 0 0 0 34 +0 0 2 1 666928268 333344332200033101313136333344433220003443313131363310334 D 3 0 0 0 0 0 57 +0 1 1 2 3963350650 333343322000331313136333344332200034331313137 E 2 1 1 0 3 4 45 +0 1 1 1 2488363720 33334433220003313131363333534433220003433131313633334 D 3 0 0 0 0 0 53 +0 1 1 0 3093694816 3333443322000331313137 E 1 1 0 0 1 3 22 +0 0 0 1 1687313534 000333331313136000333343313131363333445353534333333 E 3 0 0 1 3 3 51 +1 1 0 3 2802078776 333344332200033131313633334534332210000344331331136333344 D 3 0 0 0 0 0 57 +1 0 0 0 2611042688 3333443322000331313137 E 1 1 0 0 1 6 22 +1 0 1 3 3661663245 3333534332200033131313633335353443322000353433131313633335344 D 3 0 0 0 0 0 61 +0 1 1 2 2392278758 20003333331313136000333353433131310137 E 2 0 0 0 0 7 38 +1 2 1 2 747684203 3333453433220003313131363333445353401103322000343313131363333534 D 3 0 0 0 0 0 64 +1 0 0 2 1282150918 3333443322000331313137 E 1 1 0 0 1 8 22 +1 2 1 0 3329502992 00033333131313600033334331313136333344 D 3 0 0 0 0 0 38 +1 0 0 0 3574189389 33334534332200033131313633334433220001034331313137 E 2 1 1 0 3 3 50 +1 1 1 3 987487304 333353443322000331313137 E 1 1 0 0 1 5 24 +0 0 1 2 2269477968 00033333131313600033334331313137 E 2 0 0 0 0 7 32 +0 1 1 2 3286341726 33334433220003313131363333444332200034331313137 E 2 1 1 0 3 1 47 +0 0 2 2 1914367938 3333443322000331313137 E 1 1 0 0 1 9 22 +0 1 1 0 1287227828 333353453433220003313131013633334 D 2 0 0 0 0 0 33 +0 0 1 3 3663427114 333343322000331313136333344332200034331331136333103534433332333 E 3 1 1 1 6 2 63 +1 2 0 1 1498213367 33334453410323322000331313136333344433220003433131313633334 D 3 0 0 0 0 0 59 +1 0 1 3 1533192627 0001033333131313600033334331313136333344333333 E 3 0 0 1 3 4 46 +0 1 0 1 1706827836 33334433220003313131363333444013322000353433131313633334 D 3 0 0 0 0 0 56 +0 1 1 0 3311667583 000333331313137 E 1 0 0 0 0 5 15 +0 0 1 2 1492060310 333353443322000331313137 E 1 1 0 0 1 5 24 1 1 1 1 2548866966 00031033331313137 E 1 0 0 0 0 5 17 1 2 0 0 2038754940 000333331313136000333344331313137 E 2 0 0 0 0 9 33 1 2 0 2 4099178877 000333331313137 E 1 0 0 0 0 13 15 @@ -321,282 +311,292 @@ 0 0 1 1 3548489726 333343322000331313136332333443322000353534331313137 E 2 1 1 0 3 4 51 1 0 1 3 2882313562 0003333313131360003333433131313630133345344333333 E 3 0 0 1 3 1 49 0 1 2 3 2723862117 000333331313137 E 1 0 0 0 0 12 15 -1 1 2 0 2090529802 333344332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 51 -0 2 1 1 2720081663 3333443322000331313136333345343322000344331313137 E 2 1 1 0 3 4 49 -0 0 1 2 2190117177 0003333313131360003333534331313137 E 2 0 0 0 0 7 34 -1 0 0 3 3769938973 3333443322000331313136333344332200034331313136333344 D 3 0 0 0 0 0 52 -1 1 2 1 4054797698 0003333313131360003333443313131363333444333333 E 3 0 0 1 3 2 46 -0 1 2 0 1477992365 333344013322000331313136333344332200034331313136333344 D 3 0 0 0 0 0 54 -0 2 2 1 2583018554 33334433220003313131363333444332200035344331313136333344 D 3 0 0 0 0 0 56 -0 2 1 0 3599463099 333345353433220003313131363333443322000353453433131313633334 D 3 0 0 0 0 0 60 -0 0 2 2 3172293761 0003333313131360003333433131301136333353444333333 E 3 0 0 1 3 6 49 -1 1 1 2 3817898447 33334332200033131313610333344332200035343313131363323334 D 3 0 0 0 0 0 56 -1 0 2 1 1490620596 3333443322000233313131363300113344332200034331313136333344 D 3 0 0 0 0 0 58 -0 0 0 2 1293887613 333344332202003331313137 E 1 1 0 0 1 8 24 -0 2 1 0 1683704931 33335345343322000331313136333344332200034 D 2 0 0 0 0 0 41 -0 0 0 3 1544155919 000333331313137 E 1 0 0 0 0 12 15 -0 0 0 2 4062214807 33013343322000331313137 E 1 1 0 0 1 11 23 -0 2 0 1 3581275385 0003333313131360003333534331313136333344444333333 E 3 0 0 1 3 2 49 -0 1 2 3 2243708699 33334433220200333131313633334534332200035343313131363333444333333 E 3 1 1 1 6 2 65 -1 0 1 3 72446857 333343322000331313136333344332200034331313137 E 2 1 1 0 3 4 45 -0 0 1 3 217575525 0003333313131360003333534331313136333344333333 E 3 0 0 1 3 4 46 -1 1 1 3 377247387 01333343322000331313137 E 1 1 0 0 1 7 23 -1 2 2 1 3656956049 000333331313136000333344331313136333344433332333 E 3 0 0 1 3 3 48 -0 2 1 1 3907308546 3333453534332200033131313633335353443322000343313131363333534 D 3 0 0 0 0 0 61 -1 1 2 0 3706231036 3333433220003313131363332334433220003433131313633334433233333 E 3 1 1 1 6 2 61 -1 2 0 2 2256206212 331033443322000331313137 E 1 1 0 0 1 11 24 -1 2 1 2 3512843309 333344332200033131313632333344332200034331313137 E 2 1 1 0 3 4 48 -1 1 2 1 2885573219 3333443322000331313137 E 1 1 0 0 1 6 22 -0 2 1 0 2848247750 0003333313131360003333433131313633334534534333333 E 3 0 0 1 3 1 49 -1 2 2 1 181215459 33334433220003313131363333445343322000343313131363333534 D 3 0 0 0 0 0 56 -1 1 2 2 2295361180 33335103453433220003313131363333443322000353433131331610333344333333 E 3 1 1 1 6 2 68 -0 1 2 2 3185757944 000333331313136000333344331313137 E 2 0 0 0 0 9 33 -1 1 2 1 2315586636 00033333131313600031033343313131363333444333333 E 3 0 0 1 3 4 47 -1 0 1 2 345409060 3333433220003313131363333443322000353433131013136333344333333 E 3 1 1 1 6 1 61 -1 0 2 1 1982156594 33334534332200033131313633334433220003433131313633334534 D 3 0 0 0 0 0 56 -1 1 0 3 2826887221 333343322000331313137 E 1 1 0 0 1 11 21 -0 0 0 3 3482087992 333344332200033131313633334433220003443313131363333444333333 E 3 1 1 1 6 2 60 -1 2 2 1 179410054 333344233322000331313136301333445341033220003534331313137 E 2 1 1 0 3 6 57 -0 0 1 0 3338016350 33334332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 50 -1 1 2 3 3229952058 3333433220003313133163333443322000343313231313633334535344333333 E 3 1 1 1 6 3 64 -0 1 1 1 3830000532 333343322000331313137 E 1 1 0 0 1 6 21 -0 0 0 1 3895333671 3333534433220200333101313136333344332200034433131313633334 D 3 0 0 0 0 0 58 -1 1 2 0 3781299192 333344332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 51 -0 0 0 3 4038723726 33334443322000331313137 E 1 1 0 0 1 7 23 -1 2 2 3 1905411407 3333443322000331313136323333534443323220003433131313633334 D 3 0 0 0 0 0 58 -0 0 2 0 3960252200 3333453433220003313131363333453534433220003443313131363333534 D 3 0 0 0 0 0 61 -0 1 0 0 175821106 00303333131313600033334331313137 E 2 0 0 0 0 9 32 -1 1 2 0 3019007750 000333331313137 E 1 0 0 0 0 8 15 -1 1 1 2 1901018477 000333331313137 E 1 0 0 0 0 6 15 -1 0 0 1 1831368069 33334433220003313131363333443322000344331313136333344 D 3 0 0 0 0 0 53 -1 1 0 1 2109266322 3333443322000333113136333344332200034331313136333353444333333 E 3 1 1 1 6 1 61 -0 1 1 0 4264606077 33323344332200203331313137 E 1 1 0 0 1 2 26 -1 2 1 2 4020893067 3330134433220003313131363333443322322000334331313136333344 D 3 0 0 0 0 0 58 -0 0 0 0 3319433094 31033344332201000331313136323333453433220003534433131313633334 D 3 0 0 0 0 0 62 -1 1 1 2 4051592055 0003333313131360003333433131313633334432333333 E 3 0 0 1 3 2 46 -1 1 0 3 1941014989 0003333313131360003333453433131313633334433330133 E 3 0 0 1 3 7 49 -1 2 1 3 1425891611 33334433220003313131363333443322000343313131363333444333333 E 3 1 1 1 6 1 59 -1 2 1 3 958880578 000333331313136000333343313131363333444333333 E 3 0 0 1 3 2 45 -0 0 2 1 3111335297 00033333131313600033334331313137 E 2 0 0 0 0 10 32 -0 0 0 2 1078261037 33334433220003313131363333534433220003433131313633334534 D 3 0 0 0 0 0 56 -1 1 0 3 1766399836 3333433220003313131363333535353534453433220003535343313131363333453534 D 3 0 0 0 0 0 70 -1 1 2 0 2275218395 00033333311313600033334331313136333344333333 E 3 0 0 1 3 6 44 -1 0 0 3 3261462376 3333443322000331313136333344535343322000353443313131360133334 D 3 0 0 0 0 0 61 -1 0 1 1 2000836232 333343322000331313136333345343322000343313131363310334 D 3 0 0 0 0 0 54 -0 0 1 1 1279366751 333343322000331313136333344332200034331313136333344 D 3 0 0 0 0 0 51 -0 1 1 3 3869590528 3333534433220003313131363331035344332200034331313137 E 2 1 1 0 3 2 52 -0 1 0 0 2630105984 00033333131313600033334331313137 E 2 0 0 0 0 9 32 -0 1 1 3 2591356114 00033333131313600033334331313137 E 2 0 0 0 0 8 32 -1 1 2 3 3421815037 000323333313131360001033334433131313633334433301333 E 3 0 0 1 3 5 51 -0 1 2 3 1916121642 00033333131313600033334331313137 E 2 0 0 0 0 12 32 -1 1 2 0 3577166102 33334433220003313131363333534433220003433131313633334 D 3 0 0 0 0 0 53 -0 0 2 3 3425415547 0003333313131360003333443313131363333444 D 3 0 0 0 0 0 40 -1 1 0 0 3032010922 333343322000331313136333344433220003433131313633334 D 3 0 0 0 0 0 51 -1 0 1 0 3834690877 33334332200033131313633334433220010034331313136333353534 D 3 0 0 0 0 0 56 -1 0 1 2 1856374448 33334332200033131313633334433220003433311313633334 D 3 0 0 0 0 0 50 -1 2 1 0 2577790531 000333331313137 E 1 0 0 0 0 7 15 -0 2 0 3 648062555 33334534433220003313131363333444 D 2 0 0 0 0 0 32 -1 2 0 0 2679042374 3333443322000331313136333233445343322000233433131313633334 D 3 0 0 0 0 0 58 -0 2 2 2 2551055621 333344332200033131313633334443322000231034433131013137 E 2 1 1 0 3 1 54 -1 0 1 2 2200259636 33335344332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 53 -0 0 1 2 3692471018 000333331313136000333343313131363333444333333 E 3 0 0 1 3 2 45 -0 1 1 0 272533793 23013333433220003313131363333534534332200034331313137 E 2 1 1 0 3 1 53 -1 2 0 1 829615680 333345343322000331313136333344332200034331313136333345344 D 3 0 0 0 0 0 57 -0 2 2 2 1173237441 00031033331313136000333343313131363333535345345344333333 E 3 0 0 1 3 7 56 -1 0 1 3 1905238918 33334534332200033131313633334 D 2 0 0 0 0 0 29 -0 1 1 3 860586373 00033333131313600033334331313136333344333333 E 3 0 0 1 3 4 44 -1 2 2 3 656977129 3333443322000331313136333344433220003433131313633334 D 3 0 0 0 0 0 52 -0 0 0 1 3913479045 000333331313137 E 1 0 0 0 0 10 15 -1 0 1 1 1917709695 103233334332200033131313633334534332200034331313136333353534 D 3 0 0 0 0 0 60 -1 1 2 3 2684206150 000333331313136000333353433131313633335353444333333 E 3 0 0 1 3 6 51 -1 1 1 1 794535818 103333433220003313131363333443322000343313130113633334 D 3 0 0 0 0 0 54 -1 0 0 0 1593660521 00033333131313600033334331313136333344534333333 E 3 0 0 1 3 2 47 -1 1 2 0 4016377760 33334332200033131313633335344332200034433131313633335344333333 E 3 1 1 1 6 2 62 -0 2 2 2 2163831694 33334433220020333131313633334534433232200034433131313633334 D 3 0 0 0 0 0 59 -0 2 2 3 3963130683 000333331313137 E 1 0 0 0 0 15 15 -1 2 2 2 321165209 33334433220003313131363333445343322000343313131363103334 D 3 0 0 0 0 0 56 -1 1 2 1 2395211391 0003333313131360003333534331313136333345344333333 E 3 0 0 1 3 5 49 -0 0 2 0 2355140353 333353443322000331313137 E 1 1 0 0 1 7 24 -0 0 0 3 285381459 000333331313137 E 1 0 0 0 0 12 15 -1 0 0 1 1384645680 00033333131313600033335343313131363333444333333 E 3 0 0 1 3 4 47 -1 1 2 2 1543625822 333344332200033131323136333344332200034433131313633334423333333 E 3 1 1 1 6 1 63 -0 0 0 3 4285510404 3333443322000331313136333344332200034331313136333344 D 3 0 0 0 0 0 52 -0 0 1 1 3185189177 00033333131313600033335343313131363333534534333333 E 3 0 0 1 3 3 50 +1 1 2 0 2090529802 3333443322000331313136333344 D 2 0 0 0 0 0 28 +0 1 2 0 2152931598 103333443322000331313137 E 1 1 0 0 1 8 24 +1 2 1 3 4288652481 000333331313136000333343313131363333444333333 E 3 0 0 1 3 3 45 +1 1 1 0 2468279471 3333443322000331313137 E 1 1 0 0 1 3 22 +1 1 2 3 4063719293 310331034433220100032331313136333344332200034331313136333344 D 3 0 0 0 0 0 60 +1 0 1 2 1175301620 0003333313131360003333433131313633335344333333 E 3 0 0 1 3 4 46 +1 1 0 0 1521756687 3333443322000331313136333344 D 2 0 0 0 0 0 28 +1 2 1 3 2014154733 33334433220003313131363333534534332200034331313136333344 D 3 0 0 0 0 0 56 +0 2 0 1 1053599347 3333445343322000331313137 E 1 1 0 0 1 10 25 +0 2 0 2 3263987854 0300333313131360003333534443313131363333444 D 3 0 0 0 0 0 43 +0 1 0 1 1460029503 301333443322000331313136333345345343322000353433131313631033344 D 3 0 0 0 0 0 63 +1 0 0 1 2243173254 0003333313131360003333443313131363333444333333 E 3 0 0 1 3 4 46 +1 1 0 2 3787894542 3333433220003313131363333445343322000345343313131363333534 D 3 0 0 0 0 0 58 +0 1 2 2 4240408471 00023333331313136000333343313131363333444534333333 E 3 0 0 1 3 2 50 +1 0 2 3 487293829 000333331313136000333343313131363333444333333 E 3 0 0 1 3 6 45 +0 2 2 3 695309886 000333331313136000333103443313131363333445344 D 3 0 0 0 0 0 45 +1 2 2 2 1180463206 0003333313131360003333433131313633334534534534333333 E 3 0 0 1 3 3 52 +1 2 2 0 2597812674 333345343322000331313137 E 1 1 0 0 1 10 24 +1 0 0 1 2404697087 3333443322000331313136333344332200034433131313633334 D 3 0 0 0 0 0 52 +0 0 0 2 153069866 333353534332200033131313633334433220003534331313137 E 2 1 1 0 3 9 51 +0 2 2 1 2584754882 333344332200033131313633334535345344332200034534331313137 E 2 1 1 0 3 2 57 +0 0 2 2 1823990911 000333331313137 E 1 0 0 0 0 11 15 +1 2 2 1 207268330 33334433220003313131363333453443322000344331313136333344 D 3 0 0 0 0 0 56 +1 1 0 1 1736319688 000333331313137 E 1 0 0 0 0 9 15 +0 2 2 3 2449891644 333344332220003331313137 E 1 1 0 0 1 14 24 +0 0 2 0 905886696 000333331313137 E 1 0 0 0 0 9 15 +1 0 2 3 3608697324 333345353433220003313131362333335344332200034331313136333344 D 3 0 0 0 0 0 60 +0 2 0 3 3437603528 33334443322000331313137 E 1 1 0 0 1 13 23 +0 0 0 0 384483849 310333401332200033131313633334433220003433131313633335344 D 3 0 0 0 0 0 57 +1 1 2 2 2180780351 3333534534332200033131313633334443322000353534 D 2 0 0 0 0 0 46 +0 0 1 2 299294849 00033333131313600033334331313136333353453534333333 E 3 0 0 1 3 5 50 +1 2 0 1 1889285096 00010333331313137 E 1 0 0 0 0 12 17 +1 2 2 3 256337668 3333443322000331313137 E 1 1 0 0 1 13 22 +0 1 1 3 2279826200 3103334332220003331313136103333443322000344331313136333344333333 E 3 1 1 1 6 2 64 +1 2 1 3 235774986 00033333131313600033334331313136333353444333333 E 3 0 0 1 3 4 47 +0 2 0 1 2665869767 333353443322000331013131363333444332200034331313136333344 D 3 0 0 0 0 0 57 +1 1 1 2 1617138107 000333331313137 E 1 0 0 0 0 6 15 +0 1 1 2 653090020 0003333313131360003333433131313633335344333333 E 3 0 0 1 3 3 46 +1 2 1 3 2907918539 3333442333220003313131363013334433221000034331313137 E 2 1 1 0 3 5 52 +1 2 1 3 4018391766 333345343322000331313136333344332200034331313136333344333333 E 3 1 1 1 6 3 60 +1 2 1 2 515931708 000333331313137 E 1 0 0 0 0 9 15 +1 2 2 2 1733840098 33334534332220003331313136333344433220003453433131313633310344 D 3 0 0 0 0 0 62 +0 1 2 1 981590103 3333433220002333131013136323333444332200034 D 2 0 0 0 0 0 43 +0 0 0 1 1732398385 000333331313136000333343313131363333444333333 E 3 0 0 1 3 3 45 +1 2 0 0 2198011114 000333331313136000333343323131313633334444 D 3 0 0 0 0 0 42 +1 0 2 3 3795498809 3333443322000331313137 E 1 1 0 0 1 9 22 +1 1 2 3 3305624935 00033333131313600033335343313131363333535344534333333 E 3 0 0 1 3 4 53 +1 2 0 2 2519541312 333344332200033131313633334443322000343313131363333444333333 E 3 1 1 1 6 4 60 +0 2 2 2 2737960456 3333443322000331313137 E 1 1 0 0 1 12 22 +1 2 1 0 1267092303 3333443322000331313137 E 1 1 0 0 1 5 22 +1 2 2 1 2551399761 3333443322000331313136333344534332200034331313136333344 D 3 0 0 0 0 0 55 +0 2 2 0 1327835381 00033333131313600023323333443313131363333534453534534333333 E 3 0 0 1 3 2 59 +0 2 2 2 1906708068 3333453534332200033131313633335352334535353535353453441033220003535353433131313633334534 D 3 0 0 0 0 0 88 +0 2 2 3 339869836 33334433220003313131363333444433220003433131313633334534 D 3 0 0 0 0 0 56 +1 1 0 3 674988136 3333443322000331313136333344534332200034433131313633334 D 3 0 0 0 0 0 55 +0 1 1 1 1904356666 000333331313137 E 1 0 0 0 0 6 15 +1 1 2 0 1556246134 3333534433220003313131363333443322000343313131363333534 D 3 0 0 0 0 0 55 +1 1 2 2 105775840 333343322000331313136333344332200034433131313633335344 D 3 0 0 0 0 0 54 +0 0 1 3 3133947252 333344332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 51 +0 1 1 3 962515889 000333331313137 E 1 0 0 0 0 8 15 +1 2 0 2 3418943117 000333331313137 E 1 0 0 0 0 13 15 +1 0 2 2 3902815413 3330135344332200033101313137 E 1 1 0 0 1 9 28 +0 1 2 2 3729156537 33334535343322000331313136333344332200034433131313633334453535353534333333 E 3 1 1 1 6 3 74 +0 2 1 1 4178914928 3333433220003313131363333443322000353433131313633335344 D 3 0 0 0 0 0 55 +1 1 0 3 185354297 000333331313137 E 1 0 0 0 0 11 15 +1 0 0 3 3261462376 3333443322000331313136333344535343322000353443313131360133334534 D 3 0 0 0 0 0 64 +0 0 1 0 3492507380 00033323331313136000333343313131363332334410333333 E 3 0 0 1 3 3 50 +0 1 2 2 826642758 3330134433220003313101313633334443322000343313131360133334 D 3 0 0 0 0 0 58 +0 0 1 0 3900860633 33334410332200033131313633334 D 2 0 0 0 0 0 29 +1 2 2 3 3946730970 000333331313137 E 1 0 0 0 0 14 15 +0 0 1 2 2293856365 000333331313137 E 1 0 0 0 0 7 15 +0 2 0 1 1819932408 000333331313137 E 1 0 0 0 0 13 15 +1 0 1 3 2606010466 333353433220003313131363333443322000103433131313633334534333333 E 3 1 1 1 6 1 63 +1 0 1 1 1229731291 3333433221000033131313633334433220003433131313633334 D 3 0 0 0 0 0 52 +1 2 0 3 517162259 0003333313131013600033334331313110363333444333333 E 3 0 0 1 3 9 49 +1 0 2 0 4280810329 000333331313136002033333433131313633335344534333333 E 3 0 0 1 3 4 51 +1 0 2 1 3125796743 000333331313136000333353433131313630133353444333333 E 3 0 0 1 3 2 51 +0 1 0 2 3126797143 333344332200100331313136333345353534443322000353534331313137 E 2 1 1 0 3 2 60 +0 1 0 2 1864014395 3333534443322320200333131313633334534 D 2 0 0 0 0 0 37 +1 2 0 0 2271575720 00033333131323137 E 1 0 0 0 0 11 17 +0 2 1 2 3144241413 3333433220003313131363333444332200034331313137 E 2 1 1 0 3 4 46 +0 2 2 2 4068581309 33310344433220003313231313633334444332200034331313137 E 2 1 1 0 3 2 53 +0 2 1 2 2537995092 3333443322000331313137 E 1 1 0 0 1 9 22 +0 2 2 3 79508493 000333331313136000333344331313136333353445344333333 E 3 0 0 1 3 3 51 +0 1 1 1 3305432865 333343322000331313136333344332200034331313137 E 2 1 1 0 3 2 45 +0 2 0 0 3533445030 33334433220003313131363333444534332200034 D 2 0 0 0 0 0 41 +0 1 2 2 4279661229 3333433220003313131363333445343322000353433131313633334 D 3 0 0 0 0 0 55 +0 0 2 2 2607086505 10333344332200033131013137 E 1 1 0 0 1 9 26 +0 1 2 3 2995349571 33323343322000331313136333344332200035353534331313137 E 2 1 1 0 3 10 53 +0 1 2 0 3826193247 0003333313131360003333433131313633335344534333333 E 3 0 0 1 3 4 49 +0 1 0 2 2186749350 000333331313136000333344534331313137 E 2 0 0 0 0 8 36 +1 2 0 2 4163677690 3333453534332200033131313633335353444332200034331313231362333335344 D 3 0 0 0 0 0 67 +0 0 2 0 1565718288 0003332333131323136000333344331313136333353444 D 3 0 0 0 0 0 46 +1 1 2 3 294577303 33334332200010331313136333344433220003433131313633334 D 3 0 0 0 0 0 53 +1 1 0 0 1458300442 330110334443322000331313137 E 1 1 0 0 1 4 27 +1 2 1 3 3826895228 333343322000331313136333345343322000343313131363333444333333 E 3 1 1 1 6 1 60 +0 1 0 2 1167393028 3333445343322000331313137 E 1 1 0 0 1 9 25 +1 2 2 1 3688957962 3333443322000331313137 E 1 1 0 0 1 10 22 +0 2 2 2 680907518 00033333131313600033334534331313137 E 2 0 0 0 0 13 35 +1 1 1 2 2684072098 3333534534332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 55 +1 2 1 3 4288189189 30003333132313137 E 1 0 0 0 0 10 17 +1 1 0 0 570170385 333343322000331313137 E 1 1 0 0 1 8 21 +1 1 1 1 2232655433 000333331313137 E 1 0 0 0 0 5 15 +1 2 0 2 1939231066 00033333131313600033334331313137 E 2 0 0 0 0 13 32 +1 2 2 3 1632047267 33334433220003313131363333444332200034331313137 E 2 1 1 0 3 8 47 +0 1 0 0 889515242 0003333313131360003333433131313633334534333333 E 3 0 0 1 3 7 46 +0 1 2 1 3139736749 000333331313137 E 1 0 0 0 0 10 15 +1 2 0 2 342457304 000333331313136000333344331313136333344444333333 E 3 0 0 1 3 1 48 +1 0 1 3 46412681 3333433220003313131363333453433220003534331313137 E 2 1 1 0 3 3 49 +0 0 2 0 2105185799 000333331313136000333343313131363333444333333 E 3 0 0 1 3 4 45 +1 2 0 0 2211131024 00033333131313600033334331313137 E 2 0 0 0 0 11 32 1 2 2 3 2772816820 00033333131313600033334433101313136333344433323333 E 3 0 0 1 3 5 50 1 0 2 2 786233446 33334534332200033131313633335344332200034331313136333344 D 3 0 0 0 0 0 56 1 0 0 2 1426438277 00033333131323137 E 1 0 0 0 0 10 17 0 0 1 3 3755864738 00033333131313600033334331313136333344333333 E 3 0 0 1 3 4 44 -0 1 1 1 3920851190 33334332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 50 -0 1 0 0 4094456307 3333534534332200033131323136333344 D 2 0 0 0 0 0 34 -0 1 2 3 3618122171 3333443322000331313137 E 1 1 0 0 1 11 22 -0 1 2 2 3776880834 3333444332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 52 -0 2 2 1 3960978620 3333443322000331313137 E 1 1 0 0 1 11 22 -1 0 2 0 595028849 000333331313137 E 1 0 0 0 0 8 15 -1 0 2 2 2307314080 333353443322000331313136333345103433220002334331313137 E 2 1 1 0 3 7 54 -1 2 2 3 3287785467 33334534332200033131313633334443322000345353433131313633334 D 3 0 0 0 0 0 59 -1 1 0 3 3912293756 3333443322000333113137 E 1 1 0 0 1 9 22 -1 1 2 0 580605929 333343322000331313137 E 1 1 0 0 1 8 21 -0 2 1 0 643620488 33334433221000033131313633301353534433220003535353534 D 2 0 0 0 0 0 53 -1 0 0 2 1724053199 33334433220100033131313633335344332200034433131313633334 D 3 0 0 0 0 0 56 -0 2 2 3 1892958193 000333331313136000333353453433131313633334453535344 D 3 0 0 0 0 0 51 -1 2 0 3 1965039035 0003333313131360003333433131313633334444333333 E 3 0 0 1 3 6 46 -1 0 2 3 1554493661 00033333131313600033334331313137 E 2 0 0 0 0 11 32 -0 2 2 0 1563964813 000333233313131360003332334433131313633334444333333 E 3 0 0 1 3 3 51 -1 0 2 3 4230783551 00033333131313600033334331313137 E 2 0 0 0 0 11 32 -1 1 2 1 2272864847 000333331013131362000333334331313136333344333333 E 3 0 0 1 3 5 48 -0 2 1 1 4193581479 333343322000331313137 E 1 1 0 0 1 9 21 -1 1 0 3 3419443454 000333331313137 E 1 0 0 0 0 11 15 -0 1 0 0 1316195819 33334443322000331313136333344 D 2 0 0 0 0 0 29 -0 1 0 1 286387138 3333444332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 52 -1 2 1 1 75255292 31033353433220003313131363330134433220003433131313633334534 D 3 0 0 0 0 0 59 -1 2 1 2 1056755976 33210333534433220003313231013137 E 1 1 0 0 1 6 32 -1 1 0 0 794888889 000333331313137 E 1 0 0 0 0 8 15 -0 1 1 3 156099054 3333443322000331313137 E 1 1 0 0 1 5 22 -0 1 1 1 944480075 333343322000331313137 E 1 1 0 0 1 6 21 -1 2 1 0 2731225269 00033333131313600033334333113137 E 2 0 0 0 0 7 32 -1 1 0 0 2393677527 00033333131313600303334331323131363333444 D 3 0 0 0 0 0 41 -1 2 2 0 3244911035 32333353443322000331313137 E 1 1 0 0 1 8 26 -0 2 1 3 2453584747 23333344103322000331313137 E 1 1 0 0 1 9 26 -1 1 2 1 644379874 00033333131313600033334331313136333013444 D 3 0 0 0 0 0 41 -0 1 1 0 1407861717 00033333131313600033334331331137 E 2 0 0 0 0 5 32 -0 1 1 3 1477929851 0003333313131360003333534332313131363333534433103333 E 3 0 0 1 3 4 52 -0 0 0 2 442300000 00033333131313600033334534331313137 E 2 0 0 0 0 10 35 -1 0 0 1 368889020 3333443322000331313136333344332200034433131313633334 D 3 0 0 0 0 0 52 -0 1 1 1 1825341125 33335344332200033131313633334 D 2 0 0 0 0 0 29 -1 0 2 0 1730073632 000333331313136000333343313131363333444333333 E 3 0 0 1 3 2 45 -0 2 0 3 4202102681 333344332200033131313601333344534332200034433131313633323344 D 3 0 0 0 0 0 60 -1 2 1 0 622687846 33334332200033131313633013013443322000343310131313633334 D 3 0 0 0 0 0 56 -0 0 0 1 156605170 33334433220003313133163333444332200034331313136333352334 D 3 0 0 0 0 0 56 -1 0 1 0 2370856377 3333433220003313131363233334433220003433131313633334 D 3 0 0 0 0 0 52 -1 0 0 0 4270955262 000333331313136000333343313131363333534534534333333 E 3 0 0 1 3 2 51 -0 2 2 1 361192273 000333331313137 E 1 0 0 0 0 13 15 -0 0 0 3 3784677326 00033333131313600033335344331313136333345344333333 E 3 0 0 1 3 6 50 -1 1 0 1 3152215146 000333331313137 E 1 0 0 0 0 9 15 -1 2 2 0 1016156086 0003333313131360003333443313131363333444534333333 E 3 0 0 1 3 1 49 -0 2 2 3 3211371560 000333331313137 E 1 0 0 0 0 15 15 +0 1 1 1 3920851190 3333443322000331313137 E 1 1 0 0 1 5 22 +1 2 0 0 2369495184 000333331313137 E 1 0 0 0 0 11 15 +1 2 1 1 2709396084 3333443322000331313137 E 1 1 0 0 1 7 22 +1 0 1 2 2897975112 333343322000331313137 E 1 1 0 0 1 6 21 +0 2 2 1 3540756673 000333331313136000333344331313137 E 2 0 0 0 0 12 33 +0 2 1 1 3904863766 333343322000331313136333344332200034331313137 E 2 1 1 0 3 5 45 +1 0 1 3 1006989001 0003333323131313600033334331313136333344333333 E 3 0 0 1 3 4 46 +1 0 0 3 2707574475 000333331313137 E 1 0 0 0 0 11 15 +1 1 0 3 2949522324 33301343322000331313137 E 1 1 0 0 1 11 23 +1 1 1 0 1948310032 00033333131313600033334331313136333344333333 E 3 0 0 1 3 1 44 +0 1 2 2 954797766 333344332200033131313633334441033220003433231313137 E 2 1 1 0 3 4 51 +1 0 2 2 1732709149 00033333131313600033334331313137 E 2 0 0 0 0 10 32 +1 2 0 0 1701949730 333344534013322000331313136333344332200034433131313633334 D 3 0 0 0 0 0 57 +0 0 2 1 443637562 3333443322000331313137 E 1 1 0 0 1 9 22 +0 0 0 2 889775688 333343322000331313136333344332200034433131323136333344 D 3 0 0 0 0 0 54 +1 2 0 3 992152249 333344332200033131013137 E 1 1 0 0 1 13 24 +0 0 2 0 3434211706 3333443322000331313136333344 D 2 0 0 0 0 0 28 +1 2 0 3 3630299449 33334433220003313131363333445353433220001035353433131313633334534534 D 3 0 0 0 0 0 68 +1 0 2 1 2669929270 000333331313136000333343313131363333444333333 E 3 0 0 1 3 3 45 +1 0 2 3 1312127343 3333443322000331313137 E 1 1 0 0 1 10 22 +1 2 2 2 167328289 3333443322000331313136330133444332200034433131313633334 D 3 0 0 0 0 0 55 +1 2 2 3 3202558252 0001033333131313620003333344331313137 E 2 0 0 0 0 13 37 +0 1 0 2 2756558187 00033323331313137 E 1 0 0 0 0 11 17 +0 2 0 3 3750244444 00033333131313600033334331313317 E 2 0 0 0 0 15 32 +0 1 0 0 2303303092 00033333131313600033334433131313633013344333333 E 3 0 0 1 3 4 47 +1 2 1 3 1721895700 3333442333220003313131363333534534332200035343313131361033335344333333 E 3 1 1 1 6 4 70 +1 2 2 1 1606100535 33334433220003313131363333444332200034433131313633334 D 3 0 0 0 0 0 53 +0 1 1 0 1113868069 33334534332200033131331633334 D 2 0 0 0 0 0 29 +0 0 1 2 1339933280 333343322000331313137 E 1 1 0 0 1 7 21 +1 1 1 2 485587940 33013344332200033131313633335233443322000343313131363333534 D 3 0 0 0 0 0 59 +1 1 1 2 1916101079 000333331313136000333343313131363333444333333 E 3 0 0 1 3 1 45 +1 1 1 2 721442538 333353534332200033231313137 E 1 1 0 0 1 6 27 +1 0 2 0 1571850093 000333331313136000333343313131363333453535344333333 E 3 0 0 1 3 1 51 +0 2 1 3 3567410857 333344332200033131313631033345344332200034331313137 E 2 1 1 0 3 5 51 +1 2 2 0 860857561 0003233333131313600033334433131313633334444333333 E 3 0 0 1 3 4 49 +1 1 0 2 3327653391 3333443322000331313137 E 1 1 0 0 1 9 22 +0 1 1 0 2394731332 333344332200033131313633334 D 2 0 0 0 0 0 27 +0 2 2 2 4098688730 3333453443322000331313137 E 1 1 0 0 1 11 25 +0 2 2 1 1691245999 0003333313131360003333534331313137 E 2 0 0 0 0 13 34 +0 0 0 3 2183682723 000333331313136000332333443313131363333444333333 E 3 0 0 1 3 3 48 +0 2 2 1 1363860226 100133334443322000331313136333345353453433220003534534331313137 E 2 1 1 0 3 1 63 +0 1 0 3 2903622553 3333433220003313131363333444433220003453433131313633334 D 3 0 0 0 0 0 55 +0 1 1 1 4133522361 00033333131313600033334331313137 E 2 0 0 0 0 6 32 +1 0 2 1 3067292385 000333331313136000333343313131363333444333333 E 3 0 0 1 3 1 45 +1 0 0 3 4242313625 0003333313131360003333443331131363333444333333 E 3 0 0 1 3 4 46 +1 2 0 0 1305545131 030033331313137 E 1 0 0 0 0 11 15 +1 0 1 3 2995611840 0003333313131360003333433131313633335344333333 E 3 0 0 1 3 4 46 +1 2 2 3 2197774978 3333443322000331313137 E 1 1 0 0 1 12 22 0 0 2 0 2931690975 3333443322000331313136333344332200034331313137 E 2 1 1 0 3 6 46 0 1 0 2 2852282521 000333331313137 E 1 0 0 0 0 11 15 0 1 0 2 2381494272 333344332200033131313633335353443322000344331313136333344 D 3 0 0 0 0 0 57 -0 1 0 2 2907723155 0003333313131360003333433131313633334444 D 3 0 0 0 0 0 40 +0 1 0 2 2907723155 0003333313131360003333443313131363333444 D 3 0 0 0 0 0 40 1 2 1 2 1465271758 00033333131313600033334331313136333344534333333 E 3 0 0 1 3 2 47 0 0 1 3 787598660 333343322000331313137 E 1 1 0 0 1 8 21 1 1 1 0 3092899853 333343322000331313137 E 1 1 0 0 1 4 21 0 0 0 2 2992737434 3333443322000331313137 E 1 1 0 0 1 10 22 0 1 1 0 473398390 3333534103322000331313137 E 1 1 0 0 1 5 25 -1 1 1 2 2509101715 333343322000331313136333344332200034331313136333344333333 E 3 1 1 1 6 2 57 -0 0 0 2 2795630214 33334443322000331313137 E 1 1 0 0 1 7 23 -0 2 1 3 3533206098 00033333131313600033334331313137 E 2 0 0 0 0 11 32 -1 0 1 3 1460090771 333343322000331313137 E 1 1 0 0 1 7 21 -0 2 2 3 1910707638 003033331313137 E 1 0 0 0 0 15 15 -0 2 0 0 1098650777 00033233331313137 E 1 0 0 0 0 12 17 -1 2 2 3 4046320493 0003333313131360003333410331313137 E 2 0 0 0 0 14 34 -1 2 1 1 2080023278 33334433220002103331313136333344332200034331313136333344 D 3 0 0 0 0 0 56 -0 1 2 1 4268602231 33335345353535343322000331313136333345353534 D 2 0 0 0 0 0 44 -1 2 0 3 1130807697 3333443322000331313136333353445343322000345353534331313136333344 D 3 0 0 0 0 0 64 -1 1 0 3 3006341891 00033333131313600033334433131313633334444 D 3 0 0 0 0 0 41 -1 2 2 1 1812516596 103333443322010003310131313601333344433220003433131313633334 D 3 0 0 0 0 0 60 -0 1 1 3 2231586941 00033333131313600033334433131313633334534333333 E 3 0 0 1 3 3 47 -0 2 2 3 3165072068 33334443322000331313136333353453445343322001003443313131363333534 D 3 0 0 0 0 0 65 -0 2 1 3 485301097 3333443322010003313131363333534433220003433131313633335344534333333 E 3 1 1 1 6 1 67 -1 2 0 1 590914243 33334433220001033131313633334433220003535344331313136333344 D 3 0 0 0 0 0 59 -1 1 1 2 235709974 3333443322000331313137 E 1 1 0 0 1 3 22 -0 0 0 3 4167334254 3333443322000331013131360110333353453444332200035345343313131363103334 D 3 0 0 0 0 0 70 -1 0 0 3 1370745481 333343322001001033131313633334440123332200034331313137 E 2 1 1 0 3 4 54 -1 1 2 0 542187043 333343322000331313137 E 1 1 0 0 1 8 21 -1 0 2 1 3027394317 333353443322000331313137 E 1 1 0 0 1 8 24 -0 0 0 0 611044482 000333331313137 E 1 0 0 0 0 9 15 -0 2 2 1 258189837 00010333331313136000333353433131313633334445344333333 E 3 0 0 1 3 1 53 -0 1 2 2 2777518087 33334332220003331313137 E 1 1 0 0 1 11 23 -1 1 0 1 1903556689 33334443322000331313137 E 1 1 0 0 1 4 23 -0 1 1 1 1235383131 00033310331313137 E 1 0 0 0 0 6 17 -1 0 1 1 1702663158 000333331313137 E 1 0 0 0 0 5 15 -0 1 1 0 2067385791 2333334332200033131313633335344332200034331313137 E 2 1 1 0 3 1 49 -0 1 1 2 2141619744 3333433220003313131363333453433222000334331313136333344333333 E 3 1 1 1 6 1 61 -1 1 0 2 3165003939 00032333331313137 E 1 0 0 0 0 10 17 -1 2 1 2 4062140859 333343322000331313137 E 1 1 0 0 1 9 21 -1 2 1 3 2467949765 000333331313137 E 1 0 0 0 0 10 15 -0 2 1 2 2621166288 0003333313131360003333433131013136333344433330133 E 3 0 0 1 3 4 49 -0 0 0 2 1867857950 33334433220003313131363333534433220003453510353534331313137 E 2 1 1 0 3 5 59 -1 2 2 2 3569545611 000333331313136000333343313131363333444534333333 E 3 0 0 1 3 4 48 -0 1 1 0 2255526388 000333331313137 E 1 0 0 0 0 5 15 -1 0 2 2 1472047360 33334433220003313131363333444332200034331313137 E 2 1 1 0 3 2 47 -0 0 1 0 730063387 00033332331313137 E 1 0 0 0 0 5 17 -1 2 2 2 3345924542 000333331313137 E 1 0 0 0 0 13 15 -0 1 1 1 1123783681 000333331331137 E 1 0 0 0 0 6 15 -0 0 2 1 2720416800 33334433220003313131363331034453433220003433131313633334 D 3 0 0 0 0 0 56 -0 0 1 3 3178749919 00033333131311037 E 1 0 0 0 0 8 17 -1 1 1 2 1732078937 33335345343322000331313136333344 D 2 0 0 0 0 0 32 -1 2 1 3 4008268553 333353443322000331313137 E 1 1 0 0 1 9 24 -1 0 0 2 2004889121 33334433220003313131363333443322000344331313136333344 D 3 0 0 0 0 0 53 -0 2 0 2 657505011 00033333131313600033334331313136333344444333333 E 3 0 0 1 3 1 47 -0 0 0 1 103804227 10000333331313137 E 1 0 0 0 0 10 17 -1 0 2 1 1221425010 3333443322000331313137 E 1 1 0 0 1 7 22 -1 2 0 0 1851460147 333353443322000331313136333344332200034331313136333344 D 3 0 0 0 0 0 54 -0 0 0 3 2980484362 000333331313137 E 1 0 0 0 0 12 15 -1 1 1 2 1113891852 000333331313136000333353534331313136333344333333 E 3 0 0 1 3 4 48 -1 1 0 0 479622470 333343322000331313136333344332200034331313137 E 2 1 1 0 3 4 45 -0 2 0 1 3263901231 000333331313137 E 1 0 0 0 0 13 15 -0 1 0 2 2967363996 33334332200033131313633334443322000344333113136333344 D 3 0 0 0 0 0 53 -1 1 1 0 1678976153 333343322000331313137 E 1 1 0 0 1 4 21 -1 2 2 0 2498835864 33334433220003313131363333444332200034433131313633334 D 3 0 0 0 0 0 53 -0 0 1 1 3406174499 00033333131313600033334331313136333344 D 3 0 0 0 0 0 38 -0 1 0 1 4106692889 00033333130113137 E 1 0 0 0 0 10 17 -0 2 2 3 1065286500 000333331313137 E 1 0 0 0 0 15 15 -1 1 2 0 3911754101 3333443322000331313137 E 1 1 0 0 1 7 22 -1 0 2 1 3567252782 000333331313136000333343313131363333444333333 E 3 0 0 1 3 2 45 -0 2 0 3 3641371741 0003333313131360003333453534331313136333344444 D 3 0 0 0 0 0 46 -1 0 0 1 4088224553 00033333131313600033335343313311363333444333333 E 3 0 0 1 3 3 47 -0 2 2 0 3228524548 2333334443322000331313137 E 1 1 0 0 1 6 25 -0 0 0 3 1265674468 333344332210000331313137 E 1 1 0 0 1 9 24 -0 1 1 2 4040763488 000333331331137 E 1 0 0 0 0 7 15 -0 2 2 1 3669230234 3333534534433220003313131363333445353433220003443313131363323334 D 3 0 0 0 0 0 64 -1 1 0 1 4140947347 333344332200033131323137 E 1 1 0 0 1 7 24 -0 1 0 1 2980599011 33334443322000331313137 E 1 1 0 0 1 6 23 -0 0 1 1 4140812687 00033333131313600033334331313136333344 D 3 0 0 0 0 0 38 -1 0 1 0 3662083636 33334332200033131313633334 D 2 0 0 0 0 0 26 -0 0 1 1 3600532337 000333331313137 E 1 0 0 0 0 6 15 -0 1 0 2 3506060951 3333443322000331313137 E 1 1 0 0 1 10 22 -0 0 1 3 2731234215 333343322000331313136333345343322000343313131363301013344333333 E 3 1 1 1 6 3 63 -0 1 2 0 611541544 000333331313136000333344331313137 E 2 0 0 0 0 8 33 -1 2 2 2 4251267351 333345343322000331313136330132334443322000353433131313633334534 D 3 0 0 0 0 0 63 -0 0 1 2 2502681551 33335353443322000331313137 E 1 1 0 0 1 4 26 -0 0 0 0 1917499382 233333443322000331313136330133443322000344331313137 E 2 1 1 0 3 5 51 -0 0 0 0 712959242 33334433220003313131363333443233220003433131313633334 D 3 0 0 0 0 0 53 -1 2 0 3 11751792 333344534332200033131313633323344433220003433131301137 E 2 1 1 0 3 3 54 -1 0 0 3 1125454224 3333443322000331313136333344332200034331313136333344 D 3 0 0 0 0 0 52 -0 0 1 2 562035643 33335344332200033131013137 E 1 1 0 0 1 5 26 -1 2 2 0 2566361801 000333331313136000333344331313136333013445344333333 E 3 0 0 1 3 1 51 -1 0 1 1 757006194 0003333313131360003333433131313623333344333333 E 3 0 0 1 3 1 46 -0 1 2 1 78292097 3333443322000331313137 E 1 1 0 0 1 9 22 -1 1 0 2 783408819 0003333313131360003333534331313136333344333333 E 3 0 0 1 3 6 46 -0 0 2 3 4241182308 3333443322000331313136333344433220003433131331633334 D 3 0 0 0 0 0 52 -1 0 0 3 2313814573 333344332200033131313633334443322000233433231313136333344 D 3 0 0 0 0 0 57 -0 1 0 2 3339828456 000333331313136000333343313311363333444333333 E 3 0 0 1 3 3 45 +1 1 1 2 2509101715 333343322000331313136333344332200034331313136333344 D 3 0 0 0 0 0 51 +0 0 2 2 1693328865 33334433220003313131363333444332200034433131313633334 D 3 0 0 0 0 0 53 +0 2 2 3 2194104172 000333331313137 E 1 0 0 0 0 15 15 +0 2 1 2 4216201758 000333331313136000333345343313131363333444333333 E 3 0 0 1 3 2 48 +0 1 0 1 2396338135 000333331313137 E 1 0 0 0 0 10 15 +1 1 0 0 393394502 3333433223210000323313131363333444332200021033433311313633334 D 3 0 0 0 0 0 61 +0 0 1 3 2668975499 33233343322010003331131360133334433220003433131313633323344 D 3 0 0 0 0 0 59 +1 2 1 0 3010582772 00033310331313137 E 1 0 0 0 0 7 17 +0 1 1 0 567011646 3333433220003313131363332334433220003433131313633334 D 3 0 0 0 0 0 52 +1 2 2 3 2554659322 3333443322000331313137 E 1 1 0 0 1 13 22 +0 0 1 0 640545973 00033333131313600033334331313136333344 D 3 0 0 0 0 0 38 +1 1 1 0 428480326 000333331313137 E 1 0 0 0 0 4 15 +0 0 1 1 3750668862 00033333131313600033334331313136333353444333333 E 3 0 0 1 3 1 47 +0 1 0 1 3888008871 00033333231313136000333343313131363333444333333 E 3 0 0 1 3 4 47 +1 2 2 0 364653106 33334433220003313131363333444332200034331313137 E 2 1 1 0 3 4 47 +0 0 2 2 1667763349 000333331313137 E 1 0 0 0 0 11 15 +0 2 2 3 112406696 3333443322000331313136333353445343322000343313131363333444 D 3 0 0 0 0 0 58 +1 0 2 1 1774029524 00033333131313600033334331313136333345344333333 E 3 0 0 1 3 3 47 +1 2 0 2 3518378751 000333331313137 E 1 0 0 0 0 13 15 +1 2 1 1 711996855 333345343322000331313137 E 1 1 0 0 1 6 24 +0 2 2 2 1165454293 333344332200033131313633334444332200034433131313633334 D 3 0 0 0 0 0 54 +0 1 0 0 2939272575 3333443322000331313110363333443322000352334433131313633334 D 3 0 0 0 0 0 58 +1 0 0 0 473578925 233333443322000331313137 E 1 1 0 0 1 6 24 +1 0 1 0 277388831 333343322000331313110363333443322000353433131313633334 D 3 0 0 0 0 0 54 +0 2 0 3 1513219607 333345343322000331313136333344433220003443313131363333444333333 E 3 1 1 1 6 1 63 +1 1 2 2 1049826673 3333443322000331313137 E 1 1 0 0 1 9 22 +1 2 0 2 856658046 00033333131313600033334331313136333344444333333 E 3 0 0 1 3 4 47 +0 2 2 3 1267761915 33334443322000331313136323333444433220003433131313633334 D 3 0 0 0 0 0 56 +0 2 0 2 2188493433 333344332200033131313633334444 D 2 0 0 0 0 0 30 +1 1 2 1 1080738185 33334534332200033131313633334443322020033433131310137 E 2 1 1 0 3 1 53 +1 1 0 0 113335942 000333331313137 E 1 0 0 0 0 8 15 +0 2 0 3 4202932265 33334443322000331313137 E 1 1 0 0 1 12 23 +1 1 0 3 3288821619 33334534332200033131313633334443322000344331313137 E 2 1 1 0 3 2 50 +0 0 1 0 3527884552 10333343322000331310131363301334433220003433131313633334 D 3 0 0 0 0 0 56 +0 1 1 1 1031284531 00033333131313600033334331313136333344333333 E 3 0 0 1 3 4 44 +1 2 0 0 287728801 00033333131313600100333344331313136333344423333333 E 3 0 0 1 3 3 50 +0 2 0 1 1929408853 3331034433220002323331313137 E 1 1 0 0 1 11 28 +1 2 0 0 1770310926 333344332200033131313633335345344332200034433131313633334 D 3 0 0 0 0 0 57 +0 1 0 3 1837082208 0003333313131360003333433131313633334444333333 E 3 0 0 1 3 4 46 +0 0 2 2 913814328 000333331313137 E 1 0 0 0 0 11 15 +1 2 0 2 2347910364 000333331313136300033344331313137 E 2 0 0 0 0 12 33 +1 0 2 0 886876986 00033333131313600033334331313136333353444333333 E 3 0 0 1 3 3 47 +1 2 0 2 2152700340 0003333313131360003333433131313633334444333333 E 3 0 0 1 3 3 46 +1 0 0 1 1920685547 33334433220003313131363333534535344332200034433131313633334 D 3 0 0 0 0 0 59 +0 1 1 0 3097652862 00033333131313600033334331313136333344 D 3 0 0 0 0 0 38 +0 1 2 1 98724712 333344332200033131313633334433220003443313131363333453534 D 3 0 0 0 0 0 57 +1 1 0 0 348145838 3333443322000331313136333344332200034331313136333344 D 3 0 0 0 0 0 52 +0 1 1 0 4103385462 000333331313137 E 1 0 0 0 0 5 15 +0 1 1 1 3765610711 333343322000331313137 E 1 1 0 0 1 6 21 +0 2 1 2 3620644614 0003333313131360003333443313131363333444333333 E 3 0 0 1 3 3 46 +1 1 2 0 248935034 30133344332200203331313137 E 1 1 0 0 1 7 26 +0 0 2 3 3488579851 000333331313137 E 1 0 0 0 0 12 15 +0 0 2 0 1337840185 333353534433220003313131363333443322000353443313131363333534 D 3 0 0 0 0 0 60 +0 0 1 0 2172587401 3333433220003313131363333443322000343313131363013334 D 3 0 0 0 0 0 52 +1 1 2 3 3404332027 3333443322000331313137 E 1 1 0 0 1 10 22 +1 2 1 1 2625381496 33334332200033131313633334453433220003433131313633334 D 3 0 0 0 0 0 53 +1 2 2 1 2525184687 33334433220003313131363333444332200034433131313633334 D 3 0 0 0 0 0 53 +0 0 1 0 229800327 33335345343322000331313136333345353433220003534331313137 E 2 1 1 0 3 2 56 +0 1 0 0 1225796239 33335343322000331313136333344332200034331313137 E 2 1 1 0 3 6 47 +0 0 2 3 3688377949 00033333131313630003334331313137 E 2 0 0 0 0 12 32 +1 0 0 1 3084775414 000333331313137 E 1 0 0 0 0 9 15 +1 1 1 2 27023980 333343322000331313137 E 1 1 0 0 1 6 21 +1 0 2 1 1415065248 0003233333131313600033334331313136333345344333333 E 3 0 0 1 3 1 49 +0 1 2 0 2827947999 333344332200230331313136333344332200034333113137 E 2 1 1 0 3 3 48 +1 0 0 2 4178099068 333344332200033131313633334433220003443313131363333444333333 E 3 1 1 1 6 1 60 +1 0 2 0 2657724202 000333331313137 E 1 0 0 0 0 8 15 +1 2 1 3 240703872 3333534332200033131313633334433220003433131313633010133534534333333 E 3 1 1 1 6 4 67 +0 2 0 0 1451500792 3333534433220003313131363333444 D 2 0 0 0 0 0 31 +0 2 2 2 974495578 3333453433220003313131363333444332200034331313136333344 D 3 0 0 0 0 0 55 +1 0 0 1 3191004127 333344332200033131313633334443322000343313131363103334 D 3 0 0 0 0 0 54 +0 1 2 2 1031906187 333344332200033131013137 E 1 1 0 0 1 9 24 +0 0 2 0 3271938017 000333331313136000333343313131363333444333333 E 3 0 0 1 3 4 45 +0 2 0 1 223115297 33334433220003313131363103335344535344332200034331313137 E 2 1 1 0 3 3 56 +0 2 2 2 2591398950 00033333131313600033334433131313633335353444333333 E 3 0 0 1 3 8 50 +0 1 1 1 2475557066 00033333131313600033335344331313136333344 D 3 0 0 0 0 0 41 +0 0 2 1 3380639515 233333443322000331313137 E 1 1 0 0 1 8 24 +0 2 1 3 4238733714 333353433220003313131363333444332200034331313137 E 2 1 1 0 3 3 48 +1 0 0 1 1306597344 000333331313136000333343313131363333444333333 E 3 0 0 1 3 5 45 +0 2 1 1 2218215961 000333331313136000333343313131363333444333333 E 3 0 0 1 3 4 45 +0 2 0 3 1862941455 333344433220003313131363333444332200034534331313137 E 2 1 1 0 3 5 51 +1 0 1 2 3739086903 333343322000331313137 E 1 1 0 0 1 6 21 +0 0 2 2 3063617785 000333331313136000333353433131313633334453433233333 E 3 0 0 1 3 4 51 +1 0 0 2 986874386 333353453534323322000331313137 E 1 1 0 0 1 8 30 +1 0 0 0 303337798 3333443322000331313137 E 1 1 0 0 1 7 22 0 0 1 0 95648142 33334534332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 53 0 0 1 2 3302592867 00033333131313600033334331313136333344 D 3 0 0 0 0 0 38 1 2 1 1 136623311 332333443322000331313136333344 D 2 0 0 0 0 0 30 -1 2 1 3 1062333302 3333443322000331313137 E 1 1 0 0 1 8 22 +1 2 1 3 1062333302 3333443322000331313137 E 1 1 0 0 1 9 22 0 0 2 1 1160888014 00033333131313600033323344331331137 E 2 0 0 0 0 9 35 0 0 1 2 1273245635 000333331313137 E 1 0 0 0 0 7 15 1 1 1 2 517988369 00033333131323137 E 1 0 0 0 0 6 17 0 2 2 1 4224030840 333344332201000331313136333344433220003443313131363333534534 D 3 0 0 0 0 0 60 -1 2 2 2 3745156010 3333443322000331313136333344534332200034433131313633334 D 3 0 0 0 0 0 55 -0 2 0 2 4222309887 3333534445343322000331313137 E 1 1 0 0 1 7 28 -0 2 1 3 403254782 000333331313136000333343313131363333444333333 E 3 0 0 1 3 6 45 -0 0 1 1 279268808 33335353433222000333131313633334534332200034331313137 E 2 1 1 0 3 2 53 -1 1 1 3 1620146695 3333433220003313131363333433220003534331313136333344333333 E 3 1 1 1 6 5 58 -0 1 0 0 1269369233 00033333131313600033335343313131363333534535353444333333 E 3 0 0 1 3 1 56 -0 2 2 0 1985823488 00033333131313600033334433131313633334444333333 E 3 0 0 1 3 2 47 -0 1 1 2 993889584 3333433220003313131363333443322000344331313137 E 2 1 1 0 3 3 46 -1 1 0 1 153699983 31033344332322000331313136333344332200034331313136333345013534 D 3 0 0 0 0 0 62 -1 1 2 1 2498181886 2030033333131313600033334331313136333344333333 E 3 0 0 1 3 6 46 -0 1 2 1 1936743281 3333443322000331313136333345344013322000342333131313633334 D 3 0 0 0 0 0 58 -1 1 0 3 3663183637 3333535343322000331313137 E 1 1 0 0 1 11 25 -0 0 0 1 2480583994 333353443322000331313137 E 1 1 0 0 1 7 24 -0 2 2 1 4212011074 0003333313131360003333535344331313136330133534534534534333333 E 3 0 0 1 3 1 61 -0 0 2 3 566990472 3333443322000331313137 E 1 1 0 0 1 10 22 -1 2 2 0 511782980 33334433220003313131363333444332200035344331313137 E 2 1 1 0 3 2 50 -1 0 2 3 1989141203 00033333103113137 E 1 0 0 0 0 11 17 -1 2 2 2 4207308921 333344332200033131313633335353453534534332200034433131313633334534 D 3 0 0 0 0 0 66 -0 2 0 2 1815762070 33334444332200033131313633335344433220003534 D 2 0 0 0 0 0 44 -0 0 0 2 2846535959 3333535352334433220003313131363333443322000353410331313137 E 2 1 1 0 3 7 58 +1 2 2 2 3745156010 33334433220003313131363333445343322000344331313136333344 D 3 0 0 0 0 0 56 +0 2 2 1 635076871 33334433220003313131363333444332200034331313137 E 2 1 1 0 3 5 47 +1 1 1 2 4126427875 33335343322000331313136333353535353535344332200035343313131363333453534 D 3 0 0 0 0 0 71 +1 2 2 3 478818824 333344332200033131313633334443322000344331313137 E 2 1 1 0 3 5 48 +1 2 1 3 2364608949 3333443322000331313136333344332200034331313136333344 D 3 0 0 0 0 0 52 +1 0 0 2 470718961 0003333313131360003323334331313137 E 2 0 0 0 0 10 34 +1 0 0 2 59643549 000333331313136000333343313131363333444333333 E 3 0 0 1 3 4 45 +1 0 0 3 2144986461 000333331313137 E 1 0 0 0 0 11 15 +1 2 2 3 484975869 33334433220003313131363310334443322000344331313136333344 D 3 0 0 0 0 0 56 +1 2 2 1 1775444899 00033333131313600033334534233313131363333445344333333 E 3 0 0 1 3 3 53 +1 1 0 0 1704839854 3333535345343322000331313136333344 D 2 0 0 0 0 0 34 +0 2 1 1 598815373 33203213334432332200100331313137 E 1 1 0 0 1 7 32 +0 0 2 3 214133279 0003333313131360003333433131313633334444333333 E 3 0 0 1 3 3 46 +1 2 0 1 2562900223 3333444332200033131313633100133453443322000343313011313633334 D 3 0 0 0 0 0 61 +0 2 1 0 1175250967 3333443322000331313136333345343322000344331331137 E 2 1 1 0 3 1 49 +1 0 2 3 1213087270 00010333331313136000333353534331313137 E 2 0 0 0 0 11 38 +1 0 1 0 466979253 000333331313137 E 1 0 0 0 0 4 15 +1 1 2 2 4213429823 0003333313131360002333334331313136333344333333 E 3 0 0 1 3 6 46 +1 2 2 3 3004707721 333344332232000331313137 E 1 1 0 0 1 12 24 +0 0 0 3 1072812929 00033333131313600033334433131313633335344534333333 E 3 0 0 1 3 5 50 +1 0 1 2 323001430 333343322000331313137 E 1 1 0 0 1 6 21 +1 1 1 2 2305152456 000333331313137 E 1 0 0 0 0 6 15 +0 0 2 3 2691808976 0003333331131360003333443313131363333444333333 E 3 0 0 1 3 5 46 +1 0 1 1 580383386 33334534332200033131313633334433220003433131313633334 D 3 0 0 0 0 0 53 +0 2 2 2 3295903195 00033333131313600033334331313137 E 2 0 0 0 0 14 32 diff --git a/docs/architecture/onchain-boundary.md b/docs/architecture/onchain-boundary.md index 64d6f7b..e5c0830 100644 --- a/docs/architecture/onchain-boundary.md +++ b/docs/architecture/onchain-boundary.md @@ -26,7 +26,8 @@ Contract: `contracts/deepmark-contract`. - **Leaderboard.** One sorted list of the best 100 verified runs, read in a single call, ranked by extraction, score, floor, HP left, then fewest actions. The number of players is not capped, and players are listed through a paged call. - **Audit.** `verify_run(hero, seed, actions)` replays any run without writing anything, so anyone can check a result from the `run_started` event and the log. - **Delvers.** The contract is also the registry of earned delvers: `owner_of`, `heroes_of`, `hero`, `balance` and `transfer`. Mint happens only inside `finish_run`, after the replay. -- **Relic assets.** Three classic assets (`DMCOMMON`, `DMRARE`, `DMCORE`), each wrapped by a Stellar Asset Contract. The game contract is the admin of each, and the issuer account is locked, so nothing can mint except a verified extraction. +- **Relic assets.** Three classic assets (`DMCOMMON`, `DMRARE`, `DMCORE`), each wrapped by a Stellar Asset Contract. The game contract is the admin of each, and the issuer account is locked (master weight zero, no other signer), so nothing can mint except a verified extraction. +- **Admin and upgrade.** The game contract has an admin (the deployer) that can replace its code, hand the role to another address or renounce it. While the role exists, players trust that admin not to ship code that skips the replay. Renouncing, or moving the role to a multisig, is the step that removes that trust, and is planned before any mainnet release. ## Off-chain (client) @@ -42,6 +43,7 @@ Contract: `contracts/deepmark-contract`. - Nobody can act for an address that did not sign. - The client cannot choose a favourable seed. - Relics exist only because a verified run banked them, and anyone can see the supply. +- Upgrades are visible: every code swap, handover and renounce emits an event, and `admin()` reports `None` once the contract is frozen. ## What it does not provide diff --git a/docs/deployments.md b/docs/deployments.md new file mode 100644 index 0000000..34753c7 --- /dev/null +++ b/docs/deployments.md @@ -0,0 +1,69 @@ +# Testnet deployments + +Every id and transaction below is on Stellar testnet and can be looked up on [stellar.expert](https://stellar.expert/explorer/testnet) or in Horizon. Times are UTC. + +## Current deployment, 2026-10-12 + +| Item | Id | +|---|---| +| Game contract | `CDEAFSBG7JBN6XMP4HRL7LFBL7L67K4RJK3CIRBV23W3TJQCEADZ7R6N` | +| `DMCOMMON` contract | `CAAFYL2QZU274NLA2IFDB4L6SUNVQKT6OLB6UXGL5L5LMQCFJXSUYFO3` | +| `DMRARE` contract | `CC4RQ4HLTPTXMCZ3OKDL546XXJGOQ5PI2ET6U33C2XTO4BTS6AJV4ANA` | +| `DMCORE` contract | `CC27JVKNDSUM5FDGP4JZDZTKGHJ5RJV7XHJGITTUGOUFE4DVPN6PGLMI` | +| Relic issuer | `GAD752QFRTNEMQW2SELGHQ5VAMIV3O7H4XRLJN2K2TQFGGXKNFG7CQUK` | +| Game admin and deployer | `GCZCKB4FUIS5BGEMJOC3EQWKLGH662BOY3HFLH3WWPCGQJXOILDXP7GZ` | +| Wasm sha256 | `256f445c6e3e31708f7a52257a8145afffc0cd9b12abf9f2657cfe5379fbcf2c` | + +The wasm hash can be checked against the chain with `stellar contract fetch --id --network testnet` followed by `shasum -a 256` on the output. + +### Setup transactions + +In order. The first column is the ledger close time of the transaction. + +| Time | Step | Transaction | +|---|---|---| +| 03:53:57 | Create the issuer account | `705b19f25b8325ab390cedbce7a5a7687f10bd1f83729d2d52dc23cddebaf5ea` | +| 03:54:02 | Create the deployer account | `340f96f54028f3909e81a37223005afcffc76cf0e5865ce2298abf2e69439147` | +| 03:54:07 | Deploy the Stellar Asset Contract of the first relic asset | `d39f4d9eff766c03d93d474d6f96b181e8cd2d02723587542b2b330d34819d31` | +| 03:54:12 | Deploy the Stellar Asset Contract of the second relic asset | `1351cee92dd107ecb0d534239e5ec7becae90b8e31638b8ed6ce1de30b8a80e4` | +| 03:54:17 | Deploy the Stellar Asset Contract of the third relic asset | `ae5e077d040f4b5dba254c8decb6e22dcd17c1cd2f5329c2464eea825184e2f5` | +| 03:54:22 | Upload the game wasm | `fa6a32a4cbfbc5ed2dbbf272f3717d5f56efae0375e4e7d9532beea196dfea04` | +| 03:54:27 | Deploy the game contract, with the admin as constructor argument | `737080c6b5c25fcc2f5726bf8327ca3a73dcec06c40ee3dc2b42f52224d935c2` | +| 03:54:32 | Set the game contract as admin of the first relic contract | `9d000b0a1b89168a9fc531a69c48d5e9f39e1b3ec98da79c5f9f03b951c4f9c6` | +| 03:54:37 | Set the game contract as admin of the second relic contract | `0722fdcbb03d87d21f85f3fa2af4bc9c6071b5d275ce572cc8c6b3dc9039fb23` | +| 03:54:42 | Set the game contract as admin of the third relic contract | `9e606376fd11b1394aea1e035e73462fd7712772a3bd4fc0a19c2cdc9406fcec` | +| 03:54:47 | Lock the issuer: master key weight set to 0 | `792dd0919d33938e0603b3ea3a4e6bac4a40de61f8680701dbbf25746447fc40` | + +The issuer was checked on Horizon after the lock: one signer with weight 0 and thresholds at 0. A transaction signed with the issuer's own key was rejected with `TxBadAuth`. The admin of each relic contract was read back and is the game contract. + +### End to end run on the new deployment + +`client/scripts/chain-smoke.ts` with a fresh account, three runs. All three transactions are `successful`. + +| Ledger | What | Transaction | +|---|---|---| +| 5150756 | `finish_run` with a defeat | `602d5a167cadfd23e81857282429f8ae41d85522a367613c9888edf6e6da89c0` | +| 5150764 | `finish_run` with a full clear, which earns delver 0 and mints relics | `28b5a324723890ff494860b0661a6083f5dcdf7f9f6bef2f1fbee8928fadf850` | +| 5150771 | `finish_run` with a full clear by the veteran delver, now at 2 clears | `a73fa14a5c92ef3fcc4fece8a7803322ef2170672cfcea6f0de9982d775bed2f` | + +## Upgrade and renounce experiment + +The admin paths were exercised on a separate, disposable contract so the game contract keeps its admin and its state. It was deployed from the same deployer, upgraded to a build that reports version 2 with the open run intact, and then frozen with `renounce_admin`. After that `admin()` returned nothing and `upgrade` failed with `Frozen`. + +| Item | Id | +|---|---| +| Disposable contract (frozen) | `CDKXM55BE7NG5DTFKJWB7XZTCTAGUHUH6JQSCE6OO5IRF55N45AJTA6S` | + +| Time | Step | Transaction | +|---|---|---| +| 03:56:17 | Deploy the disposable contract | `6c037979bae62269c8d6af763036cca601402028c70b332afd1cd89399b91ed8` | +| 03:56:27 | Start a run on it | `31eccf8a271dc353d8569b55b7834481c67c2bf285ad1c4086b010e475c553f4` | +| 03:56:32 | Upload the version 2 wasm | `51de58877faafcdf4cc994c9a60b5c570ae492f57a7c6c21ae38ec0081a448b0` | +| 03:56:37 | `upgrade` to the version 2 wasm | `b452ac33f126ed2ba3e4d91ce775ad7782319b21db0effd7a659a9ef6d18a5b3` | +| 03:56:52 | `renounce_admin` | `7f7e7eee7d640c53ff453a363d9195fadae62e11a88906a25b7165b1eb3e1cd5` | + +The game contract itself has not been upgraded or renounced. Its admin is still a single key, see `docs/threat-model.md`. + +## Previous deployment + +The first testnet deployment (contract `CDL7OO7MGCNMHZBF5QPJCMBLLY3ORR44TJ54ON44M25YUX6UZ7L46UZW`) is retired. Its relic issuer was never locked and the contract had no admin, so it could not be upgraded. The client no longer reads it, and nothing earned there migrates.