The code moves into src/: index, server and the test harness keep the root

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Fable 5 committed 2026-08-11 13:45:23 +02:00
1 parent c122745390
commit 1f636530b9
16 files changed
+16 -13

No files matched your search

+4 -2
View File
@@ -3,7 +3,8 @@
# stage COPYs the marker so BuildKit cannot skip this stage as unused.
FROM node:22-alpine AS test
WORKDIR /app
COPY *.js /app/
COPY test.js /app/
COPY src /app/src
RUN node test.js && touch /tests-passed
FROM python:3.12-alpine
@@ -14,7 +15,8 @@ FROM python:3.12-alpine
# ponytail: switch to hashed filenames if traffic ever matters.
WORKDIR /app
COPY --from=test /tests-passed /tmp/tests-passed
COPY index.html manifest.webmanifest *.js server.py /app/
COPY index.html manifest.webmanifest server.py /app/
COPY src /app/src
COPY assets /app/assets
# THE SCORES LIVE HERE AND THIS MUST BE A PERSISTENT VOLUME. Without one the json sits in the
# container's writable layer and every deploy silently resets the leaderboard to empty — which
+5 -4
View File
@@ -29,7 +29,8 @@ the primary target.
the Dockerfile. Oliver tests on his phone at splatter-tennis.wuwi.one ~30s after
a push. **Bump `main.js?v=N` in index.html every time** or phones keep the old build.
The Dockerfile must also COPY any new top-level file (it currently ships `index.html`,
`manifest.webmanifest`, `*.js`, `server.py`, `assets/`).
`manifest.webmanifest`, `server.py`, `src/`, `assets/` — `test.js` only feeds the test stage and
is deliberately not in the served image).
- **The image is `python:3.12-alpine` running `server.py`, no longer `nginx:alpine`.** The world
board needs one process that can WRITE, and nginx is not one; everything the nginx conf did here
(no-cache so a deploy lands on a plain reload, and `application/manifest+json`, without which
@@ -51,7 +52,7 @@ the primary target.
- **Oliver edits files himself** (tuning values, sometimes sprites). ALWAYS Read current file state
before editing; never assume your last version is what is on disk.
## Module map (all in repo root)
## Module map (game code in `src/`; `index.html`, `server.py` and `test.js` at the root)
`config.js` TUNE (live T panel) + structural constants + `UI` palette · `state.js` the `G` object +
P/O/ball/score + `setupPoint`/`sendBoy` · `gameplay.js` ballistics/swings/serve/rules/AI/`update()` +
transition + `splatPoint` + ball-boy routines · `beats.js` **the beat machinery: every mode
@@ -827,8 +828,8 @@ Clear the intro first: `document.getElementById('intro')?.remove()`.
own geometry could possibly reach. To check WHERE something lands, recompute the projection
directly (`T.proj(...)`) and compare numbers; keep pixel-diffing for "did anything at all change".
- **The module-refresh list MUST include the VERSIONED entry point.** `index.html` loads
`main.js?v=N`, and `fetch('main.js', {cache:'reload'})` does not bust that URL — so main.js alone
stays stale through a reload while every other module is fresh. Read the src off the page:
`src/main.js?v=N`, and `fetch('src/main.js', {cache:'reload'})` does not bust that URL — so main.js
alone stays stale through a reload while every other module is fresh. Read the src off the page:
`document.querySelector('script[type=module]').getAttribute('src')` and fetch that too. Symptom: a
change in main.js appears to do nothing at all, repeatably.
- **You MUST set `T.TUNE.autoAssist = 1.15` first** — the Browser pane reports `pointer: fine`, so
+1 -1
View File
@@ -398,6 +398,6 @@
</div>
</div>
</div>
<script type="module" src="main.js?v=148"></script>
<script type="module" src="src/main.js?v=149"></script>
</body>
</html>
View File
File renamed without changes.
View File
File renamed without changes.
View File
File renamed without changes.
View File
File renamed without changes.
View File
File renamed without changes.
View File
File renamed without changes.
+1 -1
View File
@@ -278,7 +278,7 @@ intro.addEventListener('pointerdown', () => menuMusic());
// which build am I looking at? Push = deploy and the browser cannot see git, so the served file's
// own timestamp IS the version marker — it changes on every deploy, with nothing to remember to bump.
// Match it against `git log -1 --format=%cd` when a phone looks suspiciously out of date.
fetch('main.js', { method: 'HEAD' })
fetch('src/main.js', { method: 'HEAD' }) // page-relative: index.html sits at the root, the code in src/
.then(r => { const t = r.headers.get('last-modified'); if (!t) return;
document.getElementById('build').textContent = 'BUILD ' + new Date(t).toLocaleString('en-GB').replace(',', ''); })
.catch(() => {}); // no header, no line — never a broken start screen
View File
File renamed without changes.
View File
File renamed without changes.
View File
File renamed without changes.
View File
File renamed without changes.
View File
File renamed without changes.
+5 -5
View File
@@ -60,11 +60,11 @@ globalThis.ResizeObserver = class { observe() {} disconnect() {} };
globalThis.getComputedStyle = () => ({ paddingTop: '0' });
// ---- game modules (real code) ----------------------------------------------
const { G, P, score, ball, setupPoint } = await import('./state.js');
const { update, forceZombies, splatPoint, restartRun } = await import('./gameplay.js');
const { startDuel, resolveInterlude, startBossFight, endBossFight } = await import('./beats.js');
const { zombies, spawnZombie } = await import('./zombies.js');
const { TUNE } = await import('./config.js');
const { G, P, score, ball, setupPoint } = await import('./src/state.js');
const { update, forceZombies, splatPoint, restartRun } = await import('./src/gameplay.js');
const { startDuel, resolveInterlude, startBossFight, endBossFight } = await import('./src/beats.js');
const { zombies, spawnZombie } = await import('./src/zombies.js');
const { TUNE } = await import('./src/config.js');
const DT = 1 / 60;
function freshSplatter() {