- Modified terrain shader parameters in GroundTruthData for better rendering.
- Added new user-configurable settings for water effects in TerrainShaderRegistry.
- Enhanced terrain compute shaders to incorporate water depth and blur adjustments.
- Refactored shader logic to improve water color blending and depth calculations
- Changed section title from "Shaders" to "Terrain" in WebGPUDebugOverlay.
- Updated default values for various terrain shader parameters to improve rendering quality, including noise strength, blend width, lighting strength, and cavity strength.
- Add terrain-compute-improved-lite.wgsl and terrain-compute-improved-heavy.wgsl
- Create TerrainShaderRegistry.ts for shader management
- Refactor TerrainComputePass to support dynamic shader switching
- Update TerritoryRenderer, TerritoryLayer, and GroundTruthData for new shader integration
- Enhance WebGPUDebugOverlay with additional debugging capabilities
- Introduced WebGPUComputeMetricsEvent to track compute timing.
- Added WebGPUDebugOverlay component for displaying WebGPU performance metrics.
- Refactored TerritoryLayer to utilize new shader management for territory rendering.
- Updated shaders to support new parameters for enhanced visual effects.
- Removed deprecated territory border mode settings from UserSettingModal and SettingsModal.
- Enhanced GroundTruthData to manage new textures for owner indices and relations.
- Improved shader parameter handling in TerritoryRenderer and related classes.
This commit enhances the WebGPU rendering pipeline, providing better performance insights and visual fidelity through improved shader management and debugging capabilities.
Store defended influence in defendedStrengthTexture and sample it in territory render shader
Recompute defended strength on tick for state-updated tiles and for post-change dirty tiles, with full-map fallback when diffs are large
Pack defense posts by owner on GPU (owner offsets + posts buffer)
Remove old defended clear/update passes and epoch-based params
Replace epoch-based defended texture tracking with hard-clear approach for
improved reliability and performance. Remove complex dirty state tracking
and epoch incrementing logic in favor of direct hard clears before rebuilds.
Changes:
- Remove wasDefensePostsDirty tracking from TerritoryRenderer
- Replace numUpdates > 0 checks with hasStateUpdates boolean
- Hard-clear defended texture before restamping instead of epoch management
- Mark DefendedUpdatePass as dirty when rebuilding defended state
- Rebuild bind group in DefendedUpdatePass when missing, not just on buffer change
This eliminates potential transient mismatches where defended rendering
disappeared between rebuilds and simplifies the update pipeline.
Replaced tile sampling for terrain colors with direct extraction from the theme object, significantly improving performance. Updated shore, water, shoreline water, plains, highland, and mountain color computations to utilize theme properties, eliminating the need for tile searches. This change enhances efficiency in terrain color management while maintaining visual fidelity.
Modified the workgroup size in the state-update compute shader from 1 to 64 for improved parallel processing. Adjusted the dispatch logic in StateUpdatePass to calculate the correct number of workgroups based on the new size, enhancing performance during state updates. Removed unnecessary terrain parameter upload in TerritoryRenderer to streamline resource management.
Updated the terrain recomputation logic to trigger asynchronously, improving performance by allowing rendering to continue without blocking. This change ensures that the terrain will be ready for the next frame, which may result in displaying stale terrain for one frame but enhances overall rendering efficiency.
Refactor the monolithic TerritoryWebGLRenderer into a modular, extensible
architecture that separates ground truth computation from rendering passes.
This change also includes related improvements to game state management and
hover information handling.
WebGPU Architecture Refactor:
- Extract all shaders to external .wgsl files (no inlined shaders)
- Separate ground truth data management (GroundTruthData) from rendering
- Create pass-based architecture with ComputePass and RenderPass interfaces
- Implement compute passes: StateUpdatePass, DefendedClearPass, DefendedUpdatePass
- Implement render pass: TerritoryRenderPass
- Add TerritoryRenderer orchestrator with dependency-based execution ordering
- Add WebGPUDevice for device initialization and management
- Add ShaderLoader utility for loading .wgsl files via Vite ?raw imports
Performance Optimizations:
- Dependency order computed once at init (topological sort)
- Early exit checks at orchestrator and pass levels
- Bind groups rebuilt when textures/buffers are recreated
- Zero per-frame allocations (reuse command encoders and staging buffers)
Architecture Benefits:
- Easy to extend with new compute/render passes (borders, temporal smoothing, etc.)
- Clear separation between tick-based compute and frame-based rendering
- All shaders in external files for better maintainability
- Ground truth data computed once and reused by all passes
Related Changes:
- Add defended tile state support to GameMap (isDefended/setDefended)
- Expose tileStateView() for direct GPU state access
- Extract hover info logic to HoverInfo utility
- Remove TerrainLayer (terrain now rendered by WebGPU territory pass)
- Update GameRenderer to use transparent overlay canvas
- Add viewOffset() method to TransformHandler
Files:
- Deleted: TerritoryWebGLRenderer.ts (1217 lines), TerrainLayer.ts (77 lines)
- Added: 17 new files in webgpu/ directory structure
- Updated: TerritoryLayer.ts, GameRenderer.ts, PlayerInfoOverlay.ts,
GameMap.ts, GameView.ts, GameImpl.ts, TransformHandler.ts, vite-env.d.ts
## 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).