Deepgrain Foundry: move from a claude.ai artifact to a local repository

The game as of artifact version 24, ready for local development with
coding agents.

- Source split from three large files into 52 small ones under src/sim,
  src/render and src/ui, joined in the order in src/manifest.json by
  build.py. The joined code is unchanged apart from comments; every
  headless suite gives the same output, and screenshots in both looks
  are pixel-identical to before.
- Light tidying with no change in behaviour: one movement scan loop,
  shared hazard-stripe and belt shader helpers, unused code removed, a
  shared check helper for the test suites.
- World files can be exported as an ordinary download outside claude.ai.
- Tests: broken old scripts removed, experiments moved to tools/,
  test/run_all.py added. Tools: shot.py (repeatable screenshots),
  where.py, map.py.
- Docs: AGENTS.md, CLAUDE.md, NOTES.md (the owner's to-do inbox),
  docs/PROJECT_MAP.md, DESIGN.md, TESTING.md, systems notes, the
  owner's requests and an agent log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K9kLmYADKSEjuwUN3rFxDA
This commit is contained in:
2026-10-23 16:05:31 -04:00
co-authored by Claude Opus 5.5
commit 3f971a94c2
100 changed files with 11928 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
// Starting, loading and quitting a game; modal dialogs; the pause menu.
// ---------- Start / stop ----------
function showLoading(on, p) {
$('loading').hidden = !on;
if (on) { $('loadbar').style.width = Math.round((p || 0) * 100) + '%'; $('loadtext').textContent = Math.round((p || 0) * 100) + '%'; }
}
function enterGame() {
$('title').hidden = true; $('hud').hidden = false;
GAME.running = true; GAME.paused = false;
buildSky(); resizeRender(); snapCamera();
GAME.lastAutosave = performance.now();
GAME.drag = null; GAME.lmb = GAME.rmb = false; lastBagKey = '';
hudDirty = true; renderHotbar();
}
function startNewWorld(seed, w, h) {
auInit();
showLoading(true, 0);
setTimeout(() => {
generateWorld(seed, w, h, null);
GAME.playTime = 0; GAME.clip = null; GAME.paste = false; GAME.selDrag = null; GAME.tool = 0; GAME.dropMat = 0; GAME.matChosen = false; GAME.wallMat = RSTONE; GAME.filterCfg = { m: [], inv: 0 }; GAME.pieceIdx = 0;
showLoading(false);
enterGame();
showCard({ kind: 'Welcome', title: 'The dig site', body: ['Fly with WASD. The Drill (1) breaks rock loose. The Vacuum (2) sucks up what you click on and blows it back out with right click.', 'You owe $20,000 on this claim. Sell what you dig and make at the trade dock next to you: anything that drops through its grate is paid for on the spot, and buyers post orders that pay far more. Every tip lives in the notebook (N).'], hold: 1.8 });
saveWorld('auto', true);
}, 30);
}
function startFromSave(sv) {
auInit();
try { deserializeWorld(sv.meta, sv.buffers); }
catch (e) {
toast(e && e.message ? e.message : 'That save could not be loaded.', { big: true, title: 'Save not loaded' });
GAME.running = false; $('hud').hidden = true; $('title').hidden = false; if (!TITLE.raf) drawTitle();
return false;
}
GAME.playTime = sv.playTime || 0;
GAME.drag = null; GAME.matChosen = false; GAME.dropMat = 0;
enterGame();
return true;
}
async function quitToTitle() {
await saveWorld('main', true);
GAME.running = false; closeAllModals();
$('hud').hidden = true; $('title').hidden = false;
refreshContinue();
if (!TITLE.raf) drawTitle();
}
async function refreshContinue() {
const sv = await newestSave();
const b = $('btn-continue');
if (sv && sv.meta && sv.meta.v === SAVE_VERSION) {
b.hidden = false;
const d = new Date(sv.savedAt);
$('continue-meta').textContent = `seed ${sv.meta.seed} · ${d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })} ${d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })}`;
} else b.hidden = true;
}
// ---------- Modals ----------
function anyModal() { return ['m-pause', 'm-settings', 'm-notebook', 'm-dialog'].some(id => !$(id).hidden); }
function openModal(id) { $(id).hidden = false; GAME.paused = true; setTimeout(() => { const f = $(id).querySelector('button, select, input'); if (f && !GAME.usingPadMenu) { /* keep focus neutral for mouse users */ } }, 0); }
function closeModal(id) { $(id).hidden = true; if (id === 'm-dialog') GAME.editRun = null; if (!anyModal()) GAME.paused = false; }
function closeAllModals() { ['m-pause', 'm-settings', 'm-notebook', 'm-dialog'].forEach(id => $(id).hidden = true); GAME.paused = false; }
function topModal() { for (const id of ['m-dialog', 'm-settings', 'm-notebook', 'm-pause']) if (!$(id).hidden) return id; return null; }
function dialog(title, bodyHtml, buttons) {
$('dlg-title').textContent = title; $('dlg-body').innerHTML = bodyHtml;
const foot = $('dlg-foot'); foot.innerHTML = '';
for (const b of buttons) {
const btn = el('button', { class: 'btn ' + (b.cls || '') }, esc(b.label));
btn.onclick = () => { playSfx('ui'); const keep = b.fn && b.fn(); if (!keep) closeModal('m-dialog'); };
foot.appendChild(btn);
}
openModal('m-dialog');
}
document.querySelectorAll('[data-close]').forEach(b => b.addEventListener('click', () => { playSfx('ui'); closeModal(b.closest('.modal').id); }));
// pause menu
$('pm-resume').onclick = () => { playSfx('ui'); closeModal('m-pause'); };
$('pm-notebook').onclick = () => { playSfx('ui'); closeModal('m-pause'); openNotebook(); };
$('pm-settings').onclick = () => { playSfx('ui'); openSettings(); };
$('pm-save').onclick = () => { playSfx('ui'); saveWorld('main'); };
$('pm-export').onclick = () => { playSfx('ui'); exportWorld(); };
$('pm-import').onclick = () => { playSfx('ui'); $('importfile').click(); };
$('importfile').onchange = e => { const f = e.target.files[0]; if (f) { closeAllModals(); importWorldFile(f); } e.target.value = ''; };
$('pm-title').onclick = () => { playSfx('ui'); quitToTitle(); };
function openPause() {
$('pause-meta').textContent = `seed ${worldSeed} · ${W}×${H}`;
openModal('m-pause');
}