From 06df4be96f7e180bce8c1c2b8c4fdbe0d746ed84 Mon Sep 17 00:00:00 2001 From: Adrien Jaguenet Date: Wed, 16 Sep 2026 22:41:57 +0200 Subject: [PATCH] Artillery + encirclement + budget --- AGENTS.md | 7 + client/css/style.css | 63 ++- client/index.html | 13 +- client/js/game_screen.js | 86 ++++- client/js/map_view.js | 166 +++++++- client/js/modals.js | 98 ++++- nix/module.nix | 43 ++- shared/data.js | 98 ++++- shared/game_state.js | 597 ++++++++++++++++++++++++++++- shared/hex_pathfinder.js | 12 +- shared/rules.js | 28 +- tests/data_test.js | 20 +- tests/framework/helpers.js | 1 + tests/game_screen_test.js | 73 +++- tests/game_state_buildings_test.js | 42 +- tests/hex_pathfinder_test.js | 16 + tests/modals_test.js | 54 +++ tests/rules_test.js | 58 ++- tests/run_tests.js | 4 + 19 files changed, 1398 insertions(+), 81 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2ad436a..035d753 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -79,3 +79,10 @@ There is no linter, formatter, or CI. Do not invent commands beyond these. `/client/index.html` and proxying `/ws` to the local Node server. The browser client connects to the page's own origin, so HTTPS gives it `wss://`. - Build locally with `nix build .#server` and `nix build .#web`. +- Updating without a rebuild: set `services.battle-for-tismo.appDirectory` to a + writable checkout (e.g. `/opt/battle-for-tismo`); the unit then runs + `node /server/server.js` instead of the Nix-store copy. Serve nginx + `client/` and `shared/` from that same checkout (e.g. `alias` locations), then + `git pull` and `systemctl restart battle-for-tismo`. Keep the client and + server trees on the same commit: the client mirrors the server's movement + model, so a mismatched pair makes prediction fight the server. diff --git a/client/css/style.css b/client/css/style.css index 13da9fb..ce1f0f9 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -169,12 +169,29 @@ input:focus, select:focus { border-color: var(--accent); } .entity { position: absolute; } .unit { + --unit-size: 38px; width: 38px; height: 38px; margin-left: -19px; margin-top: -19px; cursor: pointer; } +.unit .unit-statuses, +.city .city-statuses { + position: absolute; + display: flex; + gap: 1px; + pointer-events: none; + z-index: 2; +} +.unit .unit-statuses { top: -4px; right: -4px; } +.city .city-statuses { top: -6px; left: 50%; transform: translateX(-50%); } +.status-icon { + width: calc(var(--unit-size, 38px) / 5); + height: calc(var(--unit-size, 38px) / 5); + display: block; + filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.9)); +} .unit .body { width: 100%; height: 100%; @@ -216,12 +233,19 @@ input:focus, select:focus { border-color: var(--accent); } cursor: pointer; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.7); } -.city .label { - margin-top: 2px; +/* City names live in their own layer above every unit, marker and status icon, + so they stay legible instead of being covered by whatever is nearby. */ +.city-label-layer { pointer-events: none; } +.city-label { + position: absolute; + transform: translate(-50%, 0); + margin-top: 15px; font-size: 11px; color: #fff; + text-align: center; text-shadow: 0 1px 2px #000, 0 0 4px #000; white-space: nowrap; + pointer-events: none; } .path-seg { @@ -304,6 +328,30 @@ input:focus, select:focus { border-color: var(--accent); } .health-bar { width: 220px; height: 12px; background: rgba(30, 0, 0, 0.8); border-radius: 6px; overflow: hidden; margin-top: 6px; } #unit-health-fill { height: 100%; background: linear-gradient(180deg, #6fe08a, #2fa04d); } +.unit-stats { display: flex; gap: 16px; margin-top: 6px; } +.unit-stat { + font-size: 13px; + color: var(--muted); + border-bottom: 1px dotted rgba(159, 176, 200, 0.5); + cursor: help; +} +.unit-stat:hover { color: var(--text); } + +.tooltip { + position: absolute; + z-index: 80; + max-width: 280px; + padding: 6px 10px; + border-radius: 7px; + background: rgba(8, 12, 20, 0.97); + border: 1px solid var(--panel-border); + color: var(--text); + font-size: 12px; + white-space: nowrap; + pointer-events: none; + box-shadow: 0 6px 18px rgba(0, 0, 0, 0.6); +} + /* --------------------------------------------------------------- modals -- */ .modal { @@ -372,6 +420,17 @@ input:focus, select:focus { border-color: var(--accent); } .row-card .cost { font-size: 12px; color: var(--muted); margin-top: 3px; } .row-card .actions { display: flex; flex-direction: column; gap: 6px; justify-content: center; } .row-card .actions .aero { min-width: 170px; } +.row-card .value { + display: flex; + align-items: flex-start; + justify-content: flex-end; + min-width: 140px; + font-weight: 600; +} +.row-card .value.positive { color: #8fe3a8; } +.row-card .value.negative { color: #ff9a9a; } +.row-card .budget-detail { margin-top: 4px; } +.row-card .budget-line { font-size: 12px; color: var(--muted); margin-top: 2px; } .training-status { padding: 0 18px 16px; } .progress { height: 14px; margin-top: 8px; background: rgba(0, 0, 0, 0.5); border-radius: 7px; overflow: hidden; } diff --git a/client/index.html b/client/index.html index f0c7055..a37fbd9 100644 --- a/client/index.html +++ b/client/index.html @@ -87,6 +87,7 @@
+
@@ -129,9 +130,14 @@ + @@ -161,7 +167,7 @@ - +