- Updated contest strength calculations to utilize troop counts directly, enhancing accuracy in contest dynamics.
- Streamlined rendering logic by eliminating unnecessary checks and textures related to contest speeds, improving performance and clarity.
- Refactored related methods to focus on contest strength, ensuring a more cohesive approach to contest state management.
- Added support for change mask textures and framebuffers to track territory ownership changes.
- Introduced a new shader program for change mask processing, enhancing visual fidelity during transitions.
- Updated rendering logic to incorporate change mask updates, improving performance and accuracy in contested areas.
- Refactored related uniform and framebuffer management for better organization and clarity.
Refactor contest management in TerritoryLayer and TerritoryWebGLRenderer
- Updated contest duration handling to use ticks instead of milliseconds for improved precision.
- Introduced new tick-based state management for contest updates and rendering.
- Enhanced interpolation logic for smoother transitions between contest states.
- Removed obsolete smooth state handling and related properties to streamline code.
- Added support for older contest states in the WebGL renderer for better visual fidelity.
feat: Add view transforms and contest speed/strength visualization
- Add contest speed tracking with exponential moving average for dynamic contest visualization
- Add contest strength calculation based on attacker/defender troop ratios
- Implement view transform support (scale/offset) for zoom and pan functionality
- Add animated contest effects with noise-based cloud rendering
- Improve smooth territory transitions with better edge handling
- Add debug overlay for development with contest and rendering stats
- Refactor WebGL renderer to support world-space rendering instead of pixel-perfect
- Removed unused variables and logic related to contest state, including attacker relations and border management.
- Streamlined rendering logic by eliminating unnecessary checks for contested states, improving code clarity and performance.
- Added playerHighlightColor method to Theme interface and implemented it in PastelTheme and PastelThemeDark classes.
- Updated TerritoryLayer to redraw when the theme changes, ensuring consistent visual updates.
- Modified hoverHighlightOptions to use the new player highlight color for improved territory visualization.
- Removed redundant smooth state handling in TerritoryLayer, replacing it with a snapshot mechanism for smoother transitions.
- Updated rendering logic in TerritoryWebGLRenderer to utilize framebuffers for state management, enhancing performance and visual fidelity.
- Adjusted shader logic to accommodate new smoothing conditions and removed obsolete properties related to smooth state tracking.
Details:
Added per‑tile smoothing state in TerritoryLayer.ts (prev owner + change mask + 100 ms timing) and feed progress into the renderer each frame.
Added JFA seed + ping‑pong passes, prevOwner/changeMask textures, and smooth uniforms in TerritoryWebGLRenderer.ts, plus shader blending based on JFA distance.
- Introduced contest management in TerritoryLayer, replacing previous transition handling with a new system for managing contests between tile owners.
- Added methods to handle contest state, including starting, updating, and expiring contests.
- Updated TerritoryWebGLRenderer to support rendering contest data, including new textures and uniforms for contest owners, IDs, and times.
- Enhanced rendering logic to visually represent contest states, including color changes and border effects during contests.
- Added shared hover helper in HoverInfo.ts (same rules as PlayerInfoOverlay, including nearby ships on water).
- PlayerInfoOverlay.ts now uses getHoverInfo(...) instead of its local hover logic.
- TerritoryLayer.ts now uses getHoverInfo(...) and highlights the unit owner when hovering ships/boats.
- Updated transition handling in TerritoryLayer, replacing epoch and progress tracking with a more efficient system using arrays for start times and active masks.
- Introduced new methods to manage transition states and ensure proper initialization of transition data.
- Modified TerritoryWebGLRenderer to align with the new transition management, removing obsolete properties and updating uniform handling for transitions.
- Improved rendering logic to support smoother visual updates during tile transitions.
Refactor TerritoryLayer and Renderer for improved transition handling
- Removed unused imports and types, streamlining the TerritoryLayer code.
- Replaced the `TerritoryRendererStrategy` interface with direct usage of `TerritoryWebGLRenderer`.
- Enhanced transition management by introducing epoch and progress tracking for tile transitions.
- Updated rendering logic to utilize the new transition properties, ensuring smoother visual updates.
- Removed the CanvasTerritoryRenderer and related logic, consolidating rendering responsibilities within the WebGL renderer.
- Introduced a new `TileTransition` type to manage tile transitions.
- Added methods to handle the beginning and updating of tile transitions.
- Enhanced `TerritoryRenderer` interfaces to support transition progress.
- Updated `TerritoryWebGLRenderer` to manage transition textures and rendering.
- Modified `GameView` to track owner changes for tiles, enabling transition effects during gameplay.
- Added `lastPaletteSignature` to track palette changes.
- Implemented `computePaletteSignature()` to generate a unique signature based on player views and settings.
- Introduced `refreshPaletteIfNeeded()` to refresh the palette when the signature changes, ensuring visual consistency in the TerritoryLayer.
## Description:
Show bonus amount on currency packs
- Add `bonusAmount` field to `PackSchema` (non-negative int)
- Render a rotated green corner ribbon (`+X FREE!`) on pack tiles when
`bonusAmount > 0`
- Add `cosmetics.free` translation key with `numFree` param
<img width="720" height="359" alt="Screenshot 2026-05-12 at 7 40 12 AM"
src="https://github.com/user-attachments/assets/3dd70fc4-c922-47f4-aee6-055047b58563"
/>
Describe the PR.
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
evan
## Description:
improvements to clan ui.
<img width="788" height="290" alt="image"
src="https://github.com/user-attachments/assets/736ca147-bff4-44d8-8180-7b80a85556fe"
/>
added "expand all" and new collapsible sections.
<img width="787" height="550" alt="image"
src="https://github.com/user-attachments/assets/deb2f813-854b-46a9-a767-52c4f749f30f"
/>
which changes to collapse all when expanded
also adds more info about team (d,t,q,2,3,4,5,6,7 team)
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
w.o.n
## Description:
Loading spinner:
https://github.com/user-attachments/assets/9033b707-7499-4a52-b0c6-d96d8f331ee3
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
evan
## Description:
fixed issue when leaving and trying to rejoin a clan without a reload
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
w.o.n
Add a thinner hover ring for the ranked/create/join action buttons via a new --shadow-action-card-hover variable, apply the same ring to the flag and skin selectors, and split the solo button's hover scale to expand mostly vertically so it doesn't clip horizontally.
## Description:
Refactors tab handling out of the individual modal components and into
the base o-modal component. Tabs are now declared by passing tabs,
activeTab, and onTabChange props, and a new named header slot pins
consumer-supplied content above the tabs. This standardizes the modal
tab look.
<img width="1089" height="290" alt="Screenshot 2026-05-06 at 12 17
33 PM"
src="https://github.com/user-attachments/assets/08d5a039-0aef-4aa7-b972-1e43b8723685"
/>
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
evan
## Description:
Currently it is impossible to search for 2 letter clan tags (UN, FR,
EU), this is because of an off by one error present in the API
## Please complete the following:
- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
Babyboucher
## Description:
Raised MAX_INTENT_SIZE from 500 to 2000 bytes — the move_warship intent
could exceed the old limit and get rejected.
Removed the separate MAX_CONFIG_INTENT_SIZE (also 2000) and the
intentType branching, since both paths now share the same cap.
## Please complete the following:
- [ ] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
evan
## Description:
- Inline the game worker into the main bundle (`?worker&inline`) instead
of loading it from the CDN via a same-origin Blob trampoline.
- Some users were failing to load the worker — most likely a
cross-origin / CDN edge case the trampoline didn't paper over. Inlining
serves the worker as a same-origin Blob from the main bundle, bypassing
the cross-origin `new Worker(url)` restriction entirely.
- Removes `createGameWorker()`, the trampoline script, the
`trampoline_error` message type, and the `onTrampolineError` listener in
`initialize()`. The 60s init timeout backstop stays.
- `cdnBase` is still forwarded in the `init` message — that's for
in-worker asset fetches (maps, etc.), unrelated to loading the worker
module itself.
Tradeoff: the worker bundles all of `src/core`, so the main JS download
grows by that amount on every page load (including lobby/settings pages
that never start a game). Accepted because load reliability > bundle
size here.
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
evan
## Description:
Simplifies the attacking-troops overlay: removes the soldier icon and
strength bar, dropping each label down to just the troop number in cyan
(outgoing) or red (incoming) with a soft dark text-shadow halo and no
background fill so territory borders show through cleanly. Also splits
the label into outer (transitioned position) and inner (instant scale)
divs so zoom changes no longer get smeared by the 0.25s cluster-move
transition, retunes the zoom→size curve, and skips incoming labels from
bot tribes to cut clutter.
<img width="374" height="307" alt="Screenshot 2026-05-04 at 5 53 17 PM"
src="https://github.com/user-attachments/assets/a7044221-06cc-4027-b19a-6ff4ca8f542a"
/>
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
evan
## Description
Re-enables Turnstile verification (was temporarily disabled in v31 to
diagnose intermittent `invalid-input-response` rejections) and moves the
verification call off the game servers.
Game servers no longer hold `TURNSTILE_SECRET_KEY` or hit
`challenges.cloudflare.com` directly. Instead they POST to
`${jwtIssuer}/turnstile` on the api-worker (authenticated with the
existing `apiKey`), which holds the secret and proxies to Cloudflare.
Shrinks blast radius and removes the secret from every game host + GH
Actions workflow.
Response from the api-worker is Zod-validated; null tokens short-circuit
to `rejected` locally.
## Please complete the following:
- [x] I have added screenshots for all UI updates (n/a — server only)
- [x] I process any text displayed to the user through translateText()
(n/a — no user-visible text)
- [ ] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Discord:
evanpelle
## Description:
`MapLandTiles` was fetching map manifests via HTTP at
`http://localhost:3000/maps/<map>/manifest.json`. This stopped working
after PR #3494 moved all public assets from stable paths (e.g.
`/maps/...`) to content-hashed paths under `/_assets/...`. The master
server no longer serves `/maps/` -- requests fell through to the SPA
handler, returned `index.html`, failed JSON parsing, and silently fell
back to the default `1_000_000` land tile count.
With 1M tiles, `calculateMapPlayerCounts` produces `[50, 40, 25]`
instead of the real values (e.g. `[185, 140, 95]` for Alps), causing all
public lobbies to be capped at 25-50 players regardless of map.
Fixes this by reading map manifests directly from disk instead of via
HTTP:
- In production: resolves the source path through
`getRuntimeAssetManifest()` to the hashed file under `static/_assets/`,
then reads it with `fs.readFile`.
- In dev: falls back to `resources/maps/<name>/manifest.json` directly
(the Dockerfile removes `resources/maps` in production, so this branch
only runs locally).
Also adds an in-process cache so each map manifest is only read once per
server lifetime.
Verified that it works on
https://fix-map-land-tiles-asset-manifest.openfront.dev/ and locally.
## Please complete the following:
- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
FloPinguin
## Description:
Would be very good to get these fixes last minute into v31.
- **Farming nations for cities is fixed**: Here you can see city farming
in action, vari farms Finland:
https://www.youtube.com/watch?v=J4J0ajlnSHs&t=137s - Lots of 125k gold
cities... Now nations will build defense posts instead of cities:
- **Nations now build defense posts reactively** instead of through the
normal structure build order. When a nation is under significant land
attack (incoming/own troops >= 35%), it places a defense post near the
attack front -- skipped on Easy, capped at 1 on Medium, and scaled by
the incoming troop ratio on Hard/Impossible. Posts spread along the
front. The old `defensePostValue()` placement path is removed.
- **Nations now capture nuked territory.**
`hasLandBorderWithTerraNullius()` was incorrectly filtering out tiles
with fallout, causing the `nuked` attack strategy to never dispatch a
attack. Big bug
.
- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
FloPinguin
Previously, winnerVotes was keyed only on winner, so a client could send the correct winner with spoofed allPlayersStats and have their vote count toward the majority. The key is now a SHA-256 hash of both winner and allPlayersStats together, so any stats divergence produces a distinct key that must independently reach majority.
When the winner is confirmed, the log now includes votesByKey — a summary of every distinct key, its vote count, and winner value — making stat manipulation visible in the logs.
CSS mask-image triggers a CORS fetch, which failed for the CDN-hosted medal SVG. Switched to a Vite ?raw import so the SVG is embedded as a data URI at build time — no network request, no CORS.
Also stripped the SVG of Inkscape metadata and replaced filter-based color inversion with a plain fill="white", shrinking it from 3,278 → 955 bytes (387 bytes gzipped).
## Description:
The motivation is to prevent snowballing players from also gaining too
much gold by conquering other players
- Adds `conquerGoldAmount` to `Config`/`DefaultConfig`: returns 100% of
captured gold for bots/nations, 50% for human players
- Updates `GameImpl.conquerPlayer` to use this amount for the
conqueror's gold gain (the conquered player still loses their full gold)
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
evan
## Description:
reloadMissile() was inside the isInCooldown() block. For level-2+ SAMs,
isInCooldown() returns queue.length === level, so after firing one of
two missiles (queue.length = 1 < level = 2) the SAM is not in cooldown —
meaning expired timers were never cleaned up. Stale queue entries caused
subsequent shots to be treated as still-cooling even after the cooldown
elapsed.
SAM execution now mirrors the missile silo execution
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
evan