Skip to content

Conversation

@mepoohsta
Copy link
Contributor

@mepoohsta mepoohsta commented Nov 3, 2025

Description:

#2352

This is my first PR in this project, and I’ll continue refining it based on feedback.

image image image image

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

Please put your Discord username so you can be contacted if a bug or regression is found:

nikolaj_mykola

@mepoohsta mepoohsta requested review from a team as code owners November 3, 2025 20:18
@CLAassistant
Copy link

CLAassistant commented Nov 3, 2025

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 3, 2025

Walkthrough

Adds a configurable random-spawn feature: UI toggles, translations, config plumbing, seeded PlayerSpawner that chooses valid spawn tiles, executor integration to emit spawn executions during init, client flow updates for go-to-player, server config handling, and unit tests.

Changes

Cohort / File(s) Summary
Localization
resources/lang/debug.json, resources/lang/en.json
Added single_modal.random_spawn, host_modal.random_spawn, heads_up_message.random_spawn; added error_modal.spawn_failed translation and adjusted adjacent entries.
Lobby & Single‑player UI
src/client/HostLobbyModal.ts, src/client/SinglePlayerModal.ts
Added randomSpawn: boolean state, checkbox UI, handleRandomSpawnChange handlers, and included randomSpawn in putGameConfig / game start payload.
Client game flow
src/client/ClientGameRunner.ts
Added go-to-player flow for random spawn on start, suppressed sending spawn intent when random spawn enabled, and cleanup for goToPlayerTimeout on stop.
HUD rendering
src/client/graphics/layers/HeadsUpMessage.ts
Show heads_up_message.random_spawn when random spawn enabled; otherwise choose_spawn.
Schema & config API
src/core/Schemas.ts, src/core/configuration/Config.ts, src/core/configuration/DefaultConfig.ts
Added randomSpawn boolean to GameConfig schema; added isRandomSpawn() to Config interface and DefaultConfig.
Spawn execution
src/core/execution/ExecutionManager.ts, src/core/execution/utils/PlayerSpawner.ts
Added Executor.spawnPlayers() and new PlayerSpawner (seeded PRNG by gameID) with random tile selection, land/ownership/border/distance validation, producing SpawnExecution[].
Game init
src/core/GameRunner.ts
Inserted spawnPlayers execution into initialization when isRandomSpawn() true, before bot spawning.
Server config & playlist
src/server/GameManager.ts, src/server/GameServer.ts, src/server/MapPlaylist.ts
Added randomSpawn field (default false) to created game config and MapPlaylist; GameServer.updateGameConfig accepts randomSpawn.
Tests & test setup
tests/util/Setup.ts, tests/core/execution/utils/PlayerSpawner.test.ts
Added default randomSpawn to test setup and new PlayerSpawner unit tests validating land selection, distance constraints, and crowded-map failure behavior.

Sequence Diagram(s)

sequenceDiagram
    participant User as Player UI
    participant Modal as Host/Single Modal
    participant Server as Game Server
    participant Client as ClientGameRunner
    participant Executor as ExecutionManager
    participant Spawner as PlayerSpawner
    participant Game as Game State

    User->>Modal: toggle randomSpawn
    Modal->>Server: putGameConfig({ randomSpawn: true })
    Server-->>Modal: ack

    Note over Client,Executor: Game start (config.randomSpawn = true)
    Client->>Executor: request spawnPlayers()
    Executor->>Spawner: new PlayerSpawner(game, gameID).spawnPlayers()
    Spawner->>Spawner: seed PRNG with gameID
    loop per human player
        Spawner->>Spawner: pick random tile
        alt valid tile (land, unowned, not border, >= MIN_DISTANCE)
            Spawner->>Executor: add SpawnExecution(player, tile)
        else none found after tries
            Spawner->>Spawner: log warning
        end
    end
    Spawner-->>Executor: SpawnExecution[]
    Executor-->>Client: apply spawn executions
    Client->>Client: emit GoToPlayerEvent (local player)
    Client-->>User: show assigned spawns
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay extra attention to:
    • PlayerSpawner: seeded PRNG, MAX_SPAWN_TRIES, MIN_SPAWN_DISTANCE, and tile validation correctness.
    • Integration points: Executor.spawnPlayers usage in GameRunner and Application of SpawnExecution.
    • ClientGameRunner: timeout flow, spawn-failed modal path, and ensuring no race with normal spawn intent.

Possibly related PRs

Suggested labels

Feature - Simulation, Feature - Frontend, Translation

Suggested reviewers

  • scottanderson
  • evanpelle

Poem

🎲 A seeded roll, a map to comb,

Tiles chosen so each player finds home,
Checks for land, borders, and space,
Random spawns placed with careful grace,
Tests confirm the scattered race.

Pre-merge checks

✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Random spawn' directly describes the main feature added across the entire changeset: a random player spawn functionality.
Description check ✅ Passed The description references the related issue, provides UI screenshots, and confirms checklist items including localization, tests, and testing coverage.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5f036e4 and f86967f.

📒 Files selected for processing (1)
  • src/client/ClientGameRunner.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/client/ClientGameRunner.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
resources/lang/eo.json (1)

321-321: Unrelated change to performance overlay description.

Line 321 modifies performance_overlay_desc by adding "D dum la ludo por ŝalti." This change doesn't appear related to the random spawn feature. If this is an intentional fix or improvement, it would be better to:

  1. Call it out in the PR description, or
  2. Submit it as a separate PR for clarity

If it's unintentional, please revert this change.

♻️ Duplicate comments (2)
resources/lang/fi.json (1)

138-138: Non-English locale files should not be updated in this PR.

Same issue as other locale files - fi.json should only be updated in dedicated translation PRs by Aotumori, not in regular feature PRs.

Based on learnings

Also applies to: 235-235, 587-588

resources/lang/gl.json (1)

136-136: Non-English locale files should not be updated in this PR.

Same issue - gl.json should only be updated in dedicated translation PRs by Aotumori.

Based on learnings

Also applies to: 221-221, 549-550

🧹 Nitpick comments (2)
src/core/execution/PlayerSpawner.ts (2)

63-63: Fix typo in comment.

"nornally" should be "normally".


26-52: Consider making spawn spacing configurable.

The hardcoded 50-pixel minimum distance between spawns might not be appropriate for all map sizes. For smaller maps, spawns might be too crowded; for larger maps, they might be unnecessarily far apart.

Consider deriving the spacing from map dimensions or making it a configuration parameter.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ce1835 and 080408a.

📒 Files selected for processing (48)
  • resources/lang/ar.json (3 hunks)
  • resources/lang/bg.json (3 hunks)
  • resources/lang/bn.json (3 hunks)
  • resources/lang/cs.json (3 hunks)
  • resources/lang/da.json (3 hunks)
  • resources/lang/de.json (3 hunks)
  • resources/lang/debug.json (3 hunks)
  • resources/lang/en.json (3 hunks)
  • resources/lang/eo.json (3 hunks)
  • resources/lang/es.json (3 hunks)
  • resources/lang/fi.json (3 hunks)
  • resources/lang/fr.json (3 hunks)
  • resources/lang/gl.json (3 hunks)
  • resources/lang/he.json (3 hunks)
  • resources/lang/hi.json (3 hunks)
  • resources/lang/hu.json (3 hunks)
  • resources/lang/it.json (3 hunks)
  • resources/lang/ja.json (3 hunks)
  • resources/lang/ko.json (3 hunks)
  • resources/lang/mk.json (3 hunks)
  • resources/lang/nl.json (3 hunks)
  • resources/lang/pl.json (3 hunks)
  • resources/lang/pt-BR.json (3 hunks)
  • resources/lang/pt-PT.json (3 hunks)
  • resources/lang/ru.json (3 hunks)
  • resources/lang/sh.json (3 hunks)
  • resources/lang/sk.json (3 hunks)
  • resources/lang/sl.json (3 hunks)
  • resources/lang/sv-SE.json (3 hunks)
  • resources/lang/tp.json (3 hunks)
  • resources/lang/tr.json (3 hunks)
  • resources/lang/uk.json (3 hunks)
  • resources/lang/zh-CN.json (3 hunks)
  • src/client/ClientGameRunner.ts (1 hunks)
  • src/client/HostLobbyModal.ts (4 hunks)
  • src/client/SinglePlayerModal.ts (4 hunks)
  • src/client/graphics/layers/HeadsUpMessage.ts (1 hunks)
  • src/core/GameRunner.ts (1 hunks)
  • src/core/Schemas.ts (1 hunks)
  • src/core/configuration/Config.ts (1 hunks)
  • src/core/configuration/DefaultConfig.ts (1 hunks)
  • src/core/execution/ExecutionManager.ts (2 hunks)
  • src/core/execution/PlayerSpawner.ts (1 hunks)
  • src/core/game/GameView.ts (1 hunks)
  • src/server/GameManager.ts (1 hunks)
  • src/server/GameServer.ts (1 hunks)
  • src/server/MapPlaylist.ts (1 hunks)
  • tests/util/Setup.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (13)
📚 Learning: 2025-10-20T20:15:28.858Z
Learnt from: sambokai
Repo: openfrontio/OpenFrontIO PR: 2225
File: src/core/execution/FakeHumanExecution.ts:51-51
Timestamp: 2025-10-20T20:15:28.858Z
Learning: In src/core/execution/FakeHumanExecution.ts, game balance constants like MIRV_COOLDOWN_TICKS, MIRV_HESITATION_ODDS, VICTORY_DENIAL_TEAM_THRESHOLD, VICTORY_DENIAL_INDIVIDUAL_THRESHOLD, and STEAMROLL_CITY_GAP_MULTIPLIER are experimental tuning parameters subject to frequent change during balance testing. Do not flag changes to these values as issues or compare them against previous values.

Applied to files:

  • src/core/configuration/Config.ts
  • src/client/HostLobbyModal.ts
  • src/client/SinglePlayerModal.ts
  • tests/util/Setup.ts
  • src/core/GameRunner.ts
📚 Learning: 2025-06-02T14:27:37.609Z
Learnt from: andrewNiziolek
Repo: openfrontio/OpenFrontIO PR: 1007
File: resources/lang/de.json:115-115
Timestamp: 2025-06-02T14:27:37.609Z
Learning: For OpenFrontIO project: When localization keys are renamed in language JSON files, the maintainers separate technical changes from translation content updates. They wait for community translators to update the actual translation values rather than attempting to translate in the same PR. This allows technical changes to proceed while ensuring accurate translations from native speakers.

Applied to files:

  • resources/lang/bn.json
  • resources/lang/fi.json
  • resources/lang/de.json
  • resources/lang/uk.json
  • resources/lang/he.json
  • resources/lang/mk.json
  • resources/lang/pt-PT.json
  • resources/lang/sl.json
  • resources/lang/tr.json
  • resources/lang/ko.json
  • resources/lang/en.json
  • resources/lang/eo.json
  • resources/lang/da.json
  • resources/lang/zh-CN.json
  • resources/lang/sh.json
  • resources/lang/debug.json
📚 Learning: 2025-08-16T10:52:08.292Z
Learnt from: TheGiraffe3
Repo: openfrontio/OpenFrontIO PR: 884
File: resources/lang/en.json:456-461
Timestamp: 2025-08-16T10:52:08.292Z
Learning: In OpenFrontIO, translation files in resources/lang/*.json (except en.json) should not be updated in regular PRs. Only dedicated translation PRs titled "mls" and made by Aotumori should update non-English locale files. Regular PRs should only update en.json when adding or modifying translation keys.

Applied to files:

  • resources/lang/bn.json
  • resources/lang/fi.json
  • resources/lang/sl.json
  • resources/lang/tr.json
  • resources/lang/ko.json
  • resources/lang/en.json
  • resources/lang/eo.json
📚 Learning: 2025-08-12T00:31:50.144Z
Learnt from: scottanderson
Repo: openfrontio/OpenFrontIO PR: 1752
File: src/core/game/Game.ts:750-752
Timestamp: 2025-08-12T00:31:50.144Z
Learning: In the OpenFrontIO codebase, changes to the PlayerInteraction interface (like adding canDonateGold and canDonateTroops flags) do not require corresponding updates to src/core/Schemas.ts or server serialization code.

Applied to files:

  • src/core/Schemas.ts
  • src/client/SinglePlayerModal.ts
📚 Learning: 2025-06-09T02:20:43.637Z
Learnt from: VariableVince
Repo: openfrontio/OpenFrontIO PR: 1110
File: src/client/Main.ts:293-295
Timestamp: 2025-06-09T02:20:43.637Z
Learning: In src/client/Main.ts, during game start in the handleJoinLobby callback, UI elements are hidden using direct DOM manipulation with classList.add("hidden") for consistency. This includes modals, buttons, and error divs. The codebase follows this pattern rather than using component APIs for hiding elements during game transitions.

Applied to files:

  • src/client/HostLobbyModal.ts
  • src/client/SinglePlayerModal.ts
📚 Learning: 2025-10-21T20:06:04.823Z
Learnt from: Saphereye
Repo: openfrontio/OpenFrontIO PR: 2233
File: src/client/HostLobbyModal.ts:891-891
Timestamp: 2025-10-21T20:06:04.823Z
Learning: For the HumansVsNations game mode in `src/client/HostLobbyModal.ts` and related files, the implementation strategy is to generate all nations and adjust their strength for balancing, rather than limiting lobby size based on the number of available nations on the map.

Applied to files:

  • src/client/HostLobbyModal.ts
  • src/server/MapPlaylist.ts
📚 Learning: 2025-05-31T18:15:03.445Z
Learnt from: 1brucben
Repo: openfrontio/OpenFrontIO PR: 977
File: src/core/execution/AttackExecution.ts:123-125
Timestamp: 2025-05-31T18:15:03.445Z
Learning: The removeTroops function in PlayerImpl.ts already prevents negative troop counts by using minInt(this._troops, toInt(troops)) to ensure it never removes more troops than available.

Applied to files:

  • src/client/SinglePlayerModal.ts
📚 Learning: 2025-07-12T08:41:35.101Z
Learnt from: Aotumuri
Repo: openfrontio/OpenFrontIO PR: 1357
File: resources/lang/de.json:523-540
Timestamp: 2025-07-12T08:41:35.101Z
Learning: In OpenFrontIO project localization files, always check the en.json source file before flagging potential spelling errors in other language files, as some keys may intentionally use non-standard spellings that need to be consistent across all translations.

Applied to files:

  • resources/lang/en.json
  • resources/lang/eo.json
📚 Learning: 2025-08-28T22:47:31.406Z
Learnt from: BrewedCoffee
Repo: openfrontio/OpenFrontIO PR: 1957
File: tests/core/executions/PlayerExecution.test.ts:16-39
Timestamp: 2025-08-28T22:47:31.406Z
Learning: In test files, PlayerExecution instances must be manually registered with game.addExecution() because the setup utility doesn't automatically register SpawnExecution, which would normally handle this during the spawn phase in a real game.

Applied to files:

  • src/core/execution/ExecutionManager.ts
  • src/core/GameRunner.ts
  • src/core/execution/PlayerSpawner.ts
📚 Learning: 2025-08-23T08:03:44.914Z
Learnt from: ElMelchizedek
Repo: openfrontio/OpenFrontIO PR: 1876
File: src/core/execution/FakeHumanExecution.ts:547-592
Timestamp: 2025-08-23T08:03:44.914Z
Learning: In OpenFrontIO, FakeHumanExecution is used for AI-controlled nations that simulate human behavior, which is distinct from PlayerType.Bot. FakeHumanExecution players are not PlayerType.Bot - they represent more sophisticated AI that mimics human nation behavior, while PlayerType.Bot represents basic AI players.

Applied to files:

  • src/core/execution/ExecutionManager.ts
📚 Learning: 2025-10-08T17:14:49.369Z
Learnt from: Foorack
Repo: openfrontio/OpenFrontIO PR: 2141
File: src/client/ClientGameRunner.ts:228-234
Timestamp: 2025-10-08T17:14:49.369Z
Learning: In `ClientGameRunner.ts`, the `myPlayer` field is always set when `shouldPreventWindowClose()` is called, so the null check in that method is sufficient without needing to fetch it again from `gameView.playerByClientID()`.

Applied to files:

  • src/client/ClientGameRunner.ts
  • src/core/GameRunner.ts
📚 Learning: 2025-10-08T17:14:49.369Z
Learnt from: Foorack
Repo: openfrontio/OpenFrontIO PR: 2141
File: src/client/ClientGameRunner.ts:228-234
Timestamp: 2025-10-08T17:14:49.369Z
Learning: For the window close confirmation feature in `ClientGameRunner.ts`, the troop count requirement (>10,000 troops) from issue #2137 was intentionally removed because it was arbitrary and troop count can be reported as low despite having significant land. The confirmation now triggers for any alive player regardless of troop count.

Applied to files:

  • src/client/ClientGameRunner.ts
  • src/core/GameRunner.ts
📚 Learning: 2025-08-23T07:48:19.060Z
Learnt from: ElMelchizedek
Repo: openfrontio/OpenFrontIO PR: 1876
File: src/core/execution/FakeHumanExecution.ts:470-473
Timestamp: 2025-08-23T07:48:19.060Z
Learning: In FakeHumanExecution.ts DefensePost placement logic, returning -Infinity from structureSpawnTileValue when no sampled border tiles neighbor enemies is intentional. The logic samples up to 50 border tiles as a heuristic - if none are adjacent to enemies, it assumes DefensePost placement is unnecessary and aborts the entire placement attempt rather than continuing to evaluate individual tiles.

Applied to files:

  • src/client/ClientGameRunner.ts
🧬 Code graph analysis (5)
src/client/HostLobbyModal.ts (2)
src/client/Utils.ts (1)
  • translateText (92-147)
src/client/LangSelector.ts (1)
  • translateText (254-274)
src/client/SinglePlayerModal.ts (1)
src/client/Utils.ts (1)
  • translateText (92-147)
src/core/execution/ExecutionManager.ts (2)
src/core/game/Game.ts (1)
  • Execution (351-356)
src/core/execution/PlayerSpawner.ts (1)
  • PlayerSpawner (8-74)
src/server/GameServer.ts (2)
src/core/configuration/DefaultConfig.ts (1)
  • gameConfig (266-268)
src/server/MapPlaylist.ts (1)
  • gameConfig (79-105)
src/core/execution/PlayerSpawner.ts (6)
src/core/PseudoRandom.ts (1)
  • PseudoRandom (3-76)
src/core/execution/SpawnExecution.ts (1)
  • SpawnExecution (7-61)
src/core/game/Game.ts (1)
  • Game (664-748)
src/core/Schemas.ts (1)
  • GameID (25-25)
src/core/Util.ts (1)
  • simpleHash (64-72)
src/core/game/GameMap.ts (1)
  • TileRef (3-3)
🔇 Additional comments (44)
resources/lang/hu.json (1)

137-137: Translation additions are properly placed and well-formed.

The three new keys for random spawn functionality are correctly positioned across the appropriate sections (single_modal, host_modal, heads_up_message) with Hungarian translations that are grammatically sound and contextually appropriate. The JSON structure remains valid with proper syntax.

Note: Verify that all 31 translation files mentioned in the PR summary have been updated with these same keys to maintain consistency across supported languages.

Also applies to: 238-238, 593-594

resources/lang/debug.json (1)

97-97: Localization structure looks good—verify en.json has actual translations.

The debug.json changes follow the reference-file pattern with self-referential dot-notation values. Placement is logical (e.g., random_spawn slots between related options like instant_build and infinite_gold), and naming is consistent with existing keys.

However, this file only defines the structure. Confirm that resources/lang/en.json contains the actual English translations for:

  • single_modal.random_spawn
  • host_modal.random_spawn
  • heads_up_message.spawn_selected_automatically

Based on the PR checklist ("Processed any text displayed to the user through translateText() and added it to en.json"), these should already be present in en.json and not just stub keys.

Also applies to: 150-150, 171-172

resources/lang/pt-PT.json (2)

136-136: JSON structure is well-formed across modal sections.

The additions to single_modal and host_modal follow proper syntax, maintain consistent key naming (random_spawn), and align with the existing structure of each section.

Also applies to: 221-221


549-550: Verify Portuguese translations with native speakers.

While the JSON structure is syntactically correct and the translations appear reasonable ("Escolha um local de início" for spawn selection, "O ponto de aparecimento é selecionado automaticamente" for automatic spawning), verify accuracy with native Portuguese speakers or community translators. Based on learnings from prior localization work, maintainers have historically preferred deferring translation content to community contributors rather than translating in the same PR.

resources/lang/sh.json (1)

101-101: Verify translation workflow alignment and review translation accuracy.

The PR adds complete Serbo-Croatian translations for the random spawn feature across single_modal (line 101), host_modal (line 164), and a new heads_up_message object (lines 185–188). According to learnings from the project's translation practices, maintainers typically separate technical key additions from translation content updates, allowing community translators to provide accurate translations rather than including them in the same PR.

Please clarify:

  1. Does this approach (full translations included in the PR) align with the project's current localization workflow?
  2. Have these Serbo-Croatian translations been reviewed by native speakers or the project's translation team?

If the project prefers community-driven translation updates, consider deferring the translation values to be added after the PR merges, or mark them for review by qualified translators.

Based on learnings

Also applies to: 164-164, 185-188

resources/lang/uk.json (1)

137-137: Clarify the localization update approach and verify scope.

Based on learnings from the project, maintainers typically separate technical changes (adding localization keys) from translation content updates, allowing community translators to provide accurate translations in their native languages. This PR includes full Ukrainian translations directly.

Additionally, the host_badge key at line 239–240 appears unrelated to the "random spawn" feature described in the PR title. Please clarify whether it's intentional or should be addressed in a separate PR.

Suggested approach (if aligned with project practice):

Consider replacing the Ukrainian translations with English placeholders or empty strings, allowing community translators to provide accurate Ukrainian translations later:

- "random_spawn": "Випадкове появлення"
+ "random_spawn": "Random spawn"

and

- "choose_spawn": "Виберіть початкове розташування",
- "spawn_selected_automatically": "Точка появи вибирається автоматично. Підготуйтеся до бою"
+ "choose_spawn": "Choose spawn location",
+ "spawn_selected_automatically": "Spawn selected automatically. Get ready for battle"

Please verify this aligns with the project's localization workflow.

[Based on learnings]

Also applies to: 239-241, 593-594

resources/lang/pl.json (3)

138-138: Polish translations for random spawn toggle are clear and consistent.

Both instances of "random_spawn": "Losowe pojawienie" read naturally in Polish and maintain consistency between the single-player and host lobby modes.

Also applies to: 235-235


587-588: Heads-up messages are appropriately translated and contextually suitable.

The two new messages convey their purpose clearly:

  • choose_spawn instructs the player to select a spawn location (clear and direct)
  • spawn_selected_automatically warns of automatic selection and readiness for battle (appropriate tone and urgency)

Both follow the existing message style in the file and use natural, idiomatic Polish.


138-138: Verify that all translation files were updated consistently.

The PR summary mentions this change spans ~31 translation files. Please confirm:

  • All language files contain the same new keys (random_spawn, spawn_selected_automatically)
  • All choose_spawn keys were updated with trailing commas (if not present before)
  • No translation keys are missing or inconsistent across locales

Also applies to: 235-235, 587-588

resources/lang/pt-BR.json (2)

94-94: Translations are clear and appropriately placed.

The "Spawn aleatório" translation in both single_modal and host_modal sections is idiomatic Portuguese and correctly positioned.

Also applies to: 154-154


181-184: Running a final consistency verification across different regions:

All 33 language files contain the heads_up_message section with consistent structure.

Verification confirms that all language files in resources/lang/ have been updated with the new heads_up_message section. The structure is identical across all files, with each containing the two required keys: choose_spawn and spawn_selected_automatically, each with appropriate translations. Coverage is 100% (33 of 33 files) with no missing entries. The Portuguese-Brazil translation in pt-BR.json is accurate and consistent with translations in other languages.

resources/lang/cs.json (1)

108-108: Translation additions look good, but check the translation update policy.

The new translation keys for random spawn are properly formatted and consistently added. However, based on project learnings, non-English translation files are typically updated only in dedicated translation PRs (titled "mls" by Aotumori). Since this is your first PR, you might want to confirm with the maintainers whether these translations should be:

  1. Included here as part of the feature rollout, or
  2. Added to en.json only, with other languages updated in a follow-up translation PR

The actual translation content looks correct for the feature!

Based on learnings

Also applies to: 153-156, 195-196

resources/lang/ru.json (1)

137-137: Translation additions are consistent.

Same note as for the Czech translation file - the changes look correct, but please verify with maintainers about the translation update policy for non-English locale files in feature PRs.

Based on learnings

Also applies to: 238-238, 593-594

resources/lang/fr.json (1)

137-137: French translations look accurate.

The translations are well-formed and the French text appears appropriate ("Apparition aléatoire" correctly conveys "random spawn"). Same verification request as other translation files regarding the update policy.

Based on learnings

Also applies to: 238-238, 593-594

resources/lang/eo.json (1)

131-131: Random spawn translations consistent with other files.

As with other translation files, please verify the translation update policy with maintainers.

Based on learnings

Also applies to: 238-238, 593-594

src/server/GameServer.ts (1)

118-120: LGTM - Config update follows established pattern.

The randomSpawn config handling is consistent with all other config fields in updateGameConfig. Clean implementation.

src/client/ClientGameRunner.ts (1)

404-405: LGTM - Correctly prevents manual spawn during random spawn mode.

The additional !this.gameView.isRandomSpawn() condition properly blocks manual spawn selection when random spawn is enabled. The logic is sound: players should only be able to click to spawn when random spawn is disabled.

src/core/configuration/DefaultConfig.ts (1)

340-342: LGTM - Config accessor follows class pattern.

The randomSpawn() method is a straightforward accessor that matches the style and placement of surrounding config methods. Clean and consistent.

src/server/MapPlaylist.ts (1)

98-98: LGTM - Sensible default for public games.

Setting randomSpawn: false as the default for public game playlists is appropriate. This preserves the existing manual spawn behavior for public games while allowing private games and single-player to opt into random spawn.

src/core/game/GameView.ts (1)

647-649: LGTM! Clean accessor method.

The isRandomSpawn() method follows the established pattern of other configuration accessors in this class. Implementation is straightforward and delegates correctly to the underlying config.

src/core/configuration/Config.ts (1)

91-91: LGTM! Interface extension follows conventions.

The randomSpawn() method signature is correctly added to the Config interface and follows the established pattern for boolean configuration flags.

src/server/GameManager.ts (1)

59-59: LGTM! Safe default value.

Setting randomSpawn: false as the default is the correct choice for backward compatibility. This ensures existing behavior is preserved unless explicitly enabled.

resources/lang/zh-CN.json (3)

137-137: Translation addition looks correct.

The random_spawn key is properly added to the single_modal section with appropriate Chinese translation.


239-240: Translation addition looks correct.

The random_spawn key is properly added to the host_modal section. JSON structure is valid with correct comma placement.


593-594: New heads-up message translations added.

The new heads_up_message section correctly includes both spawn-related keys with appropriate Chinese translations.

resources/lang/sk.json (3)

136-136: Translation addition looks correct.

The random_spawn key is properly added to the single_modal section with Slovak translation using consistent terminology.


221-221: Translation addition looks correct.

The random_spawn key is properly added to the host_modal section with the same consistent Slovak terminology.


549-550: Heads-up message translations added correctly.

The heads_up_message section includes both required keys with appropriate Slovak translations.

tests/util/Setup.ts (1)

71-71: LGTM! Test setup properly initialized.

Adding randomSpawn: false before the spread operator is correct. This provides a sensible default while allowing test-specific overrides via _gameConfig.

src/client/graphics/layers/HeadsUpMessage.ts (1)

43-45: LGTM! Clean conditional rendering.

The ternary operator correctly switches between spawn messages based on the random spawn configuration. The implementation is clean and uses the appropriate translation keys.

src/core/GameRunner.ts (1)

103-105: No issues found—code is correct.

Script results confirm:

  1. ExecutionManager.spawnPlayers() correctly returns Execution[] (with SpawnExecution[] as concrete implementation)
  2. PlayerSpawner.spawnPlayers() filters for PlayerType.Human only, then spawns before bots
  3. Spawn order is intentional and working as designed

The code matches the intended behavior—human players spawn first, then bots if configured.

src/core/Schemas.ts (1)

170-170: LGTM! Schema addition is clean and well-placed.

The randomSpawn boolean field fits naturally alongside the other game configuration options. The placement between instantBuild and maxPlayers is logical, and using a required boolean (without .optional()) is consistent with similar flags like infiniteGold and infiniteTroops.

resources/lang/nl.json (1)

138-138: Translation workflow concern: non-English locale updates

Based on learnings, the OpenFrontIO project follows a specific translation workflow: regular PRs should only update en.json when adding new translation keys, while non-English locale files (tr.json, de.json, bg.json, etc.) should be updated separately in dedicated translation PRs (typically titled "mls" and handled by community translators).

This PR updates 7 non-English locale files directly. While the translation keys are structurally consistent across all files, this approach differs from the established workflow. Consider:

  • Confirming that en.json was updated with these keys (it's not in the current review scope)
  • Checking with maintainers whether updating all locales in the same PR is acceptable for this feature, or if these translation updates should be extracted into a separate PR

The translation structure itself looks correct—all files consistently add single_modal.random_spawn, host_modal.random_spawn, and heads_up_message entries that align with the UI changes described in the PR.

Based on learnings

Note: This comment applies to all translation files in this PR: tr.json, de.json, bg.json, tp.json, it.json, ja.json, and nl.json.

resources/lang/en.json (1)

138-138: LGTM! Translation keys properly added.

The new translation keys for random spawn are correctly added to en.json, which is the appropriate file to update in regular PRs. The keys are well-named and consistent across the single-player and host lobby contexts.

Based on learnings

Also applies to: 266-266, 663-664

src/core/execution/ExecutionManager.ts (2)

23-23: LGTM! Import added correctly.

The PlayerSpawner import is properly added and follows the existing import structure.


135-137: LGTM! Method follows existing patterns.

The spawnPlayers() method follows the same pattern as the existing spawnBots() method, maintaining consistency in the codebase. The delegation to PlayerSpawner keeps concerns separated cleanly.

src/client/HostLobbyModal.ts (4)

51-51: LGTM! State properly initialized.

The randomSpawn state is correctly declared with a sensible default of false, consistent with other boolean options in the modal.


394-408: LGTM! UI option follows existing patterns.

The random spawn checkbox follows the same structure and styling as other options in the modal. The translation key is properly used, and the selected class binding is correct.


688-691: LGTM! Event handler is consistent.

The handleRandomSpawnChange handler follows the same pattern as other checkbox handlers in the component, properly updating state and calling putGameConfig.


774-774: LGTM! Config propagation is correct.

The randomSpawn value is properly included in the game configuration payload sent to the server, consistent with other config options.

src/client/SinglePlayerModal.ts (4)

47-47: LGTM! State properly initialized.

The randomSpawn state is correctly declared with a default of false, consistent with the HostLobbyModal implementation.


297-311: LGTM! UI option consistent with host modal.

The random spawn checkbox follows the same structure as in HostLobbyModal and is consistent with other options in this modal. Good consistency across the codebase.


460-462: LGTM! Event handler follows pattern.

The handleRandomSpawnChange handler is consistent with other checkbox handlers in the component.


587-587: LGTM! Config value properly propagated.

The randomSpawn value is correctly included in the game configuration when starting a single-player game.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/core/execution/PlayerSpawner.ts (2)

29-29: Consider making magic numbers configurable.

The hardcoded values (100 max tries, 50 minimum distance) work for typical scenarios but could be extracted as named constants or configuration options for better maintainability and testability.

Example refactor:

+  private static readonly MAX_SPAWN_TRIES = 100;
+  private static readonly MIN_SPAWN_DISTANCE = 50;
+
   randomSpawnLand(): TileRef | null {
     let tries = 0;
 
-    while (tries < 100) {
+    while (tries < PlayerSpawner.MAX_SPAWN_TRIES) {
       tries++;
 
       const tile = this.randTile();
 
       if (
         !this.gm.isLand(tile) ||
         this.gm.hasOwner(tile) ||
         this.gm.isBorder(tile)
       ) {
         continue;
       }
 
       let tooCloseToOtherPlayer = false;
       for (const spawn of this.players) {
-        if (this.gm.manhattanDist(spawn.tile, tile) < 50) {
+        if (this.gm.manhattanDist(spawn.tile, tile) < PlayerSpawner.MIN_SPAWN_DISTANCE) {
           tooCloseToOtherPlayer = true;
           break;
         }
       }

Also applies to: 44-44


68-73: Consider escalating spawn failures beyond console warnings.

The TODO comment at line 69-70 correctly identifies that spawn failures need better handling. If a human player cannot spawn, silently skipping them could lead to a broken game state. Consider throwing an error, displaying a user-facing message, or implementing fallback logic (e.g., expanding search radius or relaxing constraints).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 080408a and da2fec8.

📒 Files selected for processing (1)
  • src/core/execution/PlayerSpawner.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-28T22:47:31.406Z
Learnt from: BrewedCoffee
Repo: openfrontio/OpenFrontIO PR: 1957
File: tests/core/executions/PlayerExecution.test.ts:16-39
Timestamp: 2025-08-28T22:47:31.406Z
Learning: In test files, PlayerExecution instances must be manually registered with game.addExecution() because the setup utility doesn't automatically register SpawnExecution, which would normally handle this during the spawn phase in a real game.

Applied to files:

  • src/core/execution/PlayerSpawner.ts
📚 Learning: 2025-08-23T07:48:19.060Z
Learnt from: ElMelchizedek
Repo: openfrontio/OpenFrontIO PR: 1876
File: src/core/execution/FakeHumanExecution.ts:470-473
Timestamp: 2025-08-23T07:48:19.060Z
Learning: In FakeHumanExecution.ts DefensePost placement logic, returning -Infinity from structureSpawnTileValue when no sampled border tiles neighbor enemies is intentional. The logic samples up to 50 border tiles as a heuristic - if none are adjacent to enemies, it assumes DefensePost placement is unnecessary and aborts the entire placement attempt rather than continuing to evaluate individual tiles.

Applied to files:

  • src/core/execution/PlayerSpawner.ts
🧬 Code graph analysis (1)
src/core/execution/PlayerSpawner.ts (6)
src/core/PseudoRandom.ts (1)
  • PseudoRandom (3-76)
src/core/execution/SpawnExecution.ts (1)
  • SpawnExecution (7-61)
src/core/game/Game.ts (1)
  • Game (664-748)
src/core/Schemas.ts (1)
  • GameID (25-25)
src/core/Util.ts (1)
  • simpleHash (64-72)
src/core/game/GameMap.ts (1)
  • TileRef (3-3)
🔇 Additional comments (4)
src/core/execution/PlayerSpawner.ts (4)

1-17: LGTM!

The imports are clean and the constructor properly seeds the RNG using the game ID. This ensures deterministic spawn positions for the same game.


19-24: LGTM!

The random tile generation is straightforward and correctly uses the map dimensions.


42-52: Previous critical bug has been fixed.

The spacing check now correctly uses a flag pattern to ensure that if any existing spawn is too close, the outer loop continues to the next candidate tile. This fixes the critical logic bug identified in the previous review.


60-79: No action required — review comment based on incorrect assumption.

The concern about multiple calls accumulating duplicates is already prevented by design. Each time execManager.spawnPlayers() is called, a fresh PlayerSpawner instance is created (ExecutionManager.ts:136), and the method is called only once during game initialization (GameRunner.ts:104). A new instance means a fresh empty players array, so accumulation cannot occur. The defensive pattern suggested in the review comment is unnecessary.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/core/execution/PlayerSpawner.ts (2)

42-52: Past critical issue has been resolved.

The logic bug flagged in the previous review (where continue only affected the inner loop) has been correctly fixed using a tooCloseToOtherPlayer flag with a break statement.


69-69: Past typo has been corrected.

The typo "nornally" flagged in the previous review has been fixed to "normally".

🧹 Nitpick comments (2)
src/core/execution/PlayerSpawner.ts (2)

26-58: Consider making the distance threshold configurable.

The hardcoded Manhattan distance of 50 may not scale well across different map sizes. On small maps this might be too restrictive, while on large maps it might be too permissive.

Consider deriving the distance from map dimensions (e.g., Math.min(this.gm.width(), this.gm.height()) / 4) or exposing it as a config parameter.


60-79: Consider validating that human players exist.

The method silently returns an empty array if there are no human players. While this may be intentional, adding an early check or debug log could help surface configuration issues during development.

Example:

 spawnPlayers(): SpawnExecution[] {
+  const humanPlayers = this.gm.allPlayers().filter(p => p.type() === PlayerType.Human);
+  if (humanPlayers.length === 0) {
+    console.debug('PlayerSpawner: no human players to spawn');
+    return [];
+  }
+
-  for (const player of this.gm.allPlayers()) {
-    if (player.type() !== PlayerType.Human) {
-      continue;
-    }
+  for (const player of humanPlayers) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da2fec8 and 1452ec9.

📒 Files selected for processing (1)
  • src/core/execution/PlayerSpawner.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-28T22:47:31.406Z
Learnt from: BrewedCoffee
Repo: openfrontio/OpenFrontIO PR: 1957
File: tests/core/executions/PlayerExecution.test.ts:16-39
Timestamp: 2025-08-28T22:47:31.406Z
Learning: In test files, PlayerExecution instances must be manually registered with game.addExecution() because the setup utility doesn't automatically register SpawnExecution, which would normally handle this during the spawn phase in a real game.

Applied to files:

  • src/core/execution/PlayerSpawner.ts
📚 Learning: 2025-08-23T07:48:19.060Z
Learnt from: ElMelchizedek
Repo: openfrontio/OpenFrontIO PR: 1876
File: src/core/execution/FakeHumanExecution.ts:470-473
Timestamp: 2025-08-23T07:48:19.060Z
Learning: In FakeHumanExecution.ts DefensePost placement logic, returning -Infinity from structureSpawnTileValue when no sampled border tiles neighbor enemies is intentional. The logic samples up to 50 border tiles as a heuristic - if none are adjacent to enemies, it assumes DefensePost placement is unnecessary and aborts the entire placement attempt rather than continuing to evaluate individual tiles.

Applied to files:

  • src/core/execution/PlayerSpawner.ts
🧬 Code graph analysis (1)
src/core/execution/PlayerSpawner.ts (6)
src/core/PseudoRandom.ts (1)
  • PseudoRandom (3-76)
src/core/execution/SpawnExecution.ts (1)
  • SpawnExecution (7-61)
src/core/game/Game.ts (1)
  • Game (664-748)
src/core/Schemas.ts (1)
  • GameID (25-25)
src/core/Util.ts (1)
  • simpleHash (64-72)
src/core/game/GameMap.ts (1)
  • TileRef (3-3)
🔇 Additional comments (3)
src/core/execution/PlayerSpawner.ts (3)

1-7: Imports look good.

All necessary types and utilities are imported cleanly.


19-24: Clean and correct random tile selection.

The method correctly generates random coordinates within map bounds.


8-17: The review comment is incorrect.

The spawnPlayers() method creates a new PlayerSpawner instance each time it's called (as seen in ExecutionManager.spawnPlayers() at line 136). Therefore, the players array is always freshly initialized and does not accumulate across calls. The concern about array state persisting between invocations does not apply to the actual codebase usage pattern.

Likely an incorrect or invalid review comment.

coderabbitai[bot]
coderabbitai bot previously approved these changes Nov 3, 2025
Copy link
Collaborator

@evanpelle evanpelle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have trouble finding my spawn. I think we should emit a GoToPlayerEvent at the start so the camera goes to your player

randomSpawnLand(): TileRef | null {
let tries = 0;

while (tries < 100) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a bit of a naiive solution, as it could easily happen (especially towards the later players) that it won't be able to find a spot just by randomly guessing.
How expensive would it be if we built a low-res grid of possible spawn locations, and then iterated over a random index of those? Such a grid would have 800 elements at most in the world map, but realistically around 600.

i.e. when the map loads, check every 50 tiles verically and horizontally if this is a valid spot, if it is, add it as a tileRef to an array, and then instead of randTile choosing a totally random tile, it will choose possibleSpaces[random.nextInt(0, possibleSpaces.length)]

I don't have the final word on this, just my 5 cents.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you’re right about this approach. However, when I checked how this feature could be implemented based on the existing logic in BotSpawner and FakeHumanExecution, I noticed that the same approach is used in both places. To make it more efficient, we would definitely need to rewrite the logic, but that seems to require a major refactor across multiple components and should probably be addressed separately from the current issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also thought about a possible issue: currently, players and bots don’t take the minimum distance between each other into account. Bots only try to spawn farther from other bots, and players from other players, but not players from bots or bots from players.

I’m not sure how inconvenient this behavior might be, but it could be worth improving in future enhancements of this feature if users start reporting issues related to it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, maybe that can be another PR, hopefully 100 tries is enough. I don't think bot proximity should be a big deal, as long as the player has enough space to actually spawn, which should be guaranteed by the tile not being owned.

Copy link
Collaborator

@evanpelle evanpelle Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checking random land was the fastest way i could come up with, especially on larger maps, tracking available land can be really slow.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eventually we may need smarter spawn selection, for example if there are just 3 players, and 2 players spawn right next to each other they are at a disadvantage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eventually we may need smarter spawn selection, for example if there are just 3 players, and 2 players spawn right next to each other they are at a disadvantage.

Yes, we could adjust MIN_SPAWN_DISTANCE depending on the number of players in the game, for example. But I’m not sure, maybe some “unfair” spawns aren’t actually a bug, but rather a feature that adds extra tension to the gameplay. Will see with some feedback :)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/core/execution/utils/PlayerSpawner.ts (1)

26-58: Naive random selection can fail to find spawns.

As noted in previous review, the pure random approach with 100 tries can easily fail, especially for later players or maps with limited land. The probability of success decreases as more spawns are placed, and there's no guarantee of finding a spawn even when valid locations exist.

Consider pre-computing an array of valid spawn tiles at initialization, then sampling from that array without replacement. This would be more reliable and efficient.

🧹 Nitpick comments (1)
src/core/execution/utils/PlayerSpawner.ts (1)

44-44: Hardcoded minimum distance doesn't scale with map size.

The fixed Manhattan distance of 50 tiles doesn't account for map dimensions. On smaller maps, this could make it impossible to spawn multiple players, while on larger maps it might not provide enough separation.

Consider making this distance relative to map size (e.g., Math.min(50, this.gm.width() / 10)) or loading it from game config.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1452ec9 and 16b1f70.

📒 Files selected for processing (3)
  • src/client/ClientGameRunner.ts (3 hunks)
  • src/core/execution/ExecutionManager.ts (2 hunks)
  • src/core/execution/utils/PlayerSpawner.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-08-28T22:47:31.406Z
Learnt from: BrewedCoffee
Repo: openfrontio/OpenFrontIO PR: 1957
File: tests/core/executions/PlayerExecution.test.ts:16-39
Timestamp: 2025-08-28T22:47:31.406Z
Learning: In test files, PlayerExecution instances must be manually registered with game.addExecution() because the setup utility doesn't automatically register SpawnExecution, which would normally handle this during the spawn phase in a real game.

Applied to files:

  • src/core/execution/ExecutionManager.ts
  • src/core/execution/utils/PlayerSpawner.ts
📚 Learning: 2025-10-20T20:15:28.858Z
Learnt from: sambokai
Repo: openfrontio/OpenFrontIO PR: 2225
File: src/core/execution/FakeHumanExecution.ts:51-51
Timestamp: 2025-10-20T20:15:28.858Z
Learning: In src/core/execution/FakeHumanExecution.ts, game balance constants like MIRV_COOLDOWN_TICKS, MIRV_HESITATION_ODDS, VICTORY_DENIAL_TEAM_THRESHOLD, VICTORY_DENIAL_INDIVIDUAL_THRESHOLD, and STEAMROLL_CITY_GAP_MULTIPLIER are experimental tuning parameters subject to frequent change during balance testing. Do not flag changes to these values as issues or compare them against previous values.

Applied to files:

  • src/core/execution/ExecutionManager.ts
📚 Learning: 2025-10-18T17:54:01.311Z
Learnt from: sambokai
Repo: openfrontio/OpenFrontIO PR: 2225
File: src/core/execution/FakeHumanExecution.ts:172-173
Timestamp: 2025-10-18T17:54:01.311Z
Learning: In src/core/execution/FakeHumanExecution.ts, MIRVs and ground attacks should not be mutually exclusive. The considerMIRV() method should not short-circuit maybeAttack() - both actions can occur in the same tick.

Applied to files:

  • src/core/execution/ExecutionManager.ts
📚 Learning: 2025-08-23T08:03:44.914Z
Learnt from: ElMelchizedek
Repo: openfrontio/OpenFrontIO PR: 1876
File: src/core/execution/FakeHumanExecution.ts:547-592
Timestamp: 2025-08-23T08:03:44.914Z
Learning: In OpenFrontIO, FakeHumanExecution is used for AI-controlled nations that simulate human behavior, which is distinct from PlayerType.Bot. FakeHumanExecution players are not PlayerType.Bot - they represent more sophisticated AI that mimics human nation behavior, while PlayerType.Bot represents basic AI players.

Applied to files:

  • src/core/execution/ExecutionManager.ts
📚 Learning: 2025-10-21T20:06:04.823Z
Learnt from: Saphereye
Repo: openfrontio/OpenFrontIO PR: 2233
File: src/client/HostLobbyModal.ts:891-891
Timestamp: 2025-10-21T20:06:04.823Z
Learning: For the HumansVsNations game mode in `src/client/HostLobbyModal.ts` and related files, the implementation strategy is to generate all nations and adjust their strength for balancing, rather than limiting lobby size based on the number of available nations on the map.

Applied to files:

  • src/core/execution/ExecutionManager.ts
📚 Learning: 2025-10-08T17:14:49.369Z
Learnt from: Foorack
Repo: openfrontio/OpenFrontIO PR: 2141
File: src/client/ClientGameRunner.ts:228-234
Timestamp: 2025-10-08T17:14:49.369Z
Learning: In `ClientGameRunner.ts`, the `myPlayer` field is always set when `shouldPreventWindowClose()` is called, so the null check in that method is sufficient without needing to fetch it again from `gameView.playerByClientID()`.

Applied to files:

  • src/client/ClientGameRunner.ts
📚 Learning: 2025-06-09T02:20:43.637Z
Learnt from: VariableVince
Repo: openfrontio/OpenFrontIO PR: 1110
File: src/client/Main.ts:293-295
Timestamp: 2025-06-09T02:20:43.637Z
Learning: In src/client/Main.ts, during game start in the handleJoinLobby callback, UI elements are hidden using direct DOM manipulation with classList.add("hidden") for consistency. This includes modals, buttons, and error divs. The codebase follows this pattern rather than using component APIs for hiding elements during game transitions.

Applied to files:

  • src/client/ClientGameRunner.ts
📚 Learning: 2025-10-18T11:00:57.142Z
Learnt from: NewYearNewPhil
Repo: openfrontio/OpenFrontIO PR: 2230
File: src/client/graphics/GameRenderer.ts:269-277
Timestamp: 2025-10-18T11:00:57.142Z
Learning: In src/client/graphics/GameRenderer.ts, the GameRecapCapture implementation does not use setCaptureRenderEnabled on layers. Instead, it uses RecapCaptureSurface.capture() to render capture layers (TerrainLayer, TerritoryLayer, RailroadLayer, StructureIconsLayer, UnitLayer) directly to an off-screen canvas without requiring layer-level capture mode methods.

Applied to files:

  • src/client/ClientGameRunner.ts
🧬 Code graph analysis (3)
src/core/execution/ExecutionManager.ts (2)
src/core/game/Game.ts (1)
  • Execution (351-356)
src/core/execution/utils/PlayerSpawner.ts (1)
  • PlayerSpawner (8-80)
src/core/execution/utils/PlayerSpawner.ts (6)
src/core/PseudoRandom.ts (1)
  • PseudoRandom (3-76)
src/core/execution/SpawnExecution.ts (1)
  • SpawnExecution (7-61)
src/core/game/Game.ts (1)
  • Game (664-748)
src/core/Schemas.ts (1)
  • GameID (25-25)
src/core/Util.ts (1)
  • simpleHash (64-72)
src/core/game/GameMap.ts (1)
  • TileRef (3-3)
src/client/ClientGameRunner.ts (2)
src/core/game/GameView.ts (1)
  • myPlayer (592-594)
src/client/graphics/layers/Leaderboard.ts (1)
  • GoToPlayerEvent (21-23)
🔇 Additional comments (7)
src/client/ClientGameRunner.ts (3)

50-50: LGTM!

The import is correctly placed and necessary for the auto-navigation feature.


319-329: Consider timeout cleanup and timing.

The 1-second delay and untracked setTimeout have a few concerns:

  1. If stop() is called during the delay, the callback will still fire, potentially emitting events after cleanup.
  2. The timing seems arbitrary—if player spawning takes longer than 1 second, myPlayer might still be null.

Consider storing the timeout ID and clearing it in stop(), and verify that 1 second is sufficient for spawn processing to complete.


418-420: LGTM!

Correctly prevents manual spawn selection when random spawn mode is enabled. The logic is sound and well-integrated with existing spawn checks.

src/core/execution/ExecutionManager.ts (2)

29-29: LGTM!

Import is correctly placed and follows the existing pattern for BotSpawner.


135-137: LGTM!

Clean implementation that mirrors the existing spawnBots() pattern. The delegation to PlayerSpawner keeps the responsibility separation clear.

src/core/execution/utils/PlayerSpawner.ts (2)

1-17: LGTM!

Clean structure with deterministic randomness seeded from the game ID. This ensures consistent spawn locations across all clients for the same game.


19-24: LGTM!

Simple helper that correctly generates random tile coordinates within map bounds.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/core/execution/utils/PlayerSpawner.ts (1)

26-58: Consider making this method private.

The method relies on this.players for distance checking, meaning it only works correctly when called during spawnPlayers(). Making it public exposes internal state dependency and could lead to incorrect behavior if called independently (distance checks would be ineffective with an empty this.players array).

Unless there's a specific need to call this from outside the class, making it private would prevent misuse and clarify the intended usage pattern.

Apply this diff to make the method private:

-  randomSpawnLand(): TileRef | null {
+  private randomSpawnLand(): TileRef | null {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16b1f70 and 3dcb5cb.

📒 Files selected for processing (5)
  • resources/lang/debug.json (3 hunks)
  • resources/lang/en.json (3 hunks)
  • src/client/ClientGameRunner.ts (3 hunks)
  • src/client/graphics/layers/HeadsUpMessage.ts (1 hunks)
  • src/core/execution/utils/PlayerSpawner.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/client/ClientGameRunner.ts
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-08-28T22:47:31.406Z
Learnt from: BrewedCoffee
Repo: openfrontio/OpenFrontIO PR: 1957
File: tests/core/executions/PlayerExecution.test.ts:16-39
Timestamp: 2025-08-28T22:47:31.406Z
Learning: In test files, PlayerExecution instances must be manually registered with game.addExecution() because the setup utility doesn't automatically register SpawnExecution, which would normally handle this during the spawn phase in a real game.

Applied to files:

  • src/core/execution/utils/PlayerSpawner.ts
📚 Learning: 2025-06-02T14:27:37.609Z
Learnt from: andrewNiziolek
Repo: openfrontio/OpenFrontIO PR: 1007
File: resources/lang/de.json:115-115
Timestamp: 2025-06-02T14:27:37.609Z
Learning: For OpenFrontIO project: When localization keys are renamed in language JSON files, the maintainers separate technical changes from translation content updates. They wait for community translators to update the actual translation values rather than attempting to translate in the same PR. This allows technical changes to proceed while ensuring accurate translations from native speakers.

Applied to files:

  • resources/lang/debug.json
  • resources/lang/en.json
📚 Learning: 2025-08-16T10:52:08.292Z
Learnt from: TheGiraffe3
Repo: openfrontio/OpenFrontIO PR: 884
File: resources/lang/en.json:456-461
Timestamp: 2025-08-16T10:52:08.292Z
Learning: In OpenFrontIO, translation files in resources/lang/*.json (except en.json) should not be updated in regular PRs. Only dedicated translation PRs titled "mls" and made by Aotumori should update non-English locale files. Regular PRs should only update en.json when adding or modifying translation keys.

Applied to files:

  • resources/lang/en.json
📚 Learning: 2025-07-12T08:41:35.101Z
Learnt from: Aotumuri
Repo: openfrontio/OpenFrontIO PR: 1357
File: resources/lang/de.json:523-540
Timestamp: 2025-07-12T08:41:35.101Z
Learning: In OpenFrontIO project localization files, always check the en.json source file before flagging potential spelling errors in other language files, as some keys may intentionally use non-standard spellings that need to be consistent across all translations.

Applied to files:

  • resources/lang/en.json
📚 Learning: 2025-05-30T03:53:52.231Z
Learnt from: scottanderson
Repo: openfrontio/OpenFrontIO PR: 949
File: resources/lang/en.json:8-10
Timestamp: 2025-05-30T03:53:52.231Z
Learning: For the OpenFrontIO project, do not suggest updating translation files in resources/lang/*.json except for en.json. The project has a dedicated translation team that handles all other locale files.

Applied to files:

  • resources/lang/en.json
🧬 Code graph analysis (1)
src/core/execution/utils/PlayerSpawner.ts (6)
src/core/PseudoRandom.ts (1)
  • PseudoRandom (3-76)
src/core/execution/SpawnExecution.ts (1)
  • SpawnExecution (7-61)
src/core/game/Game.ts (1)
  • Game (664-748)
src/core/Schemas.ts (1)
  • GameID (25-25)
src/core/Util.ts (1)
  • simpleHash (64-72)
src/core/game/GameMap.ts (1)
  • TileRef (3-3)
🔇 Additional comments (8)
src/client/graphics/layers/HeadsUpMessage.ts (1)

43-45: Clean conditional rendering.

The ternary operator clearly handles the two spawn modes. The logic is simple and the translation keys match the localization files.

resources/lang/debug.json (1)

97-97: Debug keys added correctly.

All new localization keys follow the debug file pattern where values mirror their key paths. The structure is consistent with existing entries.

Also applies to: 150-150, 171-172

resources/lang/en.json (2)

138-138: Clear UI labels.

The "Random spawn" text is simple and descriptive for the toggle controls in both single-player and host lobby contexts.

Also applies to: 266-266


660-663: Informative user messages.

The spawn failure error clearly communicates the issue, and the random spawn message explains what's happening during auto-selection. Both messages are direct and functional.

Also applies to: 667-668

src/core/execution/utils/PlayerSpawner.ts (4)

1-6: LGTM!

Imports are clean and minimal, with all dependencies properly typed.


8-17: LGTM!

Clean class structure with proper encapsulation. Seeding the random number generator with the gameID hash ensures deterministic spawn locations across all clients for the same game—critical for multiplayer consistency.


19-24: LGTM!

Simple and correct tile selection logic with appropriate bounds.


60-78: Logic is correct for successful spawns.

The incremental building of the players array ensures that distance checking works properly—each new player is validated against all previously spawned players. The filtering for PlayerType.Human is appropriate.

The spawn failure handling at lines 68-72 has already been discussed in previous reviews and acknowledged with a TODO comment.

@mepoohsta mepoohsta marked this pull request as draft November 4, 2025 17:57
@mepoohsta mepoohsta marked this pull request as ready for review November 4, 2025 23:43
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/core/execution/utils/PlayerSpawner.ts (1)

26-58: Consider extracting magic numbers to named constants.

The values 100 (max tries) and 30 (minimum distance) are hardcoded. Extracting them to named constants at the class level would improve readability and make future tuning easier.

Apply this diff to extract the constants:

 export class PlayerSpawner {
   private random: PseudoRandom;
   private players: SpawnExecution[] = [];
+  private static readonly MAX_SPAWN_TRIES = 100;
+  private static readonly MIN_SPAWN_DISTANCE = 30;

   constructor(
     private gm: Game,
     gameID: GameID,
   ) {
     this.random = new PseudoRandom(simpleHash(gameID));
   }

   private randTile(): TileRef {
     const x = this.random.nextInt(0, this.gm.width());
     const y = this.random.nextInt(0, this.gm.height());

     return this.gm.ref(x, y);
   }

   randomSpawnLand(): TileRef | null {
     let tries = 0;

-    while (tries < 100) {
+    while (tries < PlayerSpawner.MAX_SPAWN_TRIES) {
       tries++;

       const tile = this.randTile();

       if (
         !this.gm.isLand(tile) ||
         this.gm.hasOwner(tile) ||
         this.gm.isBorder(tile)
       ) {
         continue;
       }

       let tooCloseToOtherPlayer = false;
       for (const spawn of this.players) {
-        if (this.gm.manhattanDist(spawn.tile, tile) < 30) {
+        if (this.gm.manhattanDist(spawn.tile, tile) < PlayerSpawner.MIN_SPAWN_DISTANCE) {
           tooCloseToOtherPlayer = true;
           break;
         }
       }

       if (tooCloseToOtherPlayer) {
         continue;
       }

       return tile;
     }

     return null;
   }
tests/core/execution/utils/PlayerSpawner.test.ts (2)

5-35: Test structure is good, but validation could be deeper.

The parameterized test approach with test.each is excellent for covering multiple maps. However, the test only verifies the count of executors, not whether the spawn locations meet the constraints.

Consider adding validation that each spawn location:

  1. Is on land (gm.isLand(spawn.tile))
  2. Is unowned (!gm.hasOwner(spawn.tile))
  3. Is not a border tile (!gm.isBorder(spawn.tile))
  4. Is at least 30 tiles away from other spawns

Here's an example of more thorough validation:

const executors = new PlayerSpawner(game, "game_id").spawnPlayers();
expect(executors.length).toBe(maxPlayers);

// Validate each spawn location meets constraints
for (const executor of executors) {
  expect(game.isLand(executor.tile)).toBe(true);
  expect(game.hasOwner(executor.tile)).toBe(false);
  expect(game.isBorder(executor.tile)).toBe(false);
}

// Validate minimum distance between spawns
for (let i = 0; i < executors.length; i++) {
  for (let j = i + 1; j < executors.length; j++) {
    const distance = game.manhattanDist(executors[i].tile, executors[j].tile);
    expect(distance).toBeGreaterThanOrEqual(30);
  }
}

8-34: Add test cases for edge scenarios.

The current tests only cover the happy path where all players successfully spawn. Consider adding test cases for:

  1. Spawn failure scenario: Test a map with limited land area and too many players to verify that spawnPlayers() handles partial failures gracefully.
  2. Single player: Verify spawn works correctly with just one player.
  3. Crowded map: Test behavior when spawn locations are scarce.

Example test for spawn failure:

test("Handles spawn failure when map is too crowded", async () => {
  const players: PlayerInfo[] = [];
  
  // Try to spawn more players than possible on a small map
  for (let i = 0; i < 50; i++) {
    players.push(
      new PlayerInfo(
        `player${i}`,
        PlayerType.Human,
        `client_id${i}`,
        `player_id${i}`,
      ),
    );
  }
  
  const game = await setup("half_land_half_ocean", undefined, players);
  const executors = new PlayerSpawner(game, "game_id").spawnPlayers();
  
  // Should spawn fewer than requested when map is too small
  expect(executors.length).toBeLessThan(players.length);
  expect(executors.length).toBeGreaterThan(0);
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4cc3336 and 823ffb4.

📒 Files selected for processing (2)
  • src/core/execution/utils/PlayerSpawner.ts (1 hunks)
  • tests/core/execution/utils/PlayerSpawner.test.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-28T22:47:31.406Z
Learnt from: BrewedCoffee
Repo: openfrontio/OpenFrontIO PR: 1957
File: tests/core/executions/PlayerExecution.test.ts:16-39
Timestamp: 2025-08-28T22:47:31.406Z
Learning: In test files, PlayerExecution instances must be manually registered with game.addExecution() because the setup utility doesn't automatically register SpawnExecution, which would normally handle this during the spawn phase in a real game.

Applied to files:

  • tests/core/execution/utils/PlayerSpawner.test.ts
  • src/core/execution/utils/PlayerSpawner.ts
🧬 Code graph analysis (2)
tests/core/execution/utils/PlayerSpawner.test.ts (3)
src/core/game/Game.ts (1)
  • PlayerInfo (406-420)
tests/util/Setup.ts (1)
  • setup (23-82)
src/core/execution/utils/PlayerSpawner.ts (1)
  • PlayerSpawner (8-79)
src/core/execution/utils/PlayerSpawner.ts (6)
src/core/PseudoRandom.ts (1)
  • PseudoRandom (3-76)
src/core/execution/SpawnExecution.ts (1)
  • SpawnExecution (7-61)
src/core/game/Game.ts (1)
  • Game (664-748)
src/core/Schemas.ts (1)
  • GameID (25-25)
src/core/Util.ts (1)
  • simpleHash (64-72)
src/core/game/GameMap.ts (1)
  • TileRef (3-3)
🔇 Additional comments (3)
src/core/execution/utils/PlayerSpawner.ts (3)

12-17: Good use of deterministic randomness.

Seeding the PRNG with a hash of the game ID ensures that spawn locations are reproducible for the same game, which is helpful for debugging and fairness. The approach is clean and idiomatic.


19-24: LGTM!

The randTile method is straightforward and correct. It properly generates random coordinates within game bounds and converts them to a TileRef.


60-78: Implementation looks correct.

The method properly filters for human players, attempts to find spawn locations, and handles failures with appropriate logging. The approach integrates well with the existing SpawnExecution pattern.

Comment on lines 647 to 649
isRandomSpawn(): boolean {
return this._config.randomSpawn();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you remove this (and on Game), and leave it in the config. so GameView.config().isRandomSpawn()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed and refactored to use from config.

this.hasJoined = true;
console.log("starting game!");

if (this.gameView.isRandomSpawn()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

little worried about this if the machine is slow and it takes longer than 1 second to load the game.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored logic there to mitigate this issue.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 38c40b7 and dc5f4a4.

📒 Files selected for processing (5)
  • src/client/ClientGameRunner.ts (3 hunks)
  • src/client/graphics/layers/HeadsUpMessage.ts (1 hunks)
  • src/core/GameRunner.ts (1 hunks)
  • src/core/configuration/Config.ts (1 hunks)
  • src/core/configuration/DefaultConfig.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/GameRunner.ts
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-10-20T20:15:28.858Z
Learnt from: sambokai
Repo: openfrontio/OpenFrontIO PR: 2225
File: src/core/execution/FakeHumanExecution.ts:51-51
Timestamp: 2025-10-20T20:15:28.858Z
Learning: In src/core/execution/FakeHumanExecution.ts, game balance constants like MIRV_COOLDOWN_TICKS, MIRV_HESITATION_ODDS, VICTORY_DENIAL_TEAM_THRESHOLD, VICTORY_DENIAL_INDIVIDUAL_THRESHOLD, and STEAMROLL_CITY_GAP_MULTIPLIER are experimental tuning parameters subject to frequent change during balance testing. Do not flag changes to these values as issues or compare them against previous values.

Applied to files:

  • src/core/configuration/Config.ts
📚 Learning: 2025-10-08T17:14:49.369Z
Learnt from: Foorack
Repo: openfrontio/OpenFrontIO PR: 2141
File: src/client/ClientGameRunner.ts:228-234
Timestamp: 2025-10-08T17:14:49.369Z
Learning: In `ClientGameRunner.ts`, the `myPlayer` field is always set when `shouldPreventWindowClose()` is called, so the null check in that method is sufficient without needing to fetch it again from `gameView.playerByClientID()`.

Applied to files:

  • src/client/ClientGameRunner.ts
📚 Learning: 2025-06-09T02:20:43.637Z
Learnt from: VariableVince
Repo: openfrontio/OpenFrontIO PR: 1110
File: src/client/Main.ts:293-295
Timestamp: 2025-06-09T02:20:43.637Z
Learning: In src/client/Main.ts, during game start in the handleJoinLobby callback, UI elements are hidden using direct DOM manipulation with classList.add("hidden") for consistency. This includes modals, buttons, and error divs. The codebase follows this pattern rather than using component APIs for hiding elements during game transitions.

Applied to files:

  • src/client/ClientGameRunner.ts
📚 Learning: 2025-10-08T17:14:49.369Z
Learnt from: Foorack
Repo: openfrontio/OpenFrontIO PR: 2141
File: src/client/ClientGameRunner.ts:228-234
Timestamp: 2025-10-08T17:14:49.369Z
Learning: For the window close confirmation feature in `ClientGameRunner.ts`, the troop count requirement (>10,000 troops) from issue #2137 was intentionally removed because it was arbitrary and troop count can be reported as low despite having significant land. The confirmation now triggers for any alive player regardless of troop count.

Applied to files:

  • src/client/ClientGameRunner.ts
📚 Learning: 2025-10-18T11:00:57.142Z
Learnt from: NewYearNewPhil
Repo: openfrontio/OpenFrontIO PR: 2230
File: src/client/graphics/GameRenderer.ts:269-277
Timestamp: 2025-10-18T11:00:57.142Z
Learning: In src/client/graphics/GameRenderer.ts, the GameRecapCapture implementation does not use setCaptureRenderEnabled on layers. Instead, it uses RecapCaptureSurface.capture() to render capture layers (TerrainLayer, TerritoryLayer, RailroadLayer, StructureIconsLayer, UnitLayer) directly to an off-screen canvas without requiring layer-level capture mode methods.

Applied to files:

  • src/client/ClientGameRunner.ts
🧬 Code graph analysis (1)
src/client/ClientGameRunner.ts (4)
src/core/game/GameView.ts (1)
  • myPlayer (592-594)
src/client/Utils.ts (1)
  • translateText (92-147)
src/client/LangSelector.ts (1)
  • translateText (254-274)
src/client/graphics/layers/Leaderboard.ts (1)
  • GoToPlayerEvent (21-23)
🔇 Additional comments (5)
src/core/configuration/Config.ts (1)

91-91: LGTM! Clean interface addition.

The new isRandomSpawn() method is properly typed and follows the existing interface pattern.

src/client/graphics/layers/HeadsUpMessage.ts (1)

43-45: Good conditional rendering logic.

The ternary operator cleanly switches between spawn messages based on the game configuration.

src/client/ClientGameRunner.ts (2)

51-51: Clean import addition.

Properly imports the event type needed for the random spawn flow.


453-454: Correct guard for random spawn mode.

Properly prevents manual spawn intents when random spawn is enabled, ensuring players can't interfere with the automatic spawn process.

src/core/configuration/DefaultConfig.ts (1)

340-342: Clean implementation following existing patterns.

The getter properly delegates to the game config property, matching the style of other configuration accessors in this class.

@mepoohsta mepoohsta marked this pull request as draft November 5, 2025 19:26
@mepoohsta mepoohsta marked this pull request as ready for review November 5, 2025 21:36
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc5f4a4 and 5f036e4.

📒 Files selected for processing (4)
  • resources/lang/en.json (3 hunks)
  • src/client/ClientGameRunner.ts (5 hunks)
  • src/core/configuration/Config.ts (1 hunks)
  • src/core/configuration/DefaultConfig.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/core/configuration/Config.ts
  • src/core/configuration/DefaultConfig.ts
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-10-08T17:14:49.369Z
Learnt from: Foorack
Repo: openfrontio/OpenFrontIO PR: 2141
File: src/client/ClientGameRunner.ts:228-234
Timestamp: 2025-10-08T17:14:49.369Z
Learning: In `ClientGameRunner.ts`, the `myPlayer` field is always set when `shouldPreventWindowClose()` is called, so the null check in that method is sufficient without needing to fetch it again from `gameView.playerByClientID()`.

Applied to files:

  • src/client/ClientGameRunner.ts
📚 Learning: 2025-10-08T17:14:49.369Z
Learnt from: Foorack
Repo: openfrontio/OpenFrontIO PR: 2141
File: src/client/ClientGameRunner.ts:228-234
Timestamp: 2025-10-08T17:14:49.369Z
Learning: For the window close confirmation feature in `ClientGameRunner.ts`, the troop count requirement (>10,000 troops) from issue #2137 was intentionally removed because it was arbitrary and troop count can be reported as low despite having significant land. The confirmation now triggers for any alive player regardless of troop count.

Applied to files:

  • src/client/ClientGameRunner.ts
📚 Learning: 2025-06-09T02:20:43.637Z
Learnt from: VariableVince
Repo: openfrontio/OpenFrontIO PR: 1110
File: src/client/Main.ts:293-295
Timestamp: 2025-06-09T02:20:43.637Z
Learning: In src/client/Main.ts, during game start in the handleJoinLobby callback, UI elements are hidden using direct DOM manipulation with classList.add("hidden") for consistency. This includes modals, buttons, and error divs. The codebase follows this pattern rather than using component APIs for hiding elements during game transitions.

Applied to files:

  • src/client/ClientGameRunner.ts
📚 Learning: 2025-10-18T11:00:57.142Z
Learnt from: NewYearNewPhil
Repo: openfrontio/OpenFrontIO PR: 2230
File: src/client/graphics/GameRenderer.ts:269-277
Timestamp: 2025-10-18T11:00:57.142Z
Learning: In src/client/graphics/GameRenderer.ts, the GameRecapCapture implementation does not use setCaptureRenderEnabled on layers. Instead, it uses RecapCaptureSurface.capture() to render capture layers (TerrainLayer, TerritoryLayer, RailroadLayer, StructureIconsLayer, UnitLayer) directly to an off-screen canvas without requiring layer-level capture mode methods.

Applied to files:

  • src/client/ClientGameRunner.ts
📚 Learning: 2025-06-02T14:27:37.609Z
Learnt from: andrewNiziolek
Repo: openfrontio/OpenFrontIO PR: 1007
File: resources/lang/de.json:115-115
Timestamp: 2025-06-02T14:27:37.609Z
Learning: For OpenFrontIO project: When localization keys are renamed in language JSON files, the maintainers separate technical changes from translation content updates. They wait for community translators to update the actual translation values rather than attempting to translate in the same PR. This allows technical changes to proceed while ensuring accurate translations from native speakers.

Applied to files:

  • resources/lang/en.json
📚 Learning: 2025-08-16T10:52:08.292Z
Learnt from: TheGiraffe3
Repo: openfrontio/OpenFrontIO PR: 884
File: resources/lang/en.json:456-461
Timestamp: 2025-08-16T10:52:08.292Z
Learning: In OpenFrontIO, translation files in resources/lang/*.json (except en.json) should not be updated in regular PRs. Only dedicated translation PRs titled "mls" and made by Aotumori should update non-English locale files. Regular PRs should only update en.json when adding or modifying translation keys.

Applied to files:

  • resources/lang/en.json
📚 Learning: 2025-07-12T08:41:35.101Z
Learnt from: Aotumuri
Repo: openfrontio/OpenFrontIO PR: 1357
File: resources/lang/de.json:523-540
Timestamp: 2025-07-12T08:41:35.101Z
Learning: In OpenFrontIO project localization files, always check the en.json source file before flagging potential spelling errors in other language files, as some keys may intentionally use non-standard spellings that need to be consistent across all translations.

Applied to files:

  • resources/lang/en.json
📚 Learning: 2025-05-30T03:53:52.231Z
Learnt from: scottanderson
Repo: openfrontio/OpenFrontIO PR: 949
File: resources/lang/en.json:8-10
Timestamp: 2025-05-30T03:53:52.231Z
Learning: For the OpenFrontIO project, do not suggest updating translation files in resources/lang/*.json except for en.json. The project has a dedicated translation team that handles all other locale files.

Applied to files:

  • resources/lang/en.json
🧬 Code graph analysis (1)
src/client/ClientGameRunner.ts (3)
src/core/game/GameView.ts (1)
  • myPlayer (592-594)
src/client/Utils.ts (1)
  • translateText (92-147)
src/client/graphics/layers/Leaderboard.ts (1)
  • GoToPlayerEvent (21-23)
🔇 Additional comments (5)
resources/lang/en.json (1)

138-138: Localization additions look good.

All new keys for the random spawn feature are well-structured, clearly worded, and properly placed within their respective sections. The JSON syntax is valid, and the user-facing text is appropriate for the game UI. The approach of updating only en.json aligns with the project's localization policy.

Also applies to: 266-266, 666-674

src/client/ClientGameRunner.ts (4)

51-51: LGTM!

The import is necessary for navigating to the player's position after random spawn.


206-206: LGTM!

The field is properly typed and initialized for timeout cleanup.


463-467: LGTM!

The spawn intent guard correctly prevents manual spawn clicks when random spawn is enabled. The logic properly combines the spawn phase check with the random spawn mode check.


332-362: Localization keys verified—code is ready.

The error_modal.spawn_failed.title and error_modal.spawn_failed.description keys both exist in en.json with correct structure. The random spawn logic handles all cases correctly: it polls during spawn phase, returns early if player is missing, shows error if spawn fails, and emits event when player spawns.

@mepoohsta mepoohsta requested a review from evanpelle November 5, 2025 21:48
@mepoohsta
Copy link
Contributor Author

mepoohsta commented Nov 6, 2025

@evanpelle , should I enable random spawn for games in the playlist by default? For example, for FFA with some probability, say 25%? What do you think?

@evanpelle
Copy link
Collaborator

@evanpelle , should I enable random spawn for games in the playlist by default? For example, for FFA with some probability, say 25%? What do you think?

I think we should keep it only for private games when we release so players can test it out, and then once we know it's working well we can add it to public games.

Copy link
Collaborator

@evanpelle evanpelle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@evanpelle evanpelle added this to the v27 milestone Nov 6, 2025
@evanpelle evanpelle merged commit 25ea111 into openfrontio:main Nov 6, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants