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:
@@ -0,0 +1,76 @@
|
||||
// Keyboard and mouse listeners.
|
||||
// ---------- Input ----------
|
||||
const canvas = $('game');
|
||||
function isTyping(e) { const t = e.target; return t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.tagName === 'SELECT'); }
|
||||
window.addEventListener('keydown', e => {
|
||||
if (isTyping(e)) { if (e.key === 'Escape') e.target.blur(); return; }
|
||||
const k = e.key.length === 1 ? e.key.toLowerCase() : e.key;
|
||||
if (anyModal()) {
|
||||
if (k === 'Escape') { e.preventDefault(); const t = topModal(); closeModal(t); }
|
||||
else if (k === 'n' && !$('m-notebook').hidden) closeModal('m-notebook');
|
||||
return;
|
||||
}
|
||||
if (!GAME.running) return;
|
||||
if (e.key === 'Control') { GAME.ctrl = true; return; }
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
if (k === 's') { e.preventDefault(); saveWorld('main'); }
|
||||
else if (k === 'z' && !e.shiftKey) { e.preventDefault(); doUndo(); }
|
||||
return;
|
||||
}
|
||||
keys.add(k);
|
||||
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' ', 'Tab'].includes(k)) e.preventDefault();
|
||||
if (e.repeat) return;
|
||||
GAME.usingPad = false;
|
||||
switch (k) {
|
||||
case '1': case '2': case '3': case '4': selectTool(+k - 1); break;
|
||||
case 'c': case 'x': hudDirty = true; break;
|
||||
case 'v': startPaste(); break;
|
||||
case 'g': pickUnderCursor(); break;
|
||||
case 'q': cycleItem(-1); break;
|
||||
case 'e': cycleItem(1); break;
|
||||
case 'r': rotate(); break;
|
||||
case 't': if (toolId() === 'vacuum') toggleCollectUnderCursor(); else if (toolId() === 'build' && piece().filter) pickFilterUnderCursor(); break;
|
||||
case 'f': doInteract(); break;
|
||||
case 'h': toggleLens(); break;
|
||||
case 'i': GAME.infoOverlay = !GAME.infoOverlay; playSfx('ui'); toast(GAME.infoOverlay ? 'Info overlay on: what every filter does, and which way belts and tubes run. I hides it.' : 'Info overlay off.'); break;
|
||||
case 'l': bot.lamp = !bot.lamp; playSfx('ui'); break;
|
||||
case 'n': openNotebook(); break;
|
||||
case 'b': openNotebook('Blueprints'); break;
|
||||
case 'm': SET.muted = !SET.muted; saveSettings(); auVolumes(); toast(SET.muted ? 'Sound muted.' : 'Sound on.'); break;
|
||||
case '[': setBrush(-1); break;
|
||||
case ']': setBrush(1); break;
|
||||
case 'Delete': if (ghostBlocks.size || ghostMachines.length || ghostMolds.length) { clearAllGhosts(); toast('Blueprint ghosts cleared.'); } break;
|
||||
case 'Escape': if (GAME.selDrag || GAME.paste) stopSelectAndPaste(); else openPause(); break;
|
||||
case 'Tab': $('bagpanel').hidden = !$('bagpanel').hidden; break;
|
||||
}
|
||||
});
|
||||
window.addEventListener('keyup', e => { const k = e.key.length === 1 ? e.key.toLowerCase() : e.key; keys.delete(k); if (e.key === 'Control') GAME.ctrl = false; if (k === 'c' || k === 'x') hudDirty = true; });
|
||||
window.addEventListener('blur', () => { keys.clear(); GAME.lmb = GAME.rmb = false; GAME.ctrl = false; GAME.selDrag = null; endRmbRecord(); });
|
||||
canvas.addEventListener('mousemove', e => { GAME.ctrl = e.ctrlKey; GAME.mouse.x = e.clientX; GAME.mouse.y = e.clientY; GAME.mouse.in = true; if (GAME.usingPad) { GAME.usingPad = false; canvas.classList.remove('nocursor'); } });
|
||||
canvas.addEventListener('mouseleave', () => { GAME.mouse.in = false; });
|
||||
canvas.addEventListener('contextmenu', e => e.preventDefault());
|
||||
canvas.addEventListener('mousedown', e => {
|
||||
if (!GAME.running || GAME.paused) return;
|
||||
auInit();
|
||||
GAME.usingPad = false;
|
||||
if (e.button === 0) { GAME.lmb = true; primaryPress(e.shiftKey && !steering()); }
|
||||
else if (e.button === 2) { GAME.rmb = true; secondaryPress(); }
|
||||
else if (e.button === 1) { e.preventDefault(); pickUnderCursor(); }
|
||||
});
|
||||
window.addEventListener('mouseup', e => {
|
||||
if (e.button === 0) { GAME.ctrl = e.ctrlKey; if (GAME.lmb) primaryRelease(modShift()); GAME.lmb = false; }
|
||||
else if (e.button === 2) { GAME.rmb = false; endRmbRecord(); }
|
||||
});
|
||||
canvas.addEventListener('wheel', e => {
|
||||
e.preventDefault();
|
||||
if (!GAME.running || GAME.paused) return;
|
||||
const d = e.deltaY > 0 ? -1 : 1;
|
||||
if (e.shiftKey || e.ctrlKey) setBrush(d);
|
||||
else { SET.zoom = Math.max(2, Math.min(12, SET.zoom + d)); saveSettings(); resizeRender(); R.snapNext = true; }
|
||||
}, { passive: false });
|
||||
window.addEventListener('resize', () => { if (R.canvas) resizeRender(); });
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (!AU.ctx) return;
|
||||
if (document.hidden) { if (SET.muteUnfocused) AU.ctx.suspend(); if (GAME.running) saveWorld('auto', true); }
|
||||
else AU.ctx.resume();
|
||||
});
|
||||
Reference in New Issue
Block a user