- Introduced getOffsetX() and getOffsetY() methods in TransformHandler for better access to offset values.
- Updated TerritoryLayer to manage attached canvases more effectively, ensuring proper synchronization of view dimensions and transformations.
- Improved rendering logic to prevent unnecessary updates when view parameters remain unchanged, enhancing overall performance.
- Added new metrics to PerformanceOverlay for tracking render submissions, noops, and various CPU timings.
- Updated TerritoryLayer to optimize user settings synchronization and reduce unnecessary refresh calls.
- Enhanced Worker components to collect and report detailed rendering performance metrics, including frame compute and territory pass timings.
- Improved WorkerTerritoryRenderer to profile rendering phases
- Introduced new properties to track view dimensions, scale, and offsets for improved rendering efficiency.
- Updated rendering logic to set frameDirty flag based on changes in view parameters and shader settings.
- Refactored methods to prevent unnecessary updates when view parameters remain unchanged.
- Improved error handling in shader setting to ensure smoother rendering operations.
- Introduced batching for turn messages in ClientGameRunner to reduce the frequency of worker communication
- Updated WorkerClient to manage pending turns and schedule flushes
- Enhanced Worker.worker.ts to process turn batches
- Added new TurnBatchMessage type for better organization of turn data sent between the main thread and worker.
- Improved PerformanceOverlay to display additional metrics related to worker performance and turn processing.
- Introduced WorkerMetricsEvent and SetWorkerDebugEvent to facilitate communication between the main thread and worker for performance monitoring.
- Enhanced ClientGameRunner to emit worker metrics and handle debug configuration updates.
- Updated PerformanceOverlay to display worker metrics and allow toggling of debug settings.
- Refactored Canvas2DRendererProxy and TerritoryRendererProxy to improve rendering performance and manage render cooldowns.
- Added profiling capabilities in Worker.worker.ts to track event loop lag, simulation delays, and message handling metrics.
- Add render_done worker message and tag render_frame with an id.
- Gate TerritoryRendererProxy/Canvas2DRendererProxy to one in-flight render (2s safety timeout) to prevent render queue buildup.
- Split roster vs palette dirtiness in GameViewAdapter and only force full relations rebuilds on roster/team changes.
- Only markRelationsDirty() on roster changes in WorkerTerritoryRenderer to avoid repeated expensive uploadRelations() while paused.
- GameViewAdapter: build from tileState/terrainData buffers and game
updates (players, defense posts, embargo/alliance) instead of Game +
TerrainMapData; add DefensePostUnit/PlayerLiteView and drop config().
- Worker: keep local renderTileState; tileUpdateSink receives packed
bigint and updates buffer + dirty queue; no terrain map load in worker.
- Proxies: send view size/transform only when changed, inline in
render_frame (optional viewSize/viewTransform); remove separate
set_view_size/set_view_transform messages.
- Simulation: remove main-thread RAF heartbeat loop; worker uses
scheduleSimPump() on heartbeat/addTurn to coalesce ticks.
- GroundTruthData: take defensePostRange at construction; Territory
renderer passes it through; remove runtime defensePostRange change check.
- GameRunner: tileUpdateSink(packedTileUpdate: bigint); add
hasPendingTurns().
- Introduced new properties and methods in GroundTruthData for handling relations, including `needsRelationsUpload`, `relationsDenseBySmallId`, and `pendingRelationsPairs`.
- Implemented logic to mark relations as dirty and upload relations conditionally based on changes in diplomacy.
- Updated Worker.worker.ts to synchronize relations updates with the renderer, optimizing performance by only refreshing when necessary.
- Enhanced WorkerTerritoryRenderer with methods to mark relations dirty, ensuring proper resource management during updates.
- Added support for terrain base RGBA values to optimize rendering.
- Refactored tile marking logic to utilize map dimensions for improved accuracy.
- Updated view size handling to prevent unnecessary canvas resizing.
- Introduced methods to rebuild terrain base and refresh terrain for better visual fidelity.
- Cleaned up initialization and cleanup processes for better resource management.
- 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:
Train spawning hot-path optimization (trade destination selection)
## Summary
This PR reduces per-tick overhead in train spawning by removing
temporary allocations and reducing work in the
destination-selection path.
The change focuses on `Cluster` trade destination lookup and how
`TrainStationExecution` picks a destination.
## What changed
### 1) Maintain a “trade-capable” station subset per cluster
`src/core/game/TrainStation.ts`
- `Cluster` now maintains:
- `stations`: all stations in the cluster (unchanged)
- `tradeStations`: maintained subset of stations that can act as trade
endpoints (`City` or `Port`)
- `tradeStations` is kept in sync in:
- `addStation()`
- `removeStation()`
- `clear()`
Impact:
- Trade queries no longer scan every station in the cluster; they only
scan `tradeStations`.
### 2) Add cheap eligibility helpers
`src/core/game/TrainStation.ts`
- `hasAnyTradeDestination(player)`:
- Fast early-exit check: returns as soon as it finds any eligible trade
destination.
- `randomTradeDestination(player, random)`:
- Picks a random eligible trade destination directly without
materializing an intermediate `Set`.
### 3) Use reservoir sampling for single-pass random choice
`src/core/game/TrainStation.ts`
`Cluster.randomTradeDestination()` uses reservoir sampling:
- Iterates `tradeStations` once.
- Maintains a running count of eligible stations (`eligibleSeen`).
- Replaces the selected station with probability `1/eligibleSeen`.
Properties:
- Uniform selection among eligible stations.
- One pass instead of “count then pick by index” (two pass).
- Allocation-free.
- Returns `null` when no eligible destination exists.
### 4) Update train spawning to avoid temporary sets
`src/core/execution/TrainStationExecution.ts`
- Previously: `spawnTrain()` called `cluster.availableForTrade()` and
then `random.randFromSet(...)`.
- This built a new `Set` on the hot path.
- Now:
- Early-exit via `cluster.hasAnyTradeDestination(owner)`.
- Destination via `cluster.randomTradeDestination(owner, random)`.
Net effect:
- Less per-tick work and no per-spawn temporary `Set` allocations.
## Why this helps
Train spawning happens frequently and can become a hot path in large
games / large rail clusters.
Avoiding repeated allocations and reducing work inside `tick()` helps
keep frame/update time predictable.
## notes
- Trade rules are unchanged (`tradeAvailable(player)` still gates
eligibility).
- Destination selection remains random-uniform over eligible
`City`/`Port` stations that satisfy `tradeAvailable(player)`.
- `TrainStationExecution` now avoids calling `spawnTrain()` entirely
when `spawnTrains` is falsy (it was already guarded inside).
## Please complete the following:
- [ ] I have added screenshots for all UI updates
- [ ] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [ ] I have added relevant tests to the test directory
- [ ] 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:
DISCORD_USERNAME
If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves#3126
## Description:
update & move reached_limit check
## 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:
wraith4081
---------
Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
## Description:
This PR reduces server/client tick CPU spent on territory cluster
maintenance by:
- Cutting redundant work in `PlayerExecution.removeClusters()`
(largest-cluster bounding box reuse, fewer allocations in
`isSurrounded()` and `removeCluster()`).
- Making `calculateBoundingBox()` allocation-free per tile by switching
from `gm.cell(tile)` to `gm.x(tile)`/`gm.y(tile)` (it now only allocates
the two result `Cell`s, instead of 1 per tile).
## Commits
- `51de0a1b` core: reduce PlayerExecution cluster overhead
- `6d9d85c5` core: avoid Cell allocations in PlayerExecution
isSurrounded
- `346f6a8c` core(util): speed up calculateBoundingBox by avoiding Cell
allocations
## Notes
- This PR is intended to be behavior-preserving; changes are limited to
hot-path micro-optimizations.
- Follow-up opportunity: `calculateClusters`/flood-fill is now the top
hotspot; further wins likely come from reducing traversal work or
caching.
## Please complete the following:
- [ ] I have added screenshots for all UI updates
- [ ] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [ ] I have added relevant tests to the test directory
- [ ] 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:
DISCORD_USERNAME
## Description
Optimize border-tile maintenance in `GameImpl` to reduce per-conquest
overhead.
Border tiles are updated whenever ownership changes; this PR trims
allocations and avoids unnecessary iteration in the hot path.
## Changes
- `src/core/game/GameImpl.ts`
- `updateBorders(tile)` no longer allocates an array of tiles; it
updates the changed tile and its 4-neighbors directly via
`forEachNeighbor`.
- `calcIsBorder(tile)` no longer calls `neighbors(tile)` / loops an
array; it checks the four cardinal neighbors via `x/y` bounds and
`ownerID`.
## Affected Functions
- `GameImpl.updateBorders(tile: TileRef)`
- `GameImpl.calcIsBorder(tile: TileRef): boolean`
- Call sites impacted by behavior/perf:
- `GameImpl.conquer(owner, tile)`
- `GameImpl.relinquish(tile)`
## Please complete the following:
- [ ] I have added screenshots for all UI updates
- [ ] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [ ] I have added relevant tests to the test directory
- [ ] 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:
DISCORD_USERNAME
## Description:
Reduce FX layer rendering cost by:
- Updating the offscreen FX buffer only when needed (and clearing it
once when FX ends).
- Drawing only the visible portion of the FX buffer to the main canvas
(viewport culling).
- Reusing `TransformHandler.screenBoundingRect()` as the single source
of truth for viewport bounds.
-
## Changes
- `FxLayer`:
- Track buffered frames and skip work when there are no active FX.
- Use `performance.now()` for refresh timing.
- Draw only the visible map rect (clamp + small pad) instead of blitting
the full map-sized FX canvas.
- Compute the visible rect via `TransformHandler.screenBoundingRect()`.
- `GameRenderer`:
- Thread `TransformHandler` into `FxLayer` construction.
## Please complete the following:
- [ ] I have added screenshots for all UI updates
- [ ] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [ ] I have added relevant tests to the test directory
- [ ] 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:
DISCORD_USERNAME
## Description:
Maybe for v29.
In the 5M starting gold modifier games you can conquer a inactive player
(spawned but didn't do anything) and get their 5M gold.
Huge unfair advantage.
I think that even without the starting gold modifier you should not get
the gold of inactive players because its unfair.
I identify inactive players (spawned but didn't do anything) by checking
the attack stats.
I added a translation for the displayMessage "Conquered {name}, received
{gold} gold". Why was that not translated?
I added a new message "Conquered {name} (Inactive player, received no
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:
FloPinguin
---------
Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
## Description:
Added source for join context
## 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:
Explanation of the bot farming strategy in the discord:
https://discord.com/channels/1359946986937258015/1359949371956789289/1460928540575928478
"the result is that a player can build unlimited factories for 125 000
gold discount, trade with themselves with each train being worth 50 000
gold. First the 25 000 for neutral trade and then another 25 000 when
the bot is harvested."
"If you have a minute and ally people around you it should be trivial to
get 10 of both cities and factories for 1.25mil"
It's debatable if we want to let people do that (close this PR) or see
it as an abusive mechanic.
Here is the fix, bots try to delete all structures now. You can simply
retake them to stop the deletion:
https://github.com/user-attachments/assets/ac1ca846-50bd-42fa-8e25-5ac25a6d627e
## 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
Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
## Description:
It is possible to hit your teammates while throwing a nuke onto water or
enemies.
This PR blocks the nuking entirely if you would hit a teammates
structure. Because they are valuable.
Feature requested by Wonder :)
https://github.com/user-attachments/assets/448a3444-cc3d-4e76-acaf-595decab1634
## 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
---------
Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
## Description:
Currently only the master process sends public lobby updates to clients.
This is not scalable since it could overload the master process.
In this PR, the master uses IPC to send public lobby info to all
workers. Then clients connect to a random worker to get public lobby
updates via websocket. This way clients never connect directly to the
master websocket.
The flow looks like this:
Every 100ms:
1. Master schedules a public game on a random worker if new games are
needed
2. Master broadcasts public lobby info to all workers (all public games
& num clients connected to each game)
3. Each worker responds to that update with the number of clients
connected to its own public games
4. Master then updates its public lobby state so it knows how many
clients are connected to each public game
## 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
If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves #(issue number)
## Description:
add import, somehow missing??
## 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:
- Add getClientIDForGame function to Auth.ts that generates and stores a
consistent clientID per gameID using sessionStorage
- Update HostLobbyModal to use getClientIDForGame for lobby creation
- Update Matchmaking to use getClientIDForGame when joining games
- Update PublicLobby to use getClientIDForGame when joining lobbies
This enables reconnection support by ensuring the same clientID is used
when rejoining a game session.
## 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
---------
Co-authored-by: Evan <evanpelle@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
## Description:
Prevents translated button text from overflowing its container by
constraining layout and clipping excess content.
before
<img width="234" height="133" alt="スクリーンショット 2026-02-04 6 46 35"
src="https://github.com/user-attachments/assets/2cfe4f3e-ac5c-42d0-8175-76ca53fa3b1b"
/>
after
<img width="189" height="135" alt="スクリーンショット 2026-02-04 6 46 46"
src="https://github.com/user-attachments/assets/e2fd2439-6cd3-4831-86b2-28a374bc7ba4"
/>
## 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:
aotumuri
## Description:
fix: validate base username length separately from clan tag)
## 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:
Hide the host badge when joining public lobbies
before
<img width="802" height="635" alt="スクリーンショット 2026-02-03 21 45 45"
src="https://github.com/user-attachments/assets/2b8bd5a0-3023-4bd3-9042-1bbd0649e499"
/>
after
<img width="668" height="702" alt="スクリーンショット 2026-02-03 21 44 00"
src="https://github.com/user-attachments/assets/65eb9b66-99b0-46ce-a786-55974a443010"
/>
## 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:
aotumuri
## Description:
Flag selector country names no longer truncate with ellipses; they wrap
normally for readability.
before
<img width="561" height="182" alt="スクリーンショット 2026-02-03 22 21 01"
src="https://github.com/user-attachments/assets/965ab93e-e10f-42a0-8771-84f042c31e22"
/>
after
<img width="582" height="217" alt="スクリーンショット 2026-02-03 22 21 11"
src="https://github.com/user-attachments/assets/9c6aa840-5af7-46a7-9f54-58d78e021304"
/>
## 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:
aotumuri
Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
## Description:
Aligned the LeaderboardModal background styling with other modals (e.g.,
HelpModal). Uses the same inline background/blur/border treatment to
improve visual consistency.
before
leaderboard
<img width="848" height="657" alt="スクリーンショット 2026-02-03 22 07 58"
src="https://github.com/user-attachments/assets/b31c0754-00e0-4248-9e2d-97869df99acb"
/>
other
<img width="921" height="686" alt="スクリーンショット 2026-02-03 22 08 07"
src="https://github.com/user-attachments/assets/e84ab4e0-64b1-490f-8d93-d745f4eabbbb"
/>
after
leaderboard
<img width="852" height="650" alt="スクリーンショット 2026-02-03 22 08 20"
src="https://github.com/user-attachments/assets/6f4a4073-4d6e-4ca8-8ef1-fb08240a5e6b"
/>
other
<img width="857" height="685" alt="スクリーンショット 2026-02-03 22 08 35"
src="https://github.com/user-attachments/assets/367607f7-f665-4e1e-9c34-4566d2b68570"
/>
## 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:
aotumuri
## Description:
Join Lobby Modal will show:
Crowded modifier
Enabled
## 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