mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-07 20:26:01 +00:00
0c83444dc95b7732892526e98e0dcb0d73cbc92f
4159 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0c83444dc9 |
feat(client): custom plutonium amount purchase in store (#4515)
## Summary Adds a **custom plutonium amount** purchase option to the Store, wiring the client to the new `POST /stripe/create-custom-currency-checkout` endpoint (infra [PR #400](https://github.com/openfrontio/infra/pull/400)). Players can buy any integer **20–2000** plutonium, priced inline server-side at **20 plutonium = \$1.00**. A new `<custom-currency-card>` renders as the **last tile in the Store "Packs" tab** — a slider + number input with a live price and a "Buy for \$X.XX" button that redirects to Stripe Checkout. ## Changes | File | Change | |---|---| | `src/client/Api.ts` | New `createCustomCurrencyCheckout(hardAmount)` — POSTs `{ hardAmount, hostname }`, returns the Stripe `url` (mirrors `createCheckoutSession`). | | `src/client/components/CustomCurrencyCard.ts` | New `<custom-currency-card>` component: plutonium slider + number input, live price, clamps to 20–2000 integer client-side, redirects to Stripe on buy. | | `src/client/Store.ts` | Renders the card after the fixed packs in the "Packs" tab; drops the now-unreachable "no packs" empty state. | | `src/client/Main.ts` | Handles `type=custom_currency` in the `#purchase-completed` redirect (success alert + strip hash; balance refetches from `/users/@me` on load). | | `src/client/LangSelector.ts` | Registers `custom-currency-card` for translation-reload re-render. | | `resources/lang/en.json` | Adds `store.buy_for`, `store.custom_amount`, `store.custom_currency_purchase_success`; removes the orphaned `store.no_packs`. | ## Notes - **No catalog product** — unlike currency packs, the custom amount is priced inline server-side, so the card is standalone (not a `resolveCosmetics` item). - **Async credit** — no optimistic balance bump; the balance reflects the Stripe webhook via `/users/@me` on return, matching the currency-pack flow. - Server is authoritative on bounds/rate; client-side clamping is UX-only. ## Testing - Full suite green: **1842 tests / 153 files**; `tsc --noEmit` and eslint clean. - Manually verified in the running app (headless Chromium): card renders in the Packs tab; amount/price stay in sync across slider + number field; clamping confirmed (5000→2000/\$100.00, 5→20/\$1.00, 750→\$37.50 — matches the pricing table). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
46b9fe88bb |
fix: survive host modal close during listed-lobby auto-start (#4514)
Follow-up to the listed-lobby auto-start feature (`2a82b01e3`, on main). ## Bug When a listed lobby auto-started (and in some paths on manual start), the game began server-side but a player got ejected back to the menu before it loaded. Root cause: `BaseModal.close()` navigates via `showPage()`, which **force-closes whichever page-modal is currently visible**. During the game-start transition that is the *other* lobby modal — closing the join modal first cascade-closed the host's modal with `leaveLobbyOnClose` still armed (host disconnected → host-left teardown killed the game); reversing the order just moved the victim to the joiner. No close order fixes both sides. ## Fix - **Disarm both lobby modals before closing either** in the prestart transition (`disarmLeaveOnClose()` on `HostLobbyModal` and `JoinLobbyModal`), so no cascade order can disconnect anyone mid game-start. - Both modals re-arm `leaveLobbyOnClose` in `onOpen` instead of `onClose`'s state reset, so once disarmed no later cascade close can re-arm it until the modal is reopened. - `HostLobbyModal.closeWithoutLeaving()` (pattern `JoinLobbyModal` already used), called explicitly instead of the generic modal-close loop. - **Server hardening:** `handleClientDisconnect` bails on `hasStarted()` (which includes prestart) instead of raw `_hasStarted`, so host socket churn during the lobby → game transition can never tear down a starting game regardless of client behavior. Regression test included. ## Verification Headless end-to-end (two browser contexts, host + joiner, `leave-lobby` stack-trace tripwire armed on both pages): - **Manual start:** host and joiner both receive prestart/start and enter the game; zero `leave-lobby` dispatches. - **Auto-start:** same, with nobody interacting after listing. Also confirmed manually in a real browser. The auto-start deadline was temporarily 30s during testing and is restored to 5 minutes in the final commit. Full suite green (1843 + 161), tsc/eslint/prettier clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
2a82b01e30 |
feat: auto-start listed lobbies after 5 minutes
Hosts can't sit on a public listing: setListed(true) records listedAt, and once listedAt + HOSTED_LOBBY_AUTO_START_MS passes, GameManager's tick arms the normal start countdown (same path as the host's Start button, honoring startDelay). Cancelling the countdown re-arms on the next tick; unlisting cancels the deadline and relisting starts a fresh one. Duplicate setListed(true) calls don't extend it. The deadline rides to the host as autoStartAt in lobby info, rendered as an amber countdown next to the Public/Private switch in the host modal header (hidden once the real start countdown takes over). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5d21be826d |
feat: subscriber-hosted public lobby listing (#4480)
Part of #4040 (v1 scope: listing + browser + per-subscriber limit; custom lobby name/description left for a follow-up). ## What Subscribers can toggle their **private lobby** to be **publicly listed**; a browsable **"Open Lobbies"** list appears in the Join Lobby modal. Hard limit of **one listed lobby per subscriber**, enforced cluster-wide. ## How **Semantics** — a listed lobby stays `GameType.Private`: the host keeps full control and starts the game manually; the toggle only controls visibility. The `listed` flag lives on `GameServer` (not `GameConfig`), so it cannot be smuggled in through `update_game_config` and never touches core/sim/records. **Distribution** — reuses the existing public-lobby pipeline end to end: a new `"hosted"` `PublicGameType` bucket flows worker → master IPC → `/lobbies` websocket → `PublicLobbySocket`. Master scheduling now iterates only `SCHEDULED_PUBLIC_GAME_TYPES` (`ffa`/`team`/`special`), so it never sets countdowns on or schedules replacements for hosted lobbies. Lobbies delist automatically when the game starts/fills/dies (phase change). The broadcast fingerprint now includes browser-visible config, so host edits (map/mode) refresh the list even though the gameID doesn't change. **Gating** — new authenticated endpoint `POST /api/game/:id/listing`: - creator-only (403), private + not-started only (409) - fresh subscription check via server-side `getUserMe` using the shared `hasActiveSubscription()` helper (`active`/`trialing`); skipped in `GameEnv.Dev` (same precedent as Turnstile) so it's testable locally - one-lobby-per-creator (409): a SHA-256 hash of the creator's persistentID rides worker↔master IPC (`PublicGameInfo.creatorID`); the master dedupes as a race backstop. The hash — and host-only config (whitelist, name reveals) — are **stripped from every client payload** (broadcast + primed snapshot). **Client** — subscriber-gated "List lobby publicly" toggle in the host modal (server rejection reverts the toggle and shows a translated message); "Open Lobbies" rows (map, mode, player count) in the Join Lobby modal that reuse the existing private-join flow. **Compat** — `PublicGames.games` is now a `partialRecord`, so newer clients tolerate servers that don't send every bucket. Note: already-open old clients will fail to parse broadcasts containing the new `hosted` key until refreshed (closed Zod enum) — same class of break as previous wire-schema changes. ## Testing - `tests/server/HostedLobbyListing.test.ts` (15 tests): listed-lobby filtering, flag not settable via config intent, master aggregation + creator dedupe + no scheduling of hosted, creatorID stripping (broadcast + primed snapshot), `creatorHasListedLobby` (broadcast + local), fingerprint refresh on config change - `hasActiveSubscription` cases in `ApiSchemas.test.ts`; hosted counts-delta patch in `LobbySocket.test.ts` - Full suite green (1723 + 141 tests), tsc/eslint/prettier clean - **E2E in the real app** (headless Chromium, two browser contexts): host lists lobby → appears in second browser's Join Lobby list (creatorID absent from payload) → join succeeds (2 players in lobby) → same creator's second lobby rejected 409 with toggle revert → unlist removes it from a fresh browser's list. Curl negatives: missing auth 400, bad token 401, non-creator 403, missing game 404, bad body 400. ## Known follow-ups - Custom lobby name/description in the browser (needs the censor pipeline) — rest of #4040 - A listed lobby whose host closes the tab stays advertised indefinitely (an empty private lobby never leaves the Lobby phase) — pre-existing lifecycle, now more visible; consider delisting on creator disconnect 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
22c873cf55 |
perf(client): tick-dispatch timing harness + main-thread tick optimizations (late-game p95 −65%) (#4512)
## Problem
Every 100 ms the main thread's worker `onmessage` callback processes a
full game tick (`gameView.update` → `webglBuilder.update` →
`renderer.tick`). At 60 fps this competes with the 16.7 ms frame budget,
and on the Giant World Map it takes several ms — frame drops on low-end
hardware.
## Harness (`npm run perf:client-tick`)
Headless-Chromium harness that times every worker→main `game_update`
dispatch on the main thread, with structured-clone deserialization
measured separately from the handler body (via a
`Worker.prototype.addEventListener` wrapper installed as a page init
script — no product-code changes). It reports windowed distributions,
captures `.cpuprofile` files at chosen ticks, writes raw samples and an
end-of-run screenshot. `AnalyzeCpuProfile.ts` breaks a profile down by
inclusive time under the dispatch subtree.
Init scripts are passed as **strings**: tsx compiles function-form init
scripts with esbuild `keepNames`, whose injected `__name` helper doesn't
exist in-page and silently kills the game worker setup.
## Baseline (Giant World Map, 400 bots, headless)
Dispatch handler ms — cost **grows with game progression**:
| window | mean | p50 | p95 | max |
|---|---|---|---|---|
| tick 506 | 2.22 | 2.20 | 3.40 | 5.00 |
| tick 1506 | 2.60 | 2.00 | 7.00 | 10.40 |
| tick 2000 | 2.67 | 1.90 | **8.70** | **12.70** |
Deserialization is negligible (0.12 ms mean). CPU profiles attributed
the growing tail to the leaderboard's once-per-second refresh: its
Max-troops column calls `config().maxTroops(p)` for **all ~508
players**, and `PlayerView.units()` scanned **every unit in the game**
per call — O(players × units), growing as units accumulate.
## Round 1 — algorithmic fixes
- **GameView**: new `unitsOwnedBy(smallID)` — an active-units-by-owner
index built lazily at most once per tick. `PlayerView.units()` reads its
own units from it: O(own units) instead of O(all units). Also speeds up
unit display, player panel, and buildables queries.
- **NamePass.updateNames**: reads player state directly from the
caller's map by smallID instead of rebuilding three lookup maps per
tick; skips the slot-assignment sweep once every player has a slot.
After (same map, same spawn tile):
| window | mean | p50 | p95 | max |
|---|---|---|---|---|
| tick 506 | 2.12 | 2.00 | 3.10 | 5.20 |
| tick 1506 | 1.86 | 1.80 | 2.90 | 4.30 |
| tick 2000 | 1.74 | 1.60 | **2.40** | **4.70** |
Late-game p95 −65% (8.7 → 2.4 ms), worst dispatch −63% (12.7 → 4.7 ms),
and per-dispatch cost no longer grows with game progression. The
leaderboard disappeared from the dispatch profile entirely.
## Round 2 — allocation churn + time slicing
Aimed at GC pauses and low-end CPUs; measures flat vs round 1 on a fast
machine, as expected:
- **`FrameData.changedTiles`** is now the plain tile-ref array GameView
already builds instead of a per-tile `{ref, state}` object copy — heavy
battle ticks allocated tens of thousands of objects per tick for a
`state` field that was always 0. `TilePair` removed; `TerritoryPass`
buckets refs synchronously, so the live reference is safe.
- **`UnitView.lastPos`** is only re-sliced when a move actually appended
a position — the unconditional `slice(-1)` allocated an identical
1-element array per unit per tick, including for structures that never
move.
- **`NamePass.updateNames`** refreshes slots round-robin, a quarter per
tick — the full per-player diff pass spreads over ~400 ms, under the
existing 500 ms troop-text cadence; positions lerp continuously. Unnamed
slots and snap passes (seeks) are always processed so nothing pops in
late. Dispatch share: 17% → 13%.
Not sliced on purpose: tile ingest and frame upload need a consistent
per-tick snapshot (stale `GameMap` reads would leak into hover queries,
minimap, attack targeting) — a correctness risk not worth ~1 ms while
the worst dispatch already fits in a quarter of the frame budget.
## Verification
- `npx tsc --noEmit`, eslint clean; full suite green (1929 tests)
- 6 new GameView tests cover the owner index (grouping, inactive
exclusion, ownership capture, death, type filtering, copy semantics);
changedTiles tests updated to the ref-array contract
- Headless end-of-run screenshots verified after each round: leaderboard
Max-troops values, map names + troop counts + flags all render correctly
(including with name slicing active)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
571f58440d |
perf(client): main-thread memory harness + drop three map-sized render buffers (-23%) (#4511)
## Summary Follow-up to #4507, moving the memory-footprint campaign to the **main thread** (client). Two parts: a headless browser measurement harness, and a first optimization round that cuts the main-thread live heap on Giant World Map by **23%** (166 → 128 MB at tick 2000). ## Part 1 — `npm run perf:client-mem`: headless main-thread memory harness Drives a real singleplayer game in headless Chromium and measures the **page's isolate only** (the core sim worker is a separate CDP target): - Starts its own vite dev server on a private port (default 9017) so it always measures the current checkout. - Double-forced-GC checkpoints every `--window` ticks: JS heap, ArrayBuffer backing-store bytes (`Runtime.getHeapUsage`), DOM nodes, listeners, ticks/s. - `--snapshot-at <ticks>` writes V8 heap snapshots, analyzable with the retainer/summary tools from #4507. - Spoofs the unmasked WebGL renderer string via an init script so the software-GL gate (#4324) admits SwiftShader — no game code touched; rendering still runs software (hence the rAF throttle). - End-of-run screenshot as a rendering sanity check. Baseline (Giant World Map, 400 bots, 12,000 ticks): ~176 MB live, of which ~116 MB is **static per-tile buffers** allocated up front for the 8M-tile map — flat during play, no leaks. ## Part 2 — drop three map-sized render-layer buffer copies | Buffer | Before | After | |---|---|---| | `TrailPass.cpuTrailState` | 15.3 MB copy | **deleted** — dead code; every upload entry point sets the live reference to TrailManager's array | | `RailroadPass.cpuRailroadState` | 15.3 MB across 2 arrays | references `RailroadCache.railroadState` (stable identity, mutated in place) | | `RailroadPass.cpuGhostRailState` | ↑ | sparse `Map<ref, value>`; preview diffs applied as per-texel `texSubImage2D` writes (path-sized work instead of a full 8 MB texture upload per build-preview mouse move) | | `TerrainPass` + `MapRenderer` terrain bytes | 7.6 MB (one buffer, two retainers) | `terrainSource()` provider — re-bakes (theme change, context restore) regenerate from the live game map, which already reflects water-nuke conversions | Tick-2000 snapshot comparison (giant world, 400 bots): **166.4 → 128.4 MB**. ## Verification - `tsc --noEmit`, eslint, full test suite (1924 tests) pass. - 2000-tick headless giant-world game after the change: no GL pageerrors, end-of-run screenshot renders terrain/territory/borders/names correctly, sim speed unchanged (~5 ticks/s headless). - Ghost-rail ops flush before the zoom-fade early-return, so the op queue can't grow while previewing at low zoom. - WebGL context restore recreates all passes fresh and the owner re-uploads state (existing `onContextRestored` path), consistent with the new reference-based buffers. Note: heap snapshots in `tests/perf/output/` are gitignored; the numbers above are from runs recorded in the PR discussion. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
36aa38bcd0 |
Multiple terrain changes and fixes for maps (#4502)
## Description: Multiple changes for maps: Mississippi - Fix some landlocked lakes, near the main coast. Players would sometimes accidentally put a port in the dead lakes North America - Re-add the Azores and Cape verde (accidentally removed when the map generator got updated to delete small islands) Africa - Re-add Ascencion and Saint Helena Islands, same case as NA Danish Straits - replace german empire flag , as agreed in the leads channel Giant World Map - Coast fixes, including adding Suez Canal which greatly affected trade routes Great Lakes - fix landlocked lake Two Lakes - New Terrain combining both old and recent versions World - Add strait of malacca ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: tri.star1011 |
||
|
|
eae2be6458 |
feat(store): confirm plutonium and caps purchases before charging (#4510)
## Summary Fixes #4218 Currency purchases (Plutonium and Caps) fired immediately on click with no confirmation. This adds a confirmation modal — reusing the existing `confirm-dialog` Lit component — that gates every currency purchase behind an explicit "Confirm". There were two paths that could trigger a currency purchase, and both are now gated: - **The Plutonium / Caps price buttons** — `PurchaseButton` no longer calls `onPurchaseHard`/`onPurchaseSoft` directly; it opens a `confirm-dialog` ("Buy {item} for {amount} {currency}?", warning variant) and only runs the purchase on confirm. Cancel / backdrop click dismisses. - **Whole-card click** — `CosmeticContainer` auto-fires the purchase when there's exactly one payment option, which bypassed the button entirely for currency-only items. That path now delegates to the purchase button's new `requestCurrencyPurchase()` so it goes through the same dialog. The existing purchase flow (busy guard, loading overlay, insufficient-currency dialog) is unchanged and runs after confirmation. Dollar purchases are untouched (they go through Stripe checkout, which is its own confirmation step). New i18n keys: `store.confirm_purchase_title`, `store.confirm_purchase_body` (en.json only, per Crowdin convention). ## Test plan - [x] ESLint, `tsc --noEmit`, Prettier pass - [ ] Manual check in staging: click a Plutonium or Caps price button → dialog appears with the right currency name and amount; confirm purchases, cancel doesn't - [ ] Manual check: click the card body of a currency-only item → same dialog (not an instant purchase) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7fa81c6bb9 |
perf: reduce core live-memory footprint by 45% on large maps (#4507)
## Summary
Reduces the simulation's steady-state memory footprint. On Giant World
Map at 20 game-minutes (12 000 ticks, 400 bots, seed `perf-default`),
live memory after a full GC drops **293 MB → 161 MB (−45%)**; unforced
peak heap drops **326 MB → 165 MB**. The simulation also runs ~10%
faster (85 → 94 ticks/s). The final game-state hash is **bit-identical**
(`57830793797434300`) — no behavior change.
## Measurement (first commit)
The full-game perf harness gains a footprint mode:
- `--footprint` — forces a full GC at every `--window` boundary and
records the live heap / ArrayBuffer / RSS curve across the game
(requires `NODE_OPTIONS=--expose-gc`).
- `--snapshot-at 0,2000,12000` — writes V8 `.heapsnapshot` files at
chosen ticks.
- `HeapSnapshotRetainers.ts` — attributes every heap node to its nearest
meaningfully-named retainer (e.g. `PlayerImpl._tiles`), plus prints
retainer chains for all nodes ≥128 KB. `HeapSnapshotSummary.ts` is a
streaming fallback for snapshots too large to `JSON.parse`.
Baseline attribution at tick 12 000: player `_tiles`/`_borderTiles` Sets
**83 MB**, GameMap `refToX`/`refToY` lookup tables **38 MB**, two
duplicate 30.5 MB visited-scratch arrays, trade-ship stepper paths **15
MB**, a construction-only flood-fill queue **9.5 MB**.
## Optimizations
**Map-sized buffers (second commit):**
- `GameMap.x()/y()` compute `ref % width` / `(ref / width) | 0` instead
of reading two per-tile Uint16 tables (−38 MB). The arithmetic is
cheaper than the tables' random-access cache misses — this is where the
speedup comes from.
- `PlayerExecution` and `SpatialQuery` each kept their own per-game
generation-stamped visited `Uint32Array`; both now share one via
`TileTraversalScratch` (−30 MB).
- `PathFinderStepper` stores numeric paths as `Uint32Array` (half the
bytes; steppers hold their full path for a unit's whole journey).
- `ConnectedComponents` frees its flood-fill queue after `initialize()`.
**Player tile sets (third commit):**
- New `TileSet`: insertion-ordered set of tile refs backed by a dense
`Uint32Array` plus an open-addressing hash index — ~12 bytes/element vs
~34 for a native `Set<number>`. Deletes tombstone; compaction is
deferred while iteration is in progress so positions never shift under
an iterator.
- Iteration semantics match `Set` exactly (insertion order, entries
added mid-iteration visited, deleted ones skipped, delete+re-add moves
to end) — the simulation relies on this order for determinism, and the
unchanged hash confirms it.
- `Player.borderTiles()` now returns `ReadonlyTileSet` (a native `Set`
still satisfies it structurally); `GameRunner.playerBorderTiles` copies
into a real `Set` since that result crosses the worker boundary via
structured clone.
## Footprint curve (giant world map, live MB after forced GC)
| checkpoint | before | after |
|---|---|---|
| spawn end | 20 + 100 buf | 20 + 55 buf |
| tick 6301 | 119 + 161 buf | 29 + 127 buf |
| tick 12301 | 130 + 161 buf | 32 + 129 buf |
## Validation
- Final hash `57830793797434300` identical across baseline / round 1 /
round 2 runs (12 000 ticks).
- Full suite passes (1798 + 126 tests), including new `TileSet` tests:
order semantics, mutation-during-iteration parity with `Set`, tombstone
compaction, and a 20 000-op randomized differential test against native
`Set`.
- Runs recorded in
`tests/perf/output/footprint-{baseline,round1,round2}-giant.txt`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
b0f85c5739 | fix: keep Playwire bottom rail below the in-game HUD so it can't cover the control panel | ||
|
|
66063d6178 |
feat(doomsday-clock): decay warships alongside troops for doomed sides (#4499)
## Description: Follow-up to #4469. The Doomsday Clock drains a doomed side's troops but leaves its navy untouched, so a coastal or island turtle can sit below the bar indefinitely on warship defense, exactly the stall the clock is meant to break. This decays the warships of a flagged (sub-threshold, non-leader) side on the same ramp as its troops: - Each warship loses a percentage of its (veterancy-adjusted) max health per second, reusing `doomsdayClockDrain`, so the fleet and the army bleed in lockstep and reach zero together (~55s from full at the default rate). - Destruction passes **no attacker**, so it routes through `UnitImpl.delete` as an environmental loss: no kill credit, no boat-destroy stats, no veterancy granted. Scoring integrity is preserved. - Healing is suppressed for a flagged owner (`WarshipExecution.healWarship` early-returns), so the decay actually sinks the fleet instead of being out-healed at a port. Inert when the mode is off, since the mark is never set. - The leader's fleet is spared, same as its troops. No new config: warships reuse the existing drain curve. No HUD change, since warships count as part of the side's forces alongside troops. Tested: 4 new unit tests (same-ramp decay, no-kill-credit destruction, leader spared, warn-window grace), the full `DoomsdayClockExecution` and `Warship` suites, the whole test suite (1784 passing), `build-prod`, and a headless full-game sim run (resolves cleanly with the decay live, deterministic). |
||
|
|
2a1381b41e |
feat(doomsday-clock): show the zone readout to spectators and replay viewers (#4497)
## Description: Follow-up to #4469. The Doomsday Clock panel hides itself unless the viewer has a living player, so spectators, replay viewers, and eliminated or not-yet-spawned players see nothing, even though the clock is the most useful thing to watch in those cases. This shows the panel whenever the mode is on and there's no winner yet: - **Live player:** unchanged. Full personal readout (your-share line, Stable/Unstable/Collapsing status, danger pulse). - **Spectator / replay / eliminated / not-spawned:** zone-only readout. The rising threshold bar and the wave countdown ("Rising to X%", "Final X%", "Next wave X% in mm:ss"), with no personal line and no false danger pulse. One gate drives it: `live = me && me.isAlive()`. The zone math was already player-independent, so nothing new there. Tested: `tsc --noEmit`, ESLint, Prettier, the `DoomsdayClockExecution` suite (28/28), and `build-prod`, all clean. |
||
|
|
20c81ca5f6 |
perf: cut core-sim GC churn another 36% (75% cumulative) (#4498)
## Summary Round 3 of GC-churn reduction (follow-up to #4494 and #4496). All changes are **behavior-preserving** — final game-state hash unchanged on three seeded runs. ### Changes | Site | Change | Churn target | |---|---|---| | `MiniMapTransformer` | Path upscaling works in pure numeric coordinates and emits main-map `TileRef`s directly, replacing three intermediate `Cell`-object arrays per path (cell path → scaled path → smoothed path → final map). Identical arithmetic, so identical rounding and identical tiles. | ~7.1 GB | | `ShoreCoercingTransformer` | Reused neighbor buffers instead of `neighbors()` arrays; no per-call `{water, original}` objects. Tie-breaking preserved (helpers share the unified N,S,W,E order since #4495). | ~1.5 GB | | `diffPlayerUpdate` | Allocation-free all-equal fast path. Runs per player per tick and usually returns `null` (gold/troops/tiles travel via packed arrays), but previously allocated the diff object + a closure first. Field list matches the diff exactly. | ~2.6 GB | | Large-`Set` iteration | `for..of` over a `Set` allocates an iterator-result object per element — significant on 100k-tile border sets. `calculateClusters`, `calculateBoundingBox` (indexed fast path for arrays too) and `getAttackFrontTiles` (also dropped its `neighbors()` arrays) now use `Set.forEach`. | ~3.6 GB | ### Results (Giant World Map, 400 bots, 12,000 ticks, seed `perf-default`) | Metric | Before | After | vs. original (pre-#4494) | |---|---|---|---| | Sampled allocations (incl. collected) | 37.8 GB | **24.1 GB (−36%)** | 97.7 GB (**−75%**) | | Ticks/sec | 82 | **88** | 66 (+33%) | | Mean / p99 tick | 12.2 / 36.0 ms | 11.3 / 34.8 ms | 15.2 / 49.9 ms | | Peak heap | 762 MB | 529 MB | 758 MB | ## Determinism Final hash unchanged on all three reference runs: - Giant World Map 12,000 ticks: `57830793797434300` ✓ - Giant World Map 2,000 ticks: `55125379638382860` ✓ - World 1,800 ticks: `32337437717390864` ✓ ## Test plan - [x] Full suite green (1,906 tests; the `getAttackFrontTiles` test stub gained a `neighbors4` implementation to match the real interface) - [x] Hash equality on 3 seeded headless runs (2 maps) - [x] Before/after 20-min GC benchmarks 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9e9c608053 |
perf: cut core-sim GC churn another 36% (61% cumulative) (#4496)
## Summary Round 2 of GC-churn reduction, attacking the next tier of allocation sources found by the profiling harness from #4494. All changes are **behavior-preserving** — the simulation is bit-identical (final hash unchanged on three seeded runs). ### Changes | Site | Change | Churn target | |---|---|---| | `Player.units()` / `Game.units()` | Rest parameter → fixed-arity + array overloads (`units()`, `units(types[])`, `units(t1, t2?, t3?)`). The rest array was allocated on **every call** of one of the hottest functions in the sim. Spread call sites (`units(...Structures.types)`) now pass the array directly. `GameImpl.units()` builds one flat array instead of `Array.from().flatMap()` per-player intermediates. | ~18 GB | | `PlayerExecution` cluster flood fill | Results are plain `TileRef[]` in mark order instead of `Set<TileRef>` — the generation-stamped visited array already deduplicates, and consumers only iterate/measure. DFS stack reused across fills. | ~3.7 GB | | `SpatialQuery.bfsNearest` | Fused generation-stamped BFS with per-game scratch buffers (`WeakMap`-keyed, same pattern as `PlayerExecution`) instead of materializing a `Set` of the entire search area per query. Identical traversal and tie-breaking. | ~2.2 GB | | `NationWarshipBehavior` ship tracking | Single-pass loops instead of `filter().forEach()`; dropped defensive `Array.from(set)` copies (deleting the current entry while iterating a `Set` is well-defined). | ~1.4 GB | ### Results (Giant World Map, 400 bots, 12,000 ticks ≈ 20 game-min, seed `perf-default`) | Metric | Before | After | vs. pre-#4494 | |---|---|---|---| | Sampled allocations (incl. collected) | 59.2 GB | **37.8 GB (−36%)** | 97.7 GB (**−61%**) | | GC count / total pause | 1,076 / 1,830 ms | 772 / 1,442 ms | 1,682 / 3,313 ms | | Ticks/sec | 73 | **82** | 66 (+24%) | | Mean / p99 tick | 13.6 / 39.2 ms | 12.2 / 36.0 ms | 15.2 / 49.9 ms | `units()` no longer appears in the top-30 allocator list at all. The remaining leaders (possible round 3): the minimap pathfinding `Cell` pipeline (~8.5 GB), `diffPlayerUpdate`/`toFullUpdate` per-tick serialization (~4.6 GB), and iterator allocations (~3.3 GB). ## Determinism Final game-state hash unchanged on all three reference runs: - Giant World Map 12,000 ticks: `57830793797434300` ✓ - Giant World Map 2,000 ticks: `55125379638382860` ✓ - World 1,800 ticks: `32337437717390864` ✓ ## Test plan - [x] Full suite green (1,905 tests), including updated `units()` semantics tests (array overload, snapshot isolation, insertion order) - [x] Hash equality on 3 seeded headless runs (2 maps) - [x] Before/after 20-min GC benchmarks on the same commit base 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3c196cb7e7 |
crit fix: indian subcontinent map crash (#4479)
Resolves #4401 NOTE: While this PR is an improvment, the Indian subcontinent crash IS NOT caused by >253 water components, as the map only has ~15 water components. ## Description: Fixes a critical browser tab crash ("Aw, Snap! Something went wrong") when loading the game on the new Indian Subcontinent map (or any map with >= 253 water components) in Solo Mode. ### Technical Cause: 1. When a map contains >= 253 disconnected water components, the array mapping tiles to component IDs is dynamically promoted from a Uint8Array to a Uint16Array. 2. This promotion upgrades the land sentinel LAND_MARKER from 0xff (255) to LAND_MARKER_WIDE (0xffff / 65535). 3. The BFS local search filter in AStarWaterHierarchical had a hardcoded sentinel check: (t: TileRef) => this.graph.getComponentId(t) !== LAND_MARKER (evaluating against 255). 4. On promoted maps, land tiles (65535) matched this check as water. The local BFS then traversed the entire landmass of the map, resulting in CPU exhaustion and memory/stack overflows that crashed the rendering process. ### Solution: Changed the hardcoded sentinel check to query the map's terrain directly via this.map.isWater(t). This makes the check immune to any component ID promotions or sentinel representation upgrades. Verified that the existing water pathfinding test suite passes successfully. ## Please complete the following: - [ ] I have added screenshots for all UI updates (N/A) - [ ] I process any text displayed to the user through translateText() and I've added it to the en.json file (N/A) - [x] I have added relevant tests to the test directory (Existing tests in tests/core/pathfinding/PathFinding.Water.test.ts cover water pathfinding behavior and run successfully) ## Please put your Discord username so you can be contacted if a bug or regression is found: blontd6 |
||
|
|
22d5aba5ae |
refactor: standardize cardinal-neighbor iteration on neighbors() N,S,W,E order (#4495)
## Summary Follow-up to #4494. That PR added `forEachNeighborNSWE` as a third neighbor iterator because the existing allocation-free helpers (`forEachNeighbor`, `neighbors4`) visit in W,E,N,S order while `neighbors()` visits N,S,W,E — and substituting one for the other changes simulation behavior at order-sensitive call sites. This PR removes that duplication by standardizing on **one order everywhere**: `forEachNeighbor` and `neighbors4` now visit in the same N,S,W,E order as `neighbors()`, and `forEachNeighborNSWE` is deleted. ## ⚠️ Intentional behavior change Callers of the flipped helpers that are order-sensitive now make different (equally valid) decisions: - `AttackExecution.addNeighbors` — PRNG values are drawn per neighbor while building the conquest frontier, so attack expansion patterns differ - `AttackExecution.handleDeadDefender` — a dead defender's tiles go to the *first-visited* adjacent player - `WarshipExecution.bestNeighborToward` — distance ties break by visit order - `PlayerExecution` surrounded-cluster flood fill — set insertion order propagates to conquer order Game outcomes for a given seed differ from previous builds (verified: the 12k-tick reference run ends with 31 players alive vs 24 before). Determinism across clients *within* a build is unaffected — all clients run the same code, so there is no desync risk. Replays/verification pinned to old hashes will not match this build. New reference hashes for the headless perf harness (seed `perf-default`): | Run | Final hash | |---|---| | giantworldmap, 12,000 ticks | `57830793797434300` | | giantworldmap, 2,000 ticks | `55125379638382860` | | world, 1,800 ticks | `32337437717390864` | ## Verification - [x] Full suite green (1,901 tests), including new exact-order contract tests: `forEachNeighbor` and `neighbors4` must match `neighbors()` contents **and order** for every tile - [x] 20-game-minute Giant World Map benchmark: no perf regression (73 ticks/sec, GC 1.2% of wall, allocation profile unchanged) - [x] Order-sensitivity audit of every `forEachNeighbor`/`neighbors4` call site (sensitive ones listed above; the rest are booleans, counts, or min/max accumulations) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
be77ab4fc9 |
feat: structures cosmetic effect (hover-shown gradient/transition recolor) (#4492)
## Description: Adds a new `structures` cosmetic effect type: an equippable effect that recolors the owner's structure icons (City, Port, Factory, Defense Post, SAM Launcher, Missile Silo) with gradient or transition color styles. The effect is **shown while the owner's territory is hovered** — structures otherwise keep their normal player colors, so the map stays readable. **Cosmetics / selection** - `StructuresEffectAttributesSchema` (`CosmeticSchemas.ts`): its own discriminated union (`gradient` / `transition`) — structurally identical to the trail attributes today, but structures aren't trails, so it's a separate schema free to diverge. - Slot = the effectType itself: `effectTypeForSlot` is generalized to map any non-nukeExplosion effect type to itself, so server privilege checks (`Privilege.ts`), client selection, and persistence all work with no per-type code. - Effects tab, Default tile, and the store preview (shared color swatch) come from `EFFECT_TYPES`; the only UI addition is the `effects.type.structures` label in `en.json`. **Rendering** - The shared per-player effect palette grows from 2 to 3 blocks (`EFFECT_PALETTE_BLOCKS`; structures = block 2, pinned by a build-breaking guard). `syncPlayerEffects` resolves the `structures` selection through the same `writeEffectEntry` used by trails. - `StructurePass` binds the effect texture plus `uTime` and `uHoverOwner` (fed from the existing `HoverHighlightController` → `setHighlightOwner` path, now forwarded to the pass). - `structure.frag.glsl` recolors the **fill only** — the border keeps the player color for ownership legibility; alt view and construction gray bypass the effect entirely. - Style semantics: - `gradient` — the palette spans each icon's diagonal once (a visible gradient across the shape), sliding one full cycle every `colorSize · 4 · count / movementSpeed` seconds (the trail-equivalent pace; world-space banding like the trail's would put a whole icon inside one band and read as a flat color) - `transition` — the whole icon is one color at a time, cross-fading at `frequency` colors/s - Glyph contrast: the inner icon's black/white decision is now a smooth luminance fade (`smoothstep(0.25, 0.45)`) instead of a hard flip at 0.25, so animated fills cross-fade the glyph instead of snapping it between black and white. ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5e4b2791aa |
perf: reduce core-sim GC churn 42% and add GC-churn profiling to the perf harness (#4494)
## Summary
Reduces core-simulation GC churn by **42%** on a 20-game-minute Giant
World Map run, and extends the headless full-game perf harness so churn
is measurable and regressions are visible.
### 1. GC-churn measurement (`tests/perf/fullgame/GcProfiler.ts`)
`npm run perf:game` now reports:
- **GC pauses** by kind (minor/major/incremental) via a
`PerformanceObserver` on `'gc'` entries, bucketed into tick windows by
timestamp (V8 only delivers these entries on a timer task, so they're
flushed after the run)
- **Allocation rate** per `--window N` ticks (default 1000) from
used-heap deltas sampled every tick, so churn can be tracked across game
phases
- **Top allocating functions** from the V8 sampling heap profiler with
`includeObjectsCollectedBy{Major,Minor}GC` — i.e. actual churn including
short-lived garbage, not live memory — plus a `.heapprofile` loadable in
Chrome DevTools (Memory → Allocation sampling)
New flags: `--window N`, `--no-gc-profile`, `--no-alloc-profile`.
### 2. Allocation reductions in the hot paths it found
| Site | Change |
|---|---|
| `GameMap.bfs` | inline neighbor enumeration instead of an array per
visited tile |
| `GameMap`/`Game` | new `forEachNeighborNSWE` — allocation-free
iterator matching `neighbors()` N,S,W,E order for order-sensitive
callers (`forEachNeighbor` visits W,E,N,S, so substituting it would
change sim behavior) |
| `PlayerImpl.nearby` / `sharesBorderWith` / `shoreReachableNeighbors` |
no per-call neighbor arrays; no materialized shore-tile array |
| `PlayerImpl.units(types)` | gather into a reusable scratch buffer,
return one exact-size slice (still a fresh snapshot array per call) |
| `AiAttackBehavior.maybeAttack` | single pass over border neighbors
replacing the `flatMap`/`filter`/`map` chain over every border tile |
| `AiAttackBehavior.isBorderingNukedTerritory` | reusable `neighbors4`
buffer with early exit |
| `SharedWaterCache.build` | allocation-free neighbor iteration |
| `SpatialQuery.bfsNearest` | first-minimum scan instead of
collect-then-stable-sort (identical result incl. tie-breaking) |
### Results (Giant World Map, 400 bots, 12,000 ticks ≈ 20 game-minutes,
seed `perf-default`)
| Metric | Before | After |
|---|---|---|
| Sampled allocations (incl. collected) | 97.7 GB | **56.9 GB (−42%)** |
| GC count / total pause | 1,682 / 3,313 ms (1.8% of wall) | 1,058 /
2,087 ms (1.2%) |
| Ticks/sec | 66 | 70 |
| p99 / max tick | 49.9 ms / 988 ms | 43.5 ms / 689 ms |
| Ticks over 100 ms budget | 31 | 19 |
## Determinism
Every rewrite preserves exact iteration order (the new NSWE iterator
exists precisely for the order-sensitive sites). Verified by identical
final game-state hashes on three runs: Giant World Map 12,000 ticks
(`67286276735690560`), Giant World Map 2,000 ticks, and World 1,800
ticks.
## Test plan
- [x] Full suite green (1,896 tests)
- [x] New tests: `forEachNeighborNSWE` order contract vs `neighbors()`
over every tile; `units()` filtering semantics (insertion order,
fresh-array guarantee, duplicate types, Set path)
- [x] Final-hash equality on 3 seeded headless runs (2 maps)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
78ef7b56fd |
feat(doomsday-clock): battle-royale style zone gamemode (#4469)
Resolves Issue #4463 ## Description: An optional game mode that (almost) guarantees a finish instead of letting late-game stalemates drag on. Originally called sudden death, renamed to Doomsday clock Once enabled, every side (each player in FFA, each whole team in team modes) must hold a rising share of the map. A side below the bar is skulled; after a short warn its troops bleed to zero, forcing consolidation to a winner. ### How it works - **Rising zone:** a grace period, then the required share ramps up linearly to each level with 30s pauses between (a battle-royale "zone"). Levels track the ofstats FFA territory median (3/5/10/20/30%). - **Four speed presets** (slow / normal / fast / very fast) change only the pace: normal ends ~30 min, very fast ~15. - **Troop decay:** a linear ramp as a % of max capacity, ~50s from caught to zero (10s warn + ~50s ≈ 1 min total). - **UI:** a HUD panel (live share vs target, wave/decay countdowns, red/orange cues) and an on-map skull above flagged players (blinks in danger, steady while draining). ### Notes for review - Off by default; no effect on existing games. However, as discussed we can add it to the modifier pool for public games to see how popular the gamemode is vs normal play. - Sim is deterministic (integer-only, in `src/core`), covered by unit + integration tests. - One-line addition to `GameServer.updateGameConfig` so the setting survives the host → server → client round-trip. - Status is packed into the existing name-pass data slot (`pd4.w`: 0/1/2 = none/danger/draining); the skull is composited into the icon atlas at load. ### Testing `npm test`, `npm run lint`, `npx prettier --check .`, `npm run build-prod` all pass. ### UI: <img width="243" height="100" alt="Image" src="https://github.com/user-attachments/assets/c4c9eeb0-4feb-437d-9aac-b2786a841b74" /> Dropdown between slow, normal, fast, very fast Before zone: <img width="302" height="175" alt="Image" src="https://github.com/user-attachments/assets/7359a1ea-4951-446d-a23c-0711fe06cc5d" /> Zone started, player not affected the pannel also blinks orange for 10s: <img width="297" height="175" alt="Image" src="https://github.com/user-attachments/assets/fcc565a5-d5d0-47a7-97ea-d0ba9d9ad899" /> Player affected, grace period (Danger): <img width="314" height="170" alt="Image" src="https://github.com/user-attachments/assets/ff96d21e-96f3-4ef9-8190-48eecc7aac0f" /> Skull icon blinking over player (everyone sees it) - older screenshot, the clipping has been fixed <img width="462" height="145" alt="Image" src="https://github.com/user-attachments/assets/53899211-33b1-40e1-83f2-77f2096f0cad" /> Player affected, grace period ended (Draining): <img width="360" height="159" alt="Image" src="https://github.com/user-attachments/assets/4b226d57-da4d-4866-ab5f-db48e4ed1ea2" /> Skull icon no longer blinking, everyone can see you are in a state of decay, and troops are draining: <img width="732" height="146" alt="image" src="https://github.com/user-attachments/assets/cd10fedb-6e87-4dfc-9fbf-55d3945a7901" /> Skull is visible like alliances icon also on player tab <img width="558" height="81" alt="Image" src="https://github.com/user-attachments/assets/6acdbe91-bdd0-40c7-942b-3990d4dae87f" /> (just UI example, best way to see it is to hop on a solo game and play against AI) ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: zixer._ |
||
|
|
ad760a0f3d |
feat: Achievement medal overview (#4487)
## Description: In the spirit of achievement hunting, seeing how many medals a player has achieved helps show overall progress. Overview only toggles on when user clicks toggle achievements button. Works on mobile too. https://github.com/user-attachments/assets/ea77075b-5e91-4e62-8ac9-52bcdf95cc64 ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: bijx |
||
|
|
64a6111fd4 |
Bump the updates group with 2 updates (#4475)
Bumps the updates group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [toshimaru/auto-author-assign](https://github.com/toshimaru/auto-author-assign). Updates `actions/checkout` from 6 to 7 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/actions/checkout/releases">actions/checkout's releases</a>.</em></p> <blockquote> <h2>v7.0.0</h2> <h2>What's Changed</h2> <ul> <li>block checking out fork pr for pull_request_target and workflow_run by <a href="https://github.com/aiqiaoy"><code>@aiqiaoy</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li> <li>Bump actions/publish-immutable-action from 0.0.3 to 0.0.4 in the minor-actions-dependencies group across 1 directory by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2458">actions/checkout#2458</a></li> <li>Bump flatted from 3.3.1 to 3.4.2 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2460">actions/checkout#2460</a></li> <li>Bump js-yaml from 4.1.0 to 4.2.0 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2461">actions/checkout#2461</a></li> <li>Bump <code>@actions/core</code> and <code>@actions/tool-cache</code> and Remove uuid by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2459">actions/checkout#2459</a></li> <li>upgrade module to esm and update dependencies by <a href="https://github.com/aiqiaoy"><code>@aiqiaoy</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2463">actions/checkout#2463</a></li> <li>Bump the minor-npm-dependencies group across 1 directory with 3 updates by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2462">actions/checkout#2462</a></li> <li>getting ready for checkout v7 release by <a href="https://github.com/aiqiaoy"><code>@aiqiaoy</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2464">actions/checkout#2464</a></li> <li>update error wording by <a href="https://github.com/aiqiaoy"><code>@aiqiaoy</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2467">actions/checkout#2467</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/aiqiaoy"><code>@aiqiaoy</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v6.0.3...v7.0.0">https://github.com/actions/checkout/compare/v6.0.3...v7.0.0</a></p> <h2>v6.0.3</h2> <h2>What's Changed</h2> <ul> <li>Update changelog by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2357">actions/checkout#2357</a></li> <li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a href="https://github.com/yaananth"><code>@yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li> <li>Fix checkout init for SHA-256 repositories by <a href="https://github.com/yaananth"><code>@yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li> <li>Update changelog for v6.0.3 by <a href="https://github.com/yaananth"><code>@yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2446">actions/checkout#2446</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/yaananth"><code>@yaananth</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v6...v6.0.3">https://github.com/actions/checkout/compare/v6...v6.0.3</a></p> <h2>v6.0.2</h2> <h2>What's Changed</h2> <ul> <li>Add orchestration_id to git user-agent when ACTIONS_ORCHESTRATION_ID is set by <a href="https://github.com/TingluoHuang"><code>@TingluoHuang</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2355">actions/checkout#2355</a></li> <li>Fix tag handling: preserve annotations and explicit fetch-tags by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2356">actions/checkout#2356</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v6.0.1...v6.0.2">https://github.com/actions/checkout/compare/v6.0.1...v6.0.2</a></p> <h2>v6.0.1</h2> <h2>What's Changed</h2> <ul> <li>Update all references from v5 and v4 to v6 by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2314">actions/checkout#2314</a></li> <li>Add worktree support for persist-credentials includeIf by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2327">actions/checkout#2327</a></li> <li>Clarify v6 README by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2328">actions/checkout#2328</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v6...v6.0.1">https://github.com/actions/checkout/compare/v6...v6.0.1</a></p> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's changelog</a>.</em></p> <blockquote> <h1>Changelog</h1> <h2>v7.0.0</h2> <ul> <li>Block checking out fork PR for pull_request_target and workflow_run by <a href="https://github.com/aiqiaoy"><code>@aiqiaoy</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li> <li>Bump actions/publish-immutable-action from 0.0.3 to 0.0.4 in the minor-actions-dependencies group across 1 directory by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2458">actions/checkout#2458</a></li> <li>Bump flatted from 3.3.1 to 3.4.2 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2460">actions/checkout#2460</a></li> <li>Bump js-yaml from 4.1.0 to 4.2.0 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2461">actions/checkout#2461</a></li> <li>Bump <code>@actions/core</code> and <code>@actions/tool-cache</code> and Remove uuid by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2459">actions/checkout#2459</a></li> <li>upgrade module to esm and update dependencies by <a href="https://github.com/aiqiaoy"><code>@aiqiaoy</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2463">actions/checkout#2463</a></li> <li>Bump the minor-npm-dependencies group across 1 directory with 3 updates by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/actions/checkout/pull/2462">actions/checkout#2462</a></li> </ul> <h2>v6.0.3</h2> <ul> <li>Fix checkout init for SHA-256 repositories by <a href="https://github.com/yaananth"><code>@yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li> <li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a href="https://github.com/yaananth"><code>@yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li> </ul> <h2>v6.0.2</h2> <ul> <li>Fix tag handling: preserve annotations and explicit fetch-tags by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2356">actions/checkout#2356</a></li> </ul> <h2>v6.0.1</h2> <ul> <li>Add worktree support for persist-credentials includeIf by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2327">actions/checkout#2327</a></li> </ul> <h2>v6.0.0</h2> <ul> <li>Persist creds to a separate file by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2286">actions/checkout#2286</a></li> <li>Update README to include Node.js 24 support details and requirements by <a href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2248">actions/checkout#2248</a></li> </ul> <h2>v5.0.1</h2> <ul> <li>Port v6 cleanup to v5 by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2301">actions/checkout#2301</a></li> </ul> <h2>v5.0.0</h2> <ul> <li>Update actions checkout to use node 24 by <a href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li> </ul> <h2>v4.3.1</h2> <ul> <li>Port v6 cleanup to v4 by <a href="https://github.com/ericsciple"><code>@ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2305">actions/checkout#2305</a></li> </ul> <h2>v4.3.0</h2> <ul> <li>docs: update README.md by <a href="https://github.com/motss"><code>@motss</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li> <li>Add internal repos for checking out multiple repositories by <a href="https://github.com/mouismail"><code>@mouismail</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li> <li>Documentation update - add recommended permissions to Readme by <a href="https://github.com/benwells"><code>@benwells</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li> <li>Adjust positioning of user email note and permissions heading by <a href="https://github.com/joshmgross"><code>@joshmgross</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li> <li>Update README.md by <a href="https://github.com/nebuk89"><code>@nebuk89</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li> <li>Update CODEOWNERS for actions by <a href="https://github.com/TingluoHuang"><code>@TingluoHuang</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li> <li>Update package dependencies by <a href="https://github.com/salmanmkc"><code>@salmanmkc</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li> </ul> <h2>v4.2.2</h2> <ul> <li><code>url-helper.ts</code> now leverages well-known environment variables by <a href="https://github.com/jww3"><code>@jww3</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li> <li>Expand unit test coverage for <code>isGhes</code> by <a href="https://github.com/jww3"><code>@jww3</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li> </ul> <h2>v4.2.1</h2> <ul> <li>Check out other refs/* by commit if provided, fall back to ref by <a href="https://github.com/orhantoy"><code>@orhantoy</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/actions/checkout/commit/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0"><code>9c091bb</code></a> update error wording (<a href="https://redirect.github.com/actions/checkout/issues/2467">#2467</a>)</li> <li><a href="https://github.com/actions/checkout/commit/1044a6dea927916f2c38ba5aeffbc0a847b1221a"><code>1044a6d</code></a> getting ready for checkout v7 release (<a href="https://redirect.github.com/actions/checkout/issues/2464">#2464</a>)</li> <li><a href="https://github.com/actions/checkout/commit/f0282184c7ce73ab54c7e4ab5a617122602e575f"><code>f028218</code></a> Bump the minor-npm-dependencies group across 1 directory with 3 updates (<a href="https://redirect.github.com/actions/checkout/issues/2462">#2462</a>)</li> <li><a href="https://github.com/actions/checkout/commit/d914b262ffc244530a203ab40decab34c3abf34d"><code>d914b26</code></a> upgrade module to esm and update dependencies (<a href="https://redirect.github.com/actions/checkout/issues/2463">#2463</a>)</li> <li><a href="https://github.com/actions/checkout/commit/537c7ef99cef6e5ddb5e7ff5d16d14510503801d"><code>537c7ef</code></a> Bump <code>@actions/core</code> and <code>@actions/tool-cache</code> and Remove uuid (<a href="https://redirect.github.com/actions/checkout/issues/2459">#2459</a>)</li> <li><a href="https://github.com/actions/checkout/commit/130a169078a413d3a5246a393625e8e742f387f6"><code>130a169</code></a> Bump js-yaml from 4.1.0 to 4.2.0 (<a href="https://redirect.github.com/actions/checkout/issues/2461">#2461</a>)</li> <li><a href="https://github.com/actions/checkout/commit/7d09575332117a40b46e5e020664df234cd416f3"><code>7d09575</code></a> Bump flatted from 3.3.1 to 3.4.2 (<a href="https://redirect.github.com/actions/checkout/issues/2460">#2460</a>)</li> <li><a href="https://github.com/actions/checkout/commit/0f9f3aa320cb53abeb534aeb54048075d9697a0e"><code>0f9f3aa</code></a> Bump actions/publish-immutable-action (<a href="https://redirect.github.com/actions/checkout/issues/2458">#2458</a>)</li> <li><a href="https://github.com/actions/checkout/commit/f9e715a95fcd1f9253f77dd28f11e88d2d6460c7"><code>f9e715a</code></a> block checking out fork pr for pull_request_target and workflow_run (<a href="https://redirect.github.com/actions/checkout/issues/2454">#2454</a>)</li> <li>See full diff in <a href="https://github.com/actions/checkout/compare/v6...v7">compare view</a></li> </ul> </details> <br /> Updates `toshimaru/auto-author-assign` from 3.0.2 to 3.0.3 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/toshimaru/auto-author-assign/releases">toshimaru/auto-author-assign's releases</a>.</em></p> <blockquote> <h2>v3.0.3</h2> <!-- raw HTML omitted --> <blockquote> <p>[!NOTE] This is the first immutable release version. 🔒</p> </blockquote> <h2>What's Changed</h2> <h3>Dependencies</h3> <ul> <li>build(deps): bump <code>@rollup/rollup-linux-x64-gnu</code> from 4.60.2 to 4.60.3 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/toshimaru/auto-author-assign/pull/167">toshimaru/auto-author-assign#167</a></li> <li>build(deps): bump <code>@rollup/rollup-linux-x64-gnu</code> from 4.60.3 to 4.60.4 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/toshimaru/auto-author-assign/pull/169">toshimaru/auto-author-assign#169</a></li> <li>build(deps-dev): bump rollup from 4.60.2 to 4.61.1 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/toshimaru/auto-author-assign/pull/174">toshimaru/auto-author-assign#174</a></li> <li>build(deps-dev): bump <code>@rollup/plugin-commonjs</code> from 29.0.2 to 29.0.3 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/toshimaru/auto-author-assign/pull/172">toshimaru/auto-author-assign#172</a></li> <li>build(deps): bump <code>@rollup/rollup-linux-x64-gnu</code> from 4.61.1 to 4.62.0 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/toshimaru/auto-author-assign/pull/175">toshimaru/auto-author-assign#175</a></li> <li>build(deps-dev): bump rollup from 4.61.1 to 4.62.0 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/toshimaru/auto-author-assign/pull/176">toshimaru/auto-author-assign#176</a></li> </ul> <h3>Others</h3> <ul> <li>docs: bump version to v3.0.2 and add Release badge in README by <a href="https://github.com/devin-ai-integration"><code>@devin-ai-integration</code></a>[bot] in <a href="https://redirect.github.com/toshimaru/auto-author-assign/pull/166">toshimaru/auto-author-assign#166</a></li> <li>chore(main): release 3.0.3 by <a href="https://github.com/github-actions"><code>@github-actions</code></a>[bot] in <a href="https://redirect.github.com/toshimaru/auto-author-assign/pull/177">toshimaru/auto-author-assign#177</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/devin-ai-integration"><code>@devin-ai-integration</code></a>[bot] made their first contribution in <a href="https://redirect.github.com/toshimaru/auto-author-assign/pull/166">toshimaru/auto-author-assign#166</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/toshimaru/auto-author-assign/compare/v3.0.2...v3.0.3">https://github.com/toshimaru/auto-author-assign/compare/v3.0.2...v3.0.3</a></p> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/toshimaru/auto-author-assign/blob/main/CHANGELOG.md">toshimaru/auto-author-assign's changelog</a>.</em></p> <blockquote> <h1>Changelog</h1> <h2><a href="https://github.com/toshimaru/auto-author-assign/compare/v3.0.2...v3.0.3">3.0.3</a> (2026-06-15)</h2> <h3>Miscellaneous Chores</h3> <ul> <li>release 3.0.3 (<a href="https://github.com/toshimaru/auto-author-assign/commit/8f813140fc4dbdfcc4a43f89d97033b5988e3773">8f81314</a>)</li> </ul> <h2><a href="https://github.com/toshimaru/auto-author-assign/compare/v3.0.1...v3.0.2">3.0.2</a> (2026-04-27)</h2> <h3>Miscellaneous Chores</h3> <ul> <li>release 3.0.2 (<a href="https://github.com/toshimaru/auto-author-assign/commit/658b95bf703955e926268fcaca1124037270bea8">658b95b</a>)</li> <li>release 3.0.2 (<a href="https://github.com/toshimaru/auto-author-assign/commit/ca59fc3261247bab40337927fac848bf2a60863f">ca59fc3</a>)</li> </ul> <h2><a href="https://github.com/toshimaru/auto-author-assign/compare/v3.0.0...v3.0.1">3.0.1</a> (2025-12-25)</h2> <h3>Miscellaneous Chores</h3> <ul> <li>release 3.0.1 (<a href="https://github.com/toshimaru/auto-author-assign/commit/718d4ed5349747d47952ae841ae03fcbdd74ebea">718d4ed</a>)</li> </ul> <h2><a href="https://github.com/toshimaru/auto-author-assign/compare/v2.1.2...v3.0.0">3.0.0</a> (2025-12-21)</h2> <h3>Features</h3> <ul> <li>Add <code>npm run package</code> instead of <code>build</code> (<a href="https://redirect.github.com/toshimaru/auto-author-assign/issues/130">#130</a>) (<a href="https://github.com/toshimaru/auto-author-assign/commit/972720f0403d2873e807f16e350c5b0b1be4dda3">972720f</a>)</li> </ul> <h3>Miscellaneous Chores</h3> <ul> <li>release 3.0.0 (<a href="https://github.com/toshimaru/auto-author-assign/commit/d100ceff34d1e9cd2c4ea5b8055922f1409f3068">d100cef</a>)</li> </ul> <h3><a href="https://github.com/toshimaru/auto-author-assign/compare/v2.1.1...v2.1.2">2.1.2</a> (2025-12-16)</h3> <h3><a href="https://github.com/toshimaru/auto-author-assign/compare/v2.1.0...v2.1.1">2.1.1</a> (2024-06-26)</h3> <h2><a href="https://github.com/toshimaru/auto-author-assign/compare/v2.0.1...v2.1.0">2.1.0</a> (2024-01-17)</h2> <h3><a href="https://github.com/toshimaru/auto-author-assign/compare/v2.0.0...v2.0.1">2.0.1</a> (2023-09-26)</h3> <h2><a href="https://github.com/toshimaru/auto-author-assign/compare/v1.6.2...v2.0.0">2.0.0</a> (2023-09-24)</h2> <h3><a href="https://github.com/toshimaru/auto-author-assign/compare/v1.6.1...v1.6.2">1.6.2</a> (2023-01-03)</h3> <ul> <li>chore: dependencies update</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/toshimaru/auto-author-assign/commit/3e19bfc990cb1cf0589dce95e9f75289bb1e22de"><code>3e19bfc</code></a> chore(main): release 3.0.3 (<a href="https://redirect.github.com/toshimaru/auto-author-assign/issues/177">#177</a>)</li> <li><a href="https://github.com/toshimaru/auto-author-assign/commit/8f813140fc4dbdfcc4a43f89d97033b5988e3773"><code>8f81314</code></a> chore: release 3.0.3</li> <li><a href="https://github.com/toshimaru/auto-author-assign/commit/3e0e4f96b40e287b778df5273d0e53c491711daa"><code>3e0e4f9</code></a> build(deps-dev): bump rollup from 4.61.1 to 4.62.0 (<a href="https://redirect.github.com/toshimaru/auto-author-assign/issues/176">#176</a>)</li> <li><a href="https://github.com/toshimaru/auto-author-assign/commit/d481ffe32108e4f6e6c497c7a4b7ccc809eab186"><code>d481ffe</code></a> build(deps): bump <code>@rollup/rollup-linux-x64-gnu</code> from 4.61.1 to 4.62.0 (<a href="https://redirect.github.com/toshimaru/auto-author-assign/issues/175">#175</a>)</li> <li><a href="https://github.com/toshimaru/auto-author-assign/commit/4dccdc964c79c751a5f0570bf807a2baaac70ad1"><code>4dccdc9</code></a> build(deps-dev): bump <code>@rollup/plugin-commonjs</code> from 29.0.2 to 29.0.3 (<a href="https://redirect.github.com/toshimaru/auto-author-assign/issues/172">#172</a>)</li> <li><a href="https://github.com/toshimaru/auto-author-assign/commit/1bc6b5cfc226fd7ee62b4cb76dd11fefd5f7cdd4"><code>1bc6b5c</code></a> build(deps-dev): bump rollup from 4.60.2 to 4.61.1 (<a href="https://redirect.github.com/toshimaru/auto-author-assign/issues/174">#174</a>)</li> <li><a href="https://github.com/toshimaru/auto-author-assign/commit/e2fe17d9d8eb77d9dc3900514e0c29d8db5b9c28"><code>e2fe17d</code></a> build(deps): bump <code>@rollup/rollup-linux-x64-gnu</code> from 4.60.3 to 4.60.4 (<a href="https://redirect.github.com/toshimaru/auto-author-assign/issues/169">#169</a>)</li> <li><a href="https://github.com/toshimaru/auto-author-assign/commit/d57829c471f47dcbafaf6f1a43505a01459aa64e"><code>d57829c</code></a> build(deps): bump <code>@rollup/rollup-linux-x64-gnu</code> from 4.60.2 to 4.60.3 (<a href="https://redirect.github.com/toshimaru/auto-author-assign/issues/167">#167</a>)</li> <li><a href="https://github.com/toshimaru/auto-author-assign/commit/7a8235eb61f82485956f5f1a70cbc4bbae19f8e7"><code>7a8235e</code></a> docs: bump version to v3.0.2 and add Release badge in README (<a href="https://redirect.github.com/toshimaru/auto-author-assign/issues/166">#166</a>)</li> <li>See full diff in <a href="https://github.com/toshimaru/auto-author-assign/compare/bdd7688cbf9e6d5683f02f8c7d8ae4062a254b6d...3e19bfc990cb1cf0589dce95e9f75289bb1e22de">compare view</a></li> </ul> </details> <br /> Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
b72956d0c0 |
Gate users without GPU-accelerated WebGL2 instead of running at ~1fps (#4324)
## Problem After the WebGL2 renderer migration, a small number of users (~a dozen of 100k DAU) report ~5fps. Root cause: they run WebGL **without GPU acceleration** (hardware acceleration disabled, blocklisted driver, or a locked-down machine), so they get a SwiftShader/software context. Software-rendered WebGL is hopeless for a real-time game — ~1fps locally. We are not supporting a canvas2d fallback. Instead: demand a GPU-accelerated context, and if we can't get one, **gate** the user with actionable instructions rather than letting the game crawl. ## What this does - **`initGL()`** ([initGL.ts](../blob/webgl-software-render-gate/src/client/render/gl/initGL.ts)) — demands `failIfMajorPerformanceCaveat: true` **and** inspects the unmasked renderer string. The flag alone isn't enough: when hardware acceleration is turned off in browser *settings* (vs. a blocklisted driver), Chrome still hands back a SwiftShader context, so we'd otherwise run at 1fps. Classifies the outcome as `ok` / `software` / `unsupported`. - **`GPURenderer`** throws `GLUnavailableError` on a non-accelerated context; the game-start `catch` shows the gate and removes the orphaned canvas. - **`<webgl-gate>`** Lit component renders a full-screen blocking gate with per-browser steps (Chrome / Edge / Firefox / Safari) for enabling hardware acceleration / WebGL. - **`gl_init` analytics event** fires every session (`status` + `renderer` for non-ok) via the existing Google Tag, so we can size the real affected % within a day. ## Notes / decisions - The gate copy is **intentionally inlined (not translated)** — it's a rarely-seen, browser-specific troubleshooting screen; 28 Crowdin keys would be poor cost/benefit, and a non-English user still has to navigate English browser menus. - `showGLGate` lazy-loads the component (`import()`), so the `render/gl` module that `Renderer.ts` imports doesn't statically pull a UI component into its graph. ## Update: fingerprint-capped contexts (#4357) A third failure class, integrated after the initial PR: `privacy.resistFingerprinting` (default-on in LibreWolf and Mullvad Browser, opt-in in Firefox) caps `MAX_TEXTURE_SIZE` at 2048 on an otherwise hardware-accelerated context. The renderer unconditionally allocates a 4096-wide palette texture, so the oversized `texImage2D` calls fail silently and the whole map renders **black** (#4357). - `initGL` now reads `MAX_TEXTURE_SIZE` after the software check and classifies the context as **`limited`** when it's below `getPaletteSize()` (4096 — the hard floor every game needs). - Unlike `software`/`unsupported`, **`limited` is a warning, not a hard block**: `initGL` still returns the context, the game starts normally, and the gate is shown with a "Continue anyway" button. `GPURenderer` exposes the capped renderer/size via `glLimited` (surfaced through `MapRenderer`), which `ClientGameRunner` uses to show the warning and log analytics. - The gate shows fingerprinting-specific instructions for `limited` (add the site to `privacy.resistFingerprinting.exemptedDomains` in `about:config`) instead of the hardware-acceleration steps. - `gl_init` reports `max_texture_size` alongside the renderer for this status, so we can size the RFP-affected population too. Fixes #4357 ## Test plan - [x] Unit tests for `initGL`'s `ok` / `software` / `unsupported` branching, incl. the "returns a context but renderer is software" case (`tests/client/initGL.test.ts`). - [x] lint / prettier / tsc clean. - [x] **Verified in real browsers (macOS).** All three gate states reproduced: - `software`: Chrome with `--use-gl=angle --use-angle=swiftshader` (confirmed "Software only" at `chrome://gpu`), and Chrome with hardware acceleration toggled off in settings — both show the hard gate instead of a 1fps game. - `unsupported`: Firefox with `webgl.disabled=true` shows the unsupported gate. - `limited`: Firefox with `privacy.resistFingerprinting=true` (MAX_TEXTURE_SIZE capped to 2048, same as LibreWolf's default) shows the dismissible warning; "Continue anyway" starts the game, and exempting the site via `privacy.resistFingerprinting.exemptedDomains` removes the warning. ## Acceptance criteria - Accelerated users: unchanged. - Software / no-accel users: see the enable-acceleration gate, not a 1fps game. - No-WebGL2 users: see the unsupported gate. - `gl_init` fires every session with status (+ renderer for non-ok). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b6317964a7 |
feat: sparkles nuke-explosion visual type (#4490)
## Description: Follow-up to #4485: adds a second nuke-explosion visual, `"sparkles"` — a firework burst of twinkling glints that start at the detonation point and ride outward with the expanding front, reaching the cosmetic's full `size` at fade-out. **Schema (`CosmeticSchemas.ts`)** - `NukeExplosionAttributesSchema` is now a discriminated union on `type` (`"shockwave" | "sparkles"`), matching `TrailEffectAttributesSchema`. Old clients drop sparkles entries via `lenientRecord` and render the default ring. - The sparkles member adds `density` (required, positive) — roughly the total number of glints in the burst. - Literal attribute semantics, consistent with shockwave: - `size` — final burst width (diameter) in world tiles at fade-out - `speed` — tiles/s the width grows; duration = size / speed, clamped 0.1–15 s - `thickness` — **average** sparkle size in tiles; each glint hash-varies ±50% around it - `density` — approximate glint count; renderer clamps to 2–5000 - `colors` + `transitionSpeed` — shared palette-cycle semantics, with a hashed per-glint palette offset on top **Rendering** - `NukeExplosionRenderParams` now carries the visual type through to the pass as a matching TS union (previously any cosmetic was hardwired to the EMP style — this closes that gap for future visuals). - Sparkles are style 2 in the same `FxShockwavePass` instance stream: one new float (grid cell pitch, derived CPU-side from density), no other layout changes. - Fragment shader: one hashed glint per rotated front-normalized grid cell (jittered, cell-confined so each fragment samples only its own cell, ~1/3 dropout for organic scatter), hashed birth stagger. Glints are **fully opaque** — twinkle modulates color brightness, not alpha — holding full opacity through life and fading only over the last quarter. - SAM interceptions, the classic ring, and EMP shockwaves are unchanged. **Store / selection** - New `<sparkles-swatch>` preview (burst scales from center, density-scaled dot count, size-varied dots, palette cycling), branched in `CosmeticButton` by `attributes.type`. **Verification** - Schema tests incl. the real `rgb_nuke_sparkles` catalog entry, missing/non-positive `density` rejection. - Verified in-game via headless Chromium: size-250 RGB burst renders opaque red/white/blue glints expanding from the detonation point; sparse (40) vs dense (400) density comparison; no page errors. ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a7245518e2 |
Adds Map of China (#4468)
> **Before opening a PR:** discuss new features on [Discord](https://discord.gg/K9zernJB5z) first, and file bugs or small improvements as [issues](https://github.com/openfrontio/OpenFrontIO/issues/new/choose). You must be assigned to an `approved` issue — unsolicited PRs will be auto-closed. **Add approved & assigned issue number here:** Resolves #4419 ## Description: - Adds a China Map. This is themed after Qing China from the late 19th and early 20th centuries, which is why the map also includes Mongolia and Taiwan. - There are 32 nations themed after qing provinces, warlords, factions, European colonies, rebellions. - Also adds several new flags: EastTurkestan, Manchukuo, BritishHongKong, TaipingHeavenlyKingdom, etc. - Includes additional nations for a total of 62 nations. - Size: 1892x2080 - Land area: approx. 1.86M - Adds Caspian Sea and China to the new category (which i forgot earlier) https://youtu.be/ew9Qizo67cA <img width="2082" height="1895" alt="image" src="https://github.com/user-attachments/assets/d34fbb9e-fa93-4afe-a89f-8f11c5f9529b" /> ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: DISCORD_USERNAME crunchybbbbb |
||
|
|
aadca4e0ab |
Update warship instructions for veterancy (#4489)
## Description: Adds simple help instructions for new warship veterancy to help screen. Just to be clear about what levelling does and how many levels can be achieved. <img width="1136" height="388" alt="image" src="https://github.com/user-attachments/assets/c14425b4-ea23-4b62-89b3-881f8c16e1f3" /> ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: bijx |
||
|
|
6ff202afb5 |
feat: nuke-explosion cosmetic effects (per-bomb-type shockwave customization) (#4485)
## Description: Adds a new `nukeExplosion` cosmetic effect type: when a bomb detonates, every client renders the shockwave in the firing player's equipped effect for that bomb type. **Cosmetics / selection** - New `nukeExplosion` effect schema (`CosmeticSchemas.ts`) with per-bomb selection slots — a slot is the effectType for trails and the `nukeType` for explosions (`atom` / `hydro` / `mirvWarhead`), so players can equip a distinct explosion per bomb type. - Slot resolution + validation is one shared helper (`findEffectForSlot`) used by client selection, server privilege checks (`Privilege.ts`), and the renderer; a compile-time guard keeps the nukeType and effectType slot namespaces disjoint. - Effects picker gains an Atom / Hydrogen / MIRV sub-tab bar when browsing nuke explosions; selections persist per slot in UserSettings and are validated/dropped like other cosmetics. **Rendering** - `WebGLFrameBuilder` resolves each dead nuke's owner cosmetic onto the dead-unit event; `FxShockwavePass` renders an EMP-style procedural ring (jagged crackling front, rotating lightning arcs, inner energy fill) from per-instance attributes. SAM interceptions and players with no cosmetic keep the classic white ring. - Catalog attributes have literal units: - `size` — final ring width (diameter) in world tiles at fade-out, absolute — independent of the bomb's blast radius - `speed` — tiles/s the width grows; duration = size / speed, clamped to 0.1–15 s - `thickness` (required) — ring band thickness in tiles, constant while the ring expands - `colors` — palette of up to 4 colors, cycled at `transitionSpeed` steps/s (0 = static, negative = reverse; same semantics as trail transitions) - The shockwave quad is sized radius + thickness so the absolute-width band isn't clipped into a box while the ring is young. ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
006f1690a5 |
Warship veterancy (#4433)
## Description: Warship veterancy! This is an idea inspired by the unit veterancy feature of games like C&C: Red Alert 2 in which unit eliminations increases the level of individual units. I've been trying to build this mechanic for months with different ideas, and I finally landed on this being one of the more balanced implementation. Warships can earn up to three levels, represented by the gold bar insignia in the bottom right of their warship sprite. <img width="622" height="202" alt="image" src="https://github.com/user-attachments/assets/a8c31a45-4ae9-41a9-b054-9c4a7f4ab1f1" /> A veterancy bar grants 20% health from the base amount, and a 20% increase in shell damage applied _after_ the random damage roll. For example, a level 3 warship will apply a 60% damage boost on top of the random shell damage value (something between 200-325. If the random value is 250, the final damage output will be `250 * 1.60 = 400`. There are three ways to achieve a veteran level: 1. **Eliminate another warship:** any time a warship neutralizes another warship, it immediately get's a veterancy increase. https://github.com/user-attachments/assets/6a9e0958-5171-4ca3-94f6-9c2300a12f8b 2. **Eliminate transport boats:** Destroying 10 transport boats will level a warship to the next veterancy bar. https://github.com/user-attachments/assets/619ce0c0-033c-4e0b-9c64-b41eabaa791b 3. **Steal trade ships:** If the warship captures 25 trade ships, it will earn a veterancy bar. ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: bijx |
||
|
|
1d5a6ae246 |
Account Modal - Games Tab (#4473)
## Description: Continuation of https://github.com/openfrontio/infra/pull/386, adds play games sessions <img width="971" height="771" alt="image" src="https://github.com/user-attachments/assets/42c6bcbb-d690-4cd1-b859-3299a03f4350" /> ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: w.o.n --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c9850c472b |
docs: remove Wicked Sick service-provider paragraph from privacy policy (#4472)
Removes the paragraph in Section 1 ("Who We Are") of the privacy policy
that named Wicked Sick Limited (company number 11117702) as a service
provider, and bumps the "Last Updated" date to 7/1/2026.
- Repo-wide search confirmed no other references to "Wicked Sick" /
"11117702" anywhere in the codebase.
- Section 1 reads correctly after removal (data-controller block flows
into the EEA/UK representative paragraph).
Replaces closed PR #4471.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
2794ab1270 |
feat: nuke-trail cosmetic effect + tabbed effects picker (#4466)
## What Adds a **`nukeTrail`** cosmetic effectType alongside `transportShipTrail`, so nukes leave a trail colored by their own gradient/transition effect — independent of the boat-trail effect (a player can run both). Also reorganizes the effects picker and store into per-effectType **tabs**. ## Rendering Boat and nuke trails are stamped into **one** trail texture keyed only by owner, so independent coloring needs a per-tile unit-class signal: - **Trail texture** `R8UI` → `R16UI`: texel = `ownerID(bits 0-11) | nukeBit(bit 12)`. `TrailManager` stamps the bit (and preserves it when repainting on unit death); the `Uint8Array`→`Uint16Array` ripple + `UNSIGNED_SHORT` uploads flow through `GpuResources`, `TrailPass`, `Upload`, `MapRenderer`, `Renderer`, `FrameData`. - **Effect texture** widened to two stacked blocks (`TRAIL_EFFECT_BLOCKS`): rows 0–7 = transportShipTrail, rows 8–15 = nukeTrail. `writeEffectEntry(…, rowBase)`; `syncPlayerEffects` resolves both effectTypes. - **Shader** masks the owner, derives `rowBase` from the nuke bit, offsets every row, and reuses the gradient/transition decode. - Bonus: the 12-bit owner mask lifts the old `R8UI` >255-player truncation. ## Schema / server / UI - Shared attributes schema renamed `TransportShipTrail…` → **`TrailEffectAttributesSchema`** (it's no longer ship-specific); `NukeTrailEffectSchema` added to `EffectSchema` + `CosmeticsSchema.effects`. `EFFECT_TYPES = [transportShipTrail, nukeTrail]`. - Server `Privilege`, selection, and the picker grid all iterate `EFFECT_TYPES`, so they handle the new type with **no per-type code**. - **Tabs:** the selection modal uses one tab per effectType (`BaseModal`'s native tabs); the **store's** EFFECTS panel gets an internal sub-tab bar (its top-level PACKS/EFFECTS tabs can't nest). Tabs are always present, so a type you own entirely still appears as an empty tab (previously the boat-trail section vanished from the store when you owned everything). ## Review A 3-angle adversarial review (bit-packing, type-ripple, GLSL/data-flow) **refuted** the correctness concerns — the R16UI format, masking, and block layout agree across `TrailManager` / shader / builder. The minor survivors (a preview that only resolved boat trails, stale comments) were fixed. ## Testing - `tsc --noEmit`, ESLint, Prettier, `build-prod` — all clean. - Schema/`Privilege` tests updated for `nukeTrail` (96 tests pass). - The GL trail + tab UI are visual — not yet verified in a running game. - The catalog (`cosmetics.json`, closed-source API) must ship the `effects.nukeTrail` block for the effect to appear in production. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3833bfb496 |
Remove ports-disabled modifier from public games (#4464)
## Summary Removes the `isPortsDisabled` ticket from `SPECIAL_MODIFIER_POOL` in `src/server/MapPlaylist.ts`, so the "ports disabled" modifier can no longer be randomly selected for public games. The reasoning is that many games with disabled ports were filling up very slowly or not at all (one game I saw had 3 players after 2 minutes) ## Notes - Private/custom lobbies are unaffected — the modifier itself still exists (type, schema, client UI), it's just no longer in the public auto-selection pool. - The now-unreachable public-game handling for `isPortsDisabled` (the `FULL_LAND_MAPS` exclusion and the `disabledUnits` branch) is left in place to keep the change surgical; it simply never triggers for public games now. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
cb7f721e73 |
Restore subscriptions in store and account modal (#4461)
Reverts
|
||
|
|
36b23d314f |
QoL: Add billions to money utils (#4460)
## Description: A simple QoL to handle cases where money surpasses $1B. I have seen this happen in both custom games and post-game, and generally looks nicer than just showing a thousand millions. <img width="242" height="71" alt="image" src="https://github.com/user-attachments/assets/abc9adb5-2e8f-485f-93ff-47a43a019d85" /> ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: bijx |
||
|
|
ae0d9f8d5e |
Adds "Levant" map (#4456)
## Description: Adds map of the Levant / Levantine Sea. This map is a square map with 3 sides being land, with an inner sea, which itself has a large island (Cyprus, slightly moved and resized for balancing purposes). After v32 added a trading buff, island players have been going crazy, with a map like this we could have crazy strong pirate players, which will make for fun and interesting gameplay. Nations are based on medieval states from the Crusades. Also adds more additional nations for a total of 62, for gamemodes like HumanVSNations and Solo. Also adds flags for the map https://github.com/user-attachments/assets/286432bd-011b-4716-85c9-20811777ff65 ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: tri.star1011 |
||
|
|
cd5f8a6998 |
Adds map "Tierra del Fuego" (featuring impassable terrain) (#4437)
## Description: Adds map of Tierra del Fuego, the southernmost tip of South America: https://en.wikipedia.org/wiki/Tierra_del_Fuego This map uses impassable terrain to stylize the borders of this map into a triangular shape, this zone is known as "end of the world" so i thought it would be visually fitting if the borders converged at the bottom of the map. 800k land tiles similar to Caucasus. This map has a lot of islands, incluiding the Falklands for people who enjoy island-plays, and also a chokepoint (the strait of Magallanes) for piracy. Also adds flags of Chilean and Argentinian provinces for the map and the menu. <img width="818" height="544" alt="image" src="https://github.com/user-attachments/assets/59812868-7514-4e03-86a2-072cf4013aeb" /> https://github.com/user-attachments/assets/acc15020-bd7a-49e0-a504-8575a1e74f75 ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: tri.star1011 |
||
|
|
0d2179f5f3 |
Input handler.ts rework (#4225)
> **Before opening a PR:** discuss new features on [Discord](https://discord.gg/K9zernJB5z) first, and file bugs or small improvements as [issues](https://github.com/openfrontio/OpenFrontIO/issues/new/choose). You must be assigned to an `approved` issue — unsolicited PRs will be auto-closed. **Add approved & assigned issue number here:** Resolves #4193 ## Description: Use activeKeys set in places where it is checking if a key is being pressed in a different way, and it makes more sense to use the activeKeys set. Make the overall code of the InputHandler.ts file more consistent and to make it easier to add new keybinds in the future. <img width="1920" height="1080" alt="Screenshot from 2026-06-13 20-49-56" src="https://github.com/user-attachments/assets/94f6f81c-7278-4bca-845c-2442b6caea39" /> ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: tktk1234567 |
||
|
|
200f276ab2 |
feat: transport-ship trail transition effect + animated store swatch (#4455)
## What Adds a second transport-ship trail style, **transition**, alongside the existing **gradient** (#4454). Where `gradient` paints a spatial band of colors along the trail, `transition` makes the whole trail one color at a time, cross-fading through the color list over time. ```json "attributes": { "type": "transition", "colors": ["#002aff", "#4805ff"], "frequency": 1 } ``` ## How - **Schema** ([CosmeticSchemas.ts](src/core/CosmeticSchemas.ts)) — `TransportShipTrailAttributesSchema` is now a discriminated union on `type`: - `gradient`: `{ colors, colorSize, movementSpeed }` - `transition`: `{ colors, frequency }` — `frequency` = color changes per second. - **Renderer** — the effect texture gained a `styleId` discriminator (row 1's alpha; 0 = gradient, 1 = transition), with the gradient scalars shifted down a row. - [WebGLFrameBuilder.ts](src/client/WebGLFrameBuilder.ts) encodes `styleId` + the style's scalars. - [trail.frag.glsl](src/client/render/gl/shaders/map-overlay/trail.frag.glsl): for `transition`, the trail color is `mix(colors[i], colors[i+1], fract(t))` with `i = floor(uTime · frequency) mod count` — one color step every `1/frequency` seconds. - **Store/picker swatch** ([EffectPreview.ts](src/client/components/EffectPreview.ts)) — the swatch is now a `<trail-swatch>` Lit element. For `transition` it cross-fades through the colors via the Web Animations API, timed to match the shader (each step `1/frequency` s); gradient/solid stay static. The animation is canceled on disconnect. ## Notes - Animation is render-only (local time) — no simulation/determinism impact. - `gradient` swatches remain static (they don't scroll like the in-game trail) — easy to add later if wanted. ## Testing - `tsc --noEmit`, ESLint, Prettier, `build-prod` all clean. - Schema tests cover the transition member (parse + required `frequency`); 95 tests pass. - The animated swatch is visual-only (no automated coverage) and not yet verified in a running store. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7c151e76ad |
feat: render transport-ship trail cosmetic as a gradient (#4454)
## What
Renders the `transportShipTrail` cosmetic effect in-game. Transport
ships already left a trail, but it was always drawn in the player's
**territory color** — this wires the selected effect through to the
renderer so the trail shows the player's chosen **gradient**.
## How
- **Per-player effect texture** (`RGBA32F`, mirrors the palette texture)
keyed by `smallID`, sampled by the trail fragment shader. Each row holds
a gradient color; spare alpha channels carry the color count,
`colorSize`, and `movementSpeed`.
- **Shader**
([trail.frag.glsl](src/client/render/gl/shaders/map-overlay/trail.frag.glsl))
cycles a flowing gradient through the color list: 1 color → flat, 2+ →
animated bands scrolling along the trail. No effect (count 0) falls back
to the territory color; alt-view keeps affiliation colors.
- **WebGLFrameBuilder** resolves each player's catalog attributes (the
in-game cosmetic is only `{ name, effectType }`; the style/colors live
in the catalog) and encodes them. Resolution is decoupled from the
first-seen palette path so it retries until the catalog loads, and
unparseable colors are dropped so bad catalog data degrades to the
territory color rather than rendering black.
## Schema
Collapses the trail attributes to a single gradient shape:
```ts
{ type: "gradient", colors: string[], colorSize: number, movementSpeed: number }
```
- `colors` — solid = one color, rainbow = the spectrum, gradient = two
or more.
- `colorSize` — band width (tiles per color band; `1` is the default, ~4
tiles).
- `movementSpeed` — scroll rate along the trail (tiles/sec; `0` =
static).
## Notes
- Animation is render-only (local time), no simulation/determinism
impact.
- The catalog (`cosmetics.json`, served by the closed-source API) must
ship effects in this `{ type: "gradient", colors, colorSize,
movementSpeed }` shape.
- Band thickness (`4.0` base in the shader) and the gradient frequency
are visual constants picked without in-game verification — easy to tune.
## Testing
- `tsc --noEmit`, ESLint, Prettier, `build-prod` all clean.
- Schema + Privilege test suites updated for the gradient shape (92
tests pass).
- Not yet visually verified in a running game (effect selection is
flare-gated).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
f4b47ce06c |
feat: add search bar to effects picker modal
Mirror the flag/pattern modals with a search input in the EffectsModal header. Filters the effects grid by name — matching either the raw effect id or its displayed label — and hides the Default tile while searching, the same way FlagInputModal hides its no-flag tile. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
dae129c6a3 |
replace leave lobby popup with custom popup (#4449)
## Description: old: <img width="1009" height="491" alt="image" src="https://github.com/user-attachments/assets/0b95877c-dac7-4025-bdfa-62ab6879d208" /> new: <img width="1017" height="561" alt="image" src="https://github.com/user-attachments/assets/cfb49b31-eb46-4d64-bd9e-3f25bb7cd0fb" /> ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: w.o.n |
||
|
|
e196d399b4 |
feat: Change factory icons from circles to hexagons (#4439)
Resolves #4413 ## Description: The factory and city icons are currently undistinguishable when zooming out, this PR changes the factory icon to be hexagon-shaped. It also increases the icon scale (now equal to port) to put some padding around. *Before* <img width="159" height="154" alt="Before" src="https://github.com/user-attachments/assets/2967fbc1-c775-4281-9592-6a52cbadd5a4" /> *After (with `scale = 1.0`)* <img width="133" height="135" alt="After (with `scale = 1.0`)" src="https://github.com/user-attachments/assets/f2bb9d8a-2f41-434a-b4df-9c1475f51cce" /> *After (with `scale = 1.08`)* <img width="192" height="191" alt="After (with `scale = 1.08`)" src="https://github.com/user-attachments/assets/c81026d2-f6fe-4115-a00e-e7490d342787" /> ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: lents |
||
|
|
a05ab1bd60 |
fix broken import... oops (#4448)
> **Before opening a PR:** discuss new features on [Discord](https://discord.gg/K9zernJB5z) first, and file bugs or small improvements as [issues](https://github.com/openfrontio/OpenFrontIO/issues/new/choose). You must be assigned to an `approved` issue — unsolicited PRs will be auto-closed. **Add approved & assigned issue number here:** Resolves #(issue number) ## Description: Describe the PR. ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: DISCORD_USERNAME |
||
|
|
6a884eba1b |
store popup (#4435)
## Description: change the generic popup: <img width="1095" height="540" alt="image" src="https://github.com/user-attachments/assets/94d2c120-5ec5-4838-b8b4-09d43b4e83f8" /> into a popup i added for clan system: <img width="1108" height="774" alt="image" src="https://github.com/user-attachments/assets/d7de1666-7667-4422-a1bd-03b90b4ff8ab" /> caps doesn't have a "buy" button: <img width="1141" height="803" alt="image" src="https://github.com/user-attachments/assets/d26dd397-1f14-4963-8ac8-afa5f32ed8ec" /> also works for win modal: <img width="1023" height="766" alt="image" src="https://github.com/user-attachments/assets/83f7bc87-0ecc-4470-b84d-c5783560d6a3" /> ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: w.o.n --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0f1a95cbeb |
Restore default terrain colors changed by #4391 (#4447)
## Description The terrain colors settings refactor (#4391) inadvertently changed the **default** land terrain colors. The PR moved the hardcoded terrain colors into configurable settings, but the new default hex values in `render-settings.json` — combined with the new shading math — no longer reproduced the original sand/plains/highland appearance. In particular the new shading formulas use each JSON color as a *base* and apply offsets differently (e.g. highland now adds `2*(magnitude-10)` instead of `2*magnitude`), so the default hex values needed to compensate. ## Fix Surgical, JSON-only change to the defaults so the new formulas produce **pixel-identical** output to the pre-#4391 code: | Terrain | Was (#4391) | Restored | | --- | --- | --- | | sand | `#CC9E9E` | `#CCCB9E` | | plains | `#BECD8A` | `#BEDC8A` | | highland | `#C8B78A` | `#DCCB9E` | | mountain | `#e6e6e6` | `#e6e6e6` (unchanged) | | ocean | `#4785b5` | `#4785b5` (unchanged) | The settings/UI machinery from #4391 stays intact and users can still override colors — only the defaults are corrected. Verified across all magnitude/shoreline combinations that the rendered land colors match the original output exactly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
bd9ef9a317 |
feat: effects cosmetic category (transport-ship trail) + UI (#4418)
## What Adds a new **`effects`** cosmetic category alongside `skins`/`flags`. Each effect is discriminated by **`effectType`** (only `transportShipTrail` today), whose visual config lives in **`attributes`** (`solid` / `rainbow` / `pulse` / `gradient`). Schema matches the production cosmetics.json shape exactly (incl. the `url` field). **This PR is UI + taxonomy only — the in-game WebGL trail rendering is intentionally deferred.** ## UI - **Store** gains an **"Effects"** tab. - **Home page** gains an **"Effects"** button opening a picker modal. - Both render effects **grouped by `effectType` with a sub-header per type**, via a shared `<effects-grid>` Lit element (`mode="select"` for the picker, `mode="purchase"` for the store). The picker shows owned effects + a Default tile and persists per-type; the store shows purchasable effects. ## Data flow - Ownership via `effect:*` / `effect:<name>` flares (reuses `cosmeticRelationship`). - Selection is a per-`effectType` map persisted in UserSettings (`settings.effects`). - Server validates in `isEffectAllowed`, wired into `isAllowed`. - `getPlayerCosmeticsRefs` / `getPlayerCosmetics` resolve effects the same way as skins/flags (kept-on-fetch-failure, server is authority). ## Tests - `tsc --noEmit`, ESLint, Prettier clean; full suite green. - New: `CosmeticSchemas` parse tests (incl. parsing the **real** `read_transport_trail` entry), `UserSettings` per-type selection, and `Privilege` effect validation. ## Notes / follow-ups - The effect's display label shows **"Boat Trail"** for the `transportShipTrail` type (friendlier than the id). - Closed-source API gap: `/shop/purchase` (`purchaseWithCurrency`) needs to learn `"effect"` for **currency** purchase of effects; the **dollar/product** purchase path already works. Client types were widened accordingly. - In-game wake rendering can be ported from #4416. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ccd0745ad4 |
Prevent AI from placing ports on small lakes 🚢 (#4429)
## Description: AI nations were placing ports on small decorative ponds scattered across maps (Missisipi for example), wasting structure slots on strategically useless water bodies. This fix adds a water component size check to the port placement logic so the AI skips lakes that are too small for meaningful port use. We already had a check for available trade partners, but trading in small lakes is usually stupid. **How it works:** - `ConnectedComponents` now tracks component sizes during its existing flood-fill (zero extra cost - counts tiles as they're visited) - `AbstractGraph`, `WaterManager`, and the `Game` interface expose `getWaterComponentSize(tile)` so callers can query the size of any water body - `NationStructureBehavior.randCoastalTileArray()` filters out non-ocean water components below `MIN_PORT_WATER_COMPONENT_SIZE` (3000 minimap tiles, ~12000 full-map tiles) - Ocean tiles bypass the check entirely since they're always large enough ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
06d505ebc9 |
feat: Expand maps button (#4431)
## Description: QoL fix w.r.t. "All" maps category. Maps used to all be shown in a big list, now the category sections need to be expanded one by one. Categories are super clean and useful, but to visually see and pick maps when you're not certain which one you want to play becomes challenging (to click and expand each category manually). This just adds a simple "Expand All" and "Collapse All" button to the all category list on Solo and Private Match screens. https://github.com/user-attachments/assets/e5d7a754-a6b6-461c-b039-7b6a8d3bee46 ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: bijx |
||
|
|
966dcf47a5 |
removes the "subscribe" and "purchase" text (#4436)
## Description: removes the "subscribe" and "purchase" text: <img width="988" height="496" alt="image" src="https://github.com/user-attachments/assets/9566b450-0943-4684-8321-8024422bbd96" /> <img width="993" height="505" alt="image" src="https://github.com/user-attachments/assets/f26bfd99-661d-48e3-beb3-e3e5d212e6f1" /> <img width="1002" height="511" alt="image" src="https://github.com/user-attachments/assets/c8f2aadf-15d1-4e29-8142-f684c6e492f0" /> this is what it looks like if you're subbed to something now: <img width="997" height="491" alt="image" src="https://github.com/user-attachments/assets/5f011213-7ced-4a64-860e-45a6b0a7418f" /> ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: w.o.n |
||
|
|
d42b095304 |
fix: Remove FFA collusion warnings on replay and ranked (#4414) (#4434)
> **Before opening a PR:** discuss new features on [Discord](https://discord.gg/K9zernJB5z) first, and file bugs or small improvements as [issues](https://github.com/openfrontio/OpenFrontIO/issues/new/choose). You must be assigned to an `approved` issue — unsolicited PRs will be auto-closed. **Add approved & assigned issue number here:** Resolves #4414 ## Description: The 2 lines added check whether it's a replay or a ranked game. If so, don't show the FFA collusion warning ## Please complete the following: - [X] I have added screenshots for all UI updates (couldn't sign in via the dev server) - [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 ## Please put your Discord username so you can be contacted if a bug or regression is found: lents (not in the development server yet, sent request to join) |
||
|
|
c622e8581c |
collapse skins under one banner (#4432)
## Description: merges skins under one item with a circular button below it: <img width="877" height="647" alt="image" src="https://github.com/user-attachments/assets/a405ba34-a970-4e8c-9287-fe0055d6a02e" /> ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: w.o.n |