Name box in the New world panel (t10)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+3
-3
@@ -370,9 +370,9 @@ The title screen art.
|
|||||||
- functions: buildTitleCells, drawTitle
|
- functions: buildTitleCells, drawTitle
|
||||||
- variables: FONT5, TITLE
|
- variables: FONT5, TITLE
|
||||||
|
|
||||||
### src/ui/saves.js (177 lines)
|
### src/ui/saves.js (178 lines)
|
||||||
Saves in IndexedDB, world files (export / import) and the blueprint library.
|
Saves in IndexedDB, world files (export / import) and the blueprint library.
|
||||||
- functions: newWorldId, worldWrite, listWorlds, putWorldEntry, worldNewest, deleteWorld, renameWorld, migrateWorlds, saveWorld, newestSave, b64FromBytes, bytesFromB64, gz, exportWorld, importWorldFile, libLoad, libSave, bpEncode, bpValid, bpDecode
|
- functions: newWorldId, worldWrite, listWorlds, putWorldEntry, worldNewest, deleteWorld, cleanWorldName, renameWorld, migrateWorlds, saveWorld, newestSave, b64FromBytes, bytesFromB64, gz, exportWorld, importWorldFile, libLoad, libSave, bpEncode, bpValid, bpDecode
|
||||||
- variables: DB, worldIdTime, worldIdCounter, worldWrites
|
- variables: DB, worldIdTime, worldIdCounter, worldWrites
|
||||||
|
|
||||||
### src/ui/session.js (125 lines)
|
### src/ui/session.js (125 lines)
|
||||||
@@ -431,7 +431,7 @@ Previews drawn under the cursor: build ghosts, drill aim and pulses, selections,
|
|||||||
Events from the simulation (emit) turned into sounds, toasts and cards.
|
Events from the simulation (emit) turned into sounds, toasts and cards.
|
||||||
- functions: drainEvents
|
- functions: drainEvents
|
||||||
|
|
||||||
### src/ui/loop.js (119 lines)
|
### src/ui/loop.js (126 lines)
|
||||||
The frame loop and boot.
|
The frame loop and boot.
|
||||||
- functions: scanVisible, frame, boot
|
- functions: scanVisible, frame, boot
|
||||||
- variables: lastT, acc, lastRender, frameMs, simMs, fpsSm, ambientT
|
- variables: lastT, acc, lastRender, frameMs, simMs, fpsSm, ambientT
|
||||||
|
|||||||
@@ -4,6 +4,17 @@ What agents have done, newest first. Add an entry for every piece of work: the d
|
|||||||
note or request it was for, what changed, the files touched, the tests run, and anything left
|
note or request it was for, what changed, the files touched, the tests run, and anything left
|
||||||
open or not verified. Keep each entry short; the git history has the details.
|
open or not verified. Keep each entry short; the git history has the details.
|
||||||
|
|
||||||
|
## 2026-10-24: t10 World name box for a new world (Claude Code, Codex)
|
||||||
|
|
||||||
|
- `src/shell.html`, `src/styles/style.css`: a Name box across the top of the New world panel.
|
||||||
|
- `src/ui/loop.js`: opening the panel fills it with the next `World N` and selects it; Enter in
|
||||||
|
Name or Seed generates. `src/ui/session.js`: `startNewWorld(seed, w, h, name)`.
|
||||||
|
- `src/ui/saves.js`: `cleanWorldName` (trim, 40 characters, else a fallback), shared with
|
||||||
|
`renameWorld`.
|
||||||
|
- `test/play.py`: a typed name reaches the Worlds list and the pause menu; the default and an
|
||||||
|
all-spaces name both give `World N`. Screenshot `shots/play/new-world.png` checked.
|
||||||
|
- Tests: `run_all.py --browser` all pass.
|
||||||
|
|
||||||
## 2026-10-24: t7 Option to turn off smooth pan following the mouse (Claude Code, Codex)
|
## 2026-10-24: t7 Option to turn off smooth pan following the mouse (Claude Code, Codex)
|
||||||
|
|
||||||
- New setting `cameraLead` (Display: "Camera looks toward the pointer", on by default):
|
- New setting `cameraLead` (Display: "Camera looks toward the pointer", on by default):
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
<button class="btn" id="btn-worlds" hidden>Worlds <small>load · rename · delete</small></button>
|
<button class="btn" id="btn-worlds" hidden>Worlds <small>load · rename · delete</small></button>
|
||||||
<button class="btn" id="btn-new">New world <small>generate</small></button>
|
<button class="btn" id="btn-new">New world <small>generate</small></button>
|
||||||
<div class="plate newworld" id="newworld" hidden>
|
<div class="plate newworld" id="newworld" hidden>
|
||||||
|
<label>Name <input id="nw-name" type="text" maxlength="40" autocomplete="off" spellcheck="false"></label>
|
||||||
<label>Seed <input id="nw-seed" type="text" inputmode="numeric" autocomplete="off"></label>
|
<label>Seed <input id="nw-seed" type="text" inputmode="numeric" autocomplete="off"></label>
|
||||||
<label>Size
|
<label>Size
|
||||||
<select id="nw-size">
|
<select id="nw-size">
|
||||||
|
|||||||
@@ -293,6 +293,7 @@ input.text { background: var(--soot-0); border: 1px solid var(--soot-4); border-
|
|||||||
.newworld { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px; padding: 10px; }
|
.newworld { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px; padding: 10px; }
|
||||||
.newworld label { display: flex; flex-direction: column; gap: 4px; font-size: 0.86em; color: var(--ash); }
|
.newworld label { display: flex; flex-direction: column; gap: 4px; font-size: 0.86em; color: var(--ash); }
|
||||||
.newworld select, .newworld input { background: var(--soot-0); border: 1px solid var(--soot-4); border-radius: 4px; padding: 6px 8px; color: var(--bone); }
|
.newworld select, .newworld input { background: var(--soot-0); border: 1px solid var(--soot-4); border-radius: 4px; padding: 6px 8px; color: var(--bone); }
|
||||||
|
.newworld label:first-child { grid-column: 1 / -1; }
|
||||||
.newworld .full { grid-column: 1 / -1; }
|
.newworld .full { grid-column: 1 / -1; }
|
||||||
.tfoot { text-align: center; color: var(--ash-dim); font-size: 0.82em; font-family: var(--font-mono); }
|
.tfoot { text-align: center; color: var(--ash-dim); font-size: 0.82em; font-family: var(--font-mono); }
|
||||||
#loading { position: fixed; inset: 0; z-index: 30; display: grid; place-items: center; background: rgba(11, 9, 8, 0.86); }
|
#loading { position: fixed; inset: 0; z-index: 30; display: grid; place-items: center; background: rgba(11, 9, 8, 0.86); }
|
||||||
|
|||||||
+9
-2
@@ -97,11 +97,18 @@ function boot(hotData) {
|
|||||||
buildTitleCells(); drawTitle();
|
buildTitleCells(); drawTitle();
|
||||||
$('nw-seed').value = String(Math.floor(Math.random() * 900000) + 100000);
|
$('nw-seed').value = String(Math.floor(Math.random() * 900000) + 100000);
|
||||||
$('wallet').onclick = () => { playSfx('ui'); openNotebook('Market'); };
|
$('wallet').onclick = () => { playSfx('ui'); openNotebook('Market'); };
|
||||||
$('btn-new').onclick = () => { auInit(); playSfx('ui'); $('newworld').hidden = !$('newworld').hidden; if (!$('newworld').hidden) $('btn-generate').focus(); };
|
$('btn-new').onclick = async () => {
|
||||||
|
auInit(); playSfx('ui'); $('newworld').hidden = !$('newworld').hidden;
|
||||||
|
if ($('newworld').hidden) return;
|
||||||
|
try { $('nw-name').value = `World ${(await listWorlds()).length + 1}`; }
|
||||||
|
catch (e) { $('nw-name').value = 'World 1'; }
|
||||||
|
$('nw-name').focus(); $('nw-name').select();
|
||||||
|
};
|
||||||
|
for (const id of ['nw-name', 'nw-seed']) $(id).onkeydown = e => { if (e.key === 'Enter') $('btn-generate').click(); };
|
||||||
$('btn-generate').onclick = () => {
|
$('btn-generate').onclick = () => {
|
||||||
const seed = parseInt(String($('nw-seed').value).replace(/\D/g, ''), 10) || Math.floor(Math.random() * 1e6);
|
const seed = parseInt(String($('nw-seed').value).replace(/\D/g, ''), 10) || Math.floor(Math.random() * 1e6);
|
||||||
const [w, h] = $('nw-size').value.split('x').map(Number);
|
const [w, h] = $('nw-size').value.split('x').map(Number);
|
||||||
startNewWorld(seed, w, h);
|
startNewWorld(seed, w, h, $('nw-name').value);
|
||||||
};
|
};
|
||||||
$('btn-continue').onclick = async () => { auInit(); playSfx('ui'); const sv = await newestSave(); if (sv) startFromSave(sv); else toast('No saved world found.'); };
|
$('btn-continue').onclick = async () => { auInit(); playSfx('ui'); const sv = await newestSave(); if (sv) startFromSave(sv); else toast('No saved world found.'); };
|
||||||
$('btn-title-settings').onclick = () => { auInit(); playSfx('ui'); openSettings('Display'); };
|
$('btn-title-settings').onclick = () => { auInit(); playSfx('ui'); openSettings('Display'); };
|
||||||
|
|||||||
+2
-1
@@ -54,11 +54,12 @@ function deleteWorld(id) {
|
|||||||
if (GAME.worldId === id) { GAME.worldId = ''; GAME.worldName = ''; }
|
if (GAME.worldId === id) { GAME.worldId = ''; GAME.worldName = ''; }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function cleanWorldName(name, fallback) { return String(name).trim().slice(0, 40) || fallback; }
|
||||||
function renameWorld(id, name) {
|
function renameWorld(id, name) {
|
||||||
return worldWrite(async () => {
|
return worldWrite(async () => {
|
||||||
const list = await listWorlds(), entry = list.find(w => w.id === id);
|
const list = await listWorlds(), entry = list.find(w => w.id === id);
|
||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
entry.name = String(name).trim().slice(0, 40) || entry.name;
|
entry.name = cleanWorldName(name, entry.name);
|
||||||
await DB.put('worlds', list);
|
await DB.put('worlds', list);
|
||||||
if (GAME.worldId === id) GAME.worldName = entry.name;
|
if (GAME.worldId === id) GAME.worldName = entry.name;
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-2
@@ -15,13 +15,13 @@ function enterGame() {
|
|||||||
GAME.drag = null; GAME.lmb = GAME.rmb = false; lastBagKey = '';
|
GAME.drag = null; GAME.lmb = GAME.rmb = false; lastBagKey = '';
|
||||||
hudDirty = true; renderHotbar();
|
hudDirty = true; renderHotbar();
|
||||||
}
|
}
|
||||||
function startNewWorld(seed, w, h) {
|
function startNewWorld(seed, w, h, name = '') {
|
||||||
auInit();
|
auInit();
|
||||||
showLoading(true, 0);
|
showLoading(true, 0);
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
const worlds = await listWorlds();
|
const worlds = await listWorlds();
|
||||||
GAME.worldId = newWorldId(); GAME.worldName = `World ${worlds.length + 1}`;
|
GAME.worldId = newWorldId(); GAME.worldName = cleanWorldName(name, `World ${worlds.length + 1}`);
|
||||||
await putWorldEntry({ id: GAME.worldId, name: GAME.worldName, seed, w, h, createdAt: Date.now(), savedAt: 0, playTime: 0 });
|
await putWorldEntry({ id: GAME.worldId, name: GAME.worldName, seed, w, h, createdAt: Date.now(), savedAt: 0, playTime: 0 });
|
||||||
generateWorld(seed, w, h, null);
|
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;
|
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;
|
||||||
|
|||||||
+15
-2
@@ -41,11 +41,20 @@ with sync_playwright() as p:
|
|||||||
pg.click('#btn-continue'); pg.wait_for_function('GAME.running')
|
pg.click('#btn-continue'); pg.wait_for_function('GAME.running')
|
||||||
check('Continue loads the migrated seed and playtime', pg.evaluate('worldSeed === 12345 && GAME.playTime >= 3661 && !!GAME.worldId'))
|
check('Continue loads the migrated seed and playtime', pg.evaluate('worldSeed === 12345 && GAME.playTime >= 3661 && !!GAME.worldId'))
|
||||||
pg.evaluate('quitToTitle()')
|
pg.evaluate('quitToTitle()')
|
||||||
|
names = {}
|
||||||
for seed in [222, 333]:
|
for seed in [222, 333]:
|
||||||
if not pg.is_visible('#nw-seed'): pg.click('#btn-new')
|
if pg.is_visible('#nw-seed'): pg.click('#btn-new')
|
||||||
pg.fill('#nw-seed', str(seed)); pg.click('#btn-generate')
|
pg.click('#btn-new')
|
||||||
|
pg.wait_for_function("document.activeElement === $('nw-name')")
|
||||||
|
names[seed] = 'Copper Valley' if seed == 222 else pg.input_value('#nw-name')
|
||||||
|
check(f'new world {seed} offers the next default name', pg.input_value('#nw-name') == f'World {2 if seed == 222 else 3}')
|
||||||
|
if seed == 222:
|
||||||
|
pg.fill('#nw-name', names[seed])
|
||||||
|
pg.screenshot(path=str(out / 'new-world.png'))
|
||||||
|
pg.fill('#nw-seed', str(seed)); pg.press('#nw-name' if seed == 222 else '#nw-seed', 'Enter')
|
||||||
pg.wait_for_function('GAME.running', timeout=20000)
|
pg.wait_for_function('GAME.running', timeout=20000)
|
||||||
check(f'new world {seed} shows the Welcome card', pg.evaluate("[...$('cards').children].some(c => c.textContent.includes('Welcome'))"))
|
check(f'new world {seed} shows the Welcome card', pg.evaluate("[...$('cards').children].some(c => c.textContent.includes('Welcome'))"))
|
||||||
|
check(f'pause menu shows world {seed} name', names[seed] in pg.evaluate("() => { openPause(); return $('pause-meta').textContent; }"))
|
||||||
pg.evaluate('quitToTitle()')
|
pg.evaluate('quitToTitle()')
|
||||||
pg.wait_for_function("!$('title').hidden")
|
pg.wait_for_function("!$('title').hidden")
|
||||||
pg.click('#btn-worlds'); pg.locator('#m-worlds').wait_for(state='visible')
|
pg.click('#btn-worlds'); pg.locator('#m-worlds').wait_for(state='visible')
|
||||||
@@ -59,6 +68,7 @@ with sync_playwright() as p:
|
|||||||
pg.set_viewport_size({'width': 1280, 'height': 800}); pg.evaluate("SET.look = 'pixel'")
|
pg.set_viewport_size({'width': 1280, 'height': 800}); pg.evaluate("SET.look = 'pixel'")
|
||||||
ids = pg.evaluate("async () => Object.fromEntries((await listWorlds()).map(w => [w.seed, w.id]))")
|
ids = pg.evaluate("async () => Object.fromEntries((await listWorlds()).map(w => [w.seed, w.id]))")
|
||||||
for seed in [222, 333]:
|
for seed in [222, 333]:
|
||||||
|
check(f'Worlds list shows world {seed} name', pg.locator(f'[data-world="{ids[str(seed)]}"] strong').inner_text() == names[seed])
|
||||||
row = pg.locator(f'[data-world="{ids[str(seed)]}"]')
|
row = pg.locator(f'[data-world="{ids[str(seed)]}"]')
|
||||||
row.get_by_role('button', name='Load', exact=True).click(); pg.wait_for_function('GAME.running')
|
row.get_by_role('button', name='Load', exact=True).click(); pg.wait_for_function('GAME.running')
|
||||||
check(f'Load switches to seed {seed}', pg.evaluate('worldSeed') == seed and pg.evaluate('GAME.worldId') == ids[str(seed)])
|
check(f'Load switches to seed {seed}', pg.evaluate('worldSeed') == seed and pg.evaluate('GAME.worldId') == ids[str(seed)])
|
||||||
@@ -93,8 +103,11 @@ with sync_playwright() as p:
|
|||||||
check('Continue chooses the most recently saved world', pg.evaluate('worldSeed') == 333)
|
check('Continue chooses the most recently saved world', pg.evaluate('worldSeed') == 333)
|
||||||
pg.evaluate('quitToTitle()')
|
pg.evaluate('quitToTitle()')
|
||||||
if not pg.is_visible('#nw-seed'): pg.click('#btn-new')
|
if not pg.is_visible('#nw-seed'): pg.click('#btn-new')
|
||||||
|
blank_default = pg.evaluate("listWorlds().then(w => `World ${w.length + 1}`)")
|
||||||
|
pg.fill('#nw-name', ' ')
|
||||||
pg.fill('#nw-seed', '777'); pg.click('#btn-generate')
|
pg.fill('#nw-seed', '777'); pg.click('#btn-generate')
|
||||||
pg.wait_for_function('GAME.running === true', timeout=20000)
|
pg.wait_for_function('GAME.running === true', timeout=20000)
|
||||||
|
check('blank new world names use the default', pg.evaluate('GAME.worldName') == blank_default)
|
||||||
pg.wait_for_timeout(500)
|
pg.wait_for_timeout(500)
|
||||||
ev = pg.evaluate
|
ev = pg.evaluate
|
||||||
ev("window._maxStatus = ''; const _ss = setStatus; setStatus = function (s, k) { if (s && s.length > _maxStatus.length) _maxStatus = s; return _ss(s, k); }; 0")
|
ev("window._maxStatus = ''; const _ss = setStatus; setStatus = function (s, k) { if (s && s.length > _maxStatus.length) _maxStatus = s; return _ss(s, k); }; 0")
|
||||||
|
|||||||
Reference in New Issue
Block a user