Optimize MoveSearcher: 12-59x performance improvement#232
Open
lordscarlet wants to merge 13 commits into
Open
Conversation
Merge from YourDeveloperFriend
Merge changes from base
- Remove redundant nested loop that checked isSameCity for all coordinate pairs - The existing Set-based duplicate check already handles this validation efficiently - Performance improvements: - 2096 (Madagascar): 13.7s → 11.04s (19% faster) - 2026 (Southern US): 589.83s → 448.35s (24% faster) - All tests passing, route counts and signatures unchanged
Major breakthrough optimization: avoiding redundant route validation during recursive path exploration in MoveSearcher. Root cause: validatePartial was re-validating ALL previous steps on every recursion. For a path of length n, this validation was O(n²) in total. Since the searcher builds paths from valid routes, this validation is redundant during search (only needed for user action validation). Solution: Created validatePartialForSearch() that skips the expensive route validation loop, keeping only essential checks (locomotive limits, duplicate city detection, intermediate waypoint validation). Performance improvements are dramatic: - 2096 (Madagascar, 74 routes): 13.7s → 1.14s (92% faster, 12x speedup) - 2026 (Southern US, 668 routes): 589.83s → 17.05s (97% faster, 35x speedup!) - 716 (Poland, 1982 routes): Expected ~50s (from 1894s baseline) Route validation correctness verified: identical routes found before/after optimization. Tests: 61 specs, 0 failures.
Replace special UTF-8 checkmark characters (✓) with ASCII-safe [OK] indicators to prevent encoding display artifacts in terminal output (Γ£ô corruption). This ensures clean, readable output across different terminal environments.
- 2096 (Madagascar): 74 routes, now runs in ~1.07s (12x speedup) - 2026 (Southern US): 668 routes, now runs in ~17.05s (35x speedup) - 716 (Poland): 1,982 routes baseline already committed Route content verified identical before/after optimization via signature comparison
- Implement statistics calculation for duration analysis (min, max, median, mean, stdDev) - Add stats function to support performance baseline comparisons - Update searcher_perf_test with performance metrics collection - Update validator to add validatePartialForSearch for optimized search validation - Update searcher to use optimized validation and add verbose progress logging - Updated README with performance testing documentation - Updated REGRESSION_BASELINES.json with test baseline configuration - Add route signatures for regression testing (2096, 2026, 716 fixtures)
9ef4b1e to
0239e55
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR optimizes the engine’s MoveSearcher by avoiding redundant validation work during recursive route exploration, and adds a small performance test/fixture harness to prevent correctness regressions while measuring latency.
Changes:
- Add a lightweight
MoveValidator.validatePartialForSearch()and updateMoveSearcherto use it during search recursion. - Add scripts/tests/docs to export fixtures and benchmark route search deterministically against golden baselines.
- Add regression fixtures/baselines and an e2e smoke test that times Move Calculator completion.
Reviewed changes
Copilot reviewed 15 out of 18 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/engine/move/validator.ts | Refactors partial validation and adds a search-specific fast path. |
| src/engine/move/searcher.ts | Switches recursive search to lightweight validation and adds optional debug progress logging. |
| src/engine/move/searcher_perf_test.ts | Adds a Jasmine perf test harness with optional regression budgets. |
| src/scripts/measure_move_search.ts | Adds a CLI benchmark script that measures runs and checks route count/content baselines. |
| src/scripts/export_game_fixture.ts | Adds a fixture export script (DB/API/auto) for generating reproducible perf fixtures. |
| src/e2e/move_good_test.ts | Adds an e2e smoke test that opens Move Calculator and times completion. |
| src/e2e/e2e_test.ts | Wires the new “moving goods” e2e suite into the overall e2e runner. |
| src/e2e/goldens/move_perf_2096.json | Adds a golden game-state fixture for perf/regression testing. |
| src/e2e/goldens/move_perf_2096.routes.json | Adds the expected route-content baseline for the 2096 fixture. |
| src/e2e/goldens/move_perf_2096.routes.latest.json | Adds a generated “latest snapshot” file (currently committed). |
| package.json | Adds an npm script to run export_game_fixture.ts. |
| README.md | Documents how to run the perf harness and export fixtures. |
| REGRESSION_BASELINES.json | Documents regression baseline metadata and usage. |
| PERF_TESTING_QUICK_REF.md | Adds quick-reference instructions for perf testing. |
| MOVE_SEARCH_BASELINES.md | Adds recorded baseline/improvement notes for fixtures. |
| ITERATION_PLAN.md | Adds an iteration plan and success criteria for perf work. |
| GOLDEN_FIXTURE_CREATION.md | Documents how golden fixtures were created/prepared. |
| pr_body.md | Adds a PR body document capturing the methodology and results. |
| baseline_2026.log | Adds a local run log (currently committed). |
| baseline_2026_canonical.log | Adds a local run log (currently committed). |
| baseline_2026_routes.log | Adds a local run log (currently committed). |
| baseline_716_canonical.log | Adds a local run log (currently committed). |
| baseline_716_check.log | Adds a local run log (currently committed). |
| baseline_716_routes.log | Adds a local run log (currently committed). |
| measure_2026.log | Adds a local run log (currently committed). |
| perf_2026_optimized.log | Adds a local run log (currently committed). |
| perf_716_optimized.log | Adds a local run log (currently committed). |
| perf_check_2026.log | Adds a local run log (currently committed). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Validate MOVE_CALCULATOR_E2E_TIMEOUT_MS with finite-number check - Ignore/remove generated *.routes.latest.json artifacts - Restore logical same-city duplicate-stop validation in MoveValidator - Guard MoveSearcher debug-only counters/computation behind debug flag - Validate MOVE_SEARCH_PERF_RUNS with clear error handling - Fix Puerto Rico variant guidance message in measurement script - Reformat move_good_test.ts indentation
Owner
|
This is actually really cool, but the refactor of the validator is really scary. Do you mind if I ask you to engage in a little bit of rigor here to make sure we don't break the validator? I would recommend doing the following:
WDYT? Am I asking for too much? |
Contributor
Author
|
I think that sounds completely reasonable. I will see what I can do to
increase the rigor of the validation, testing, etc.
To a degree I think one of the best I can do is to get the output from a
large chunk of games and compare the output before and after.
I will think about some other approaches as well.
…On Fri, Mar 20, 2026, 6:27 PM Nathan Tate ***@***.***> wrote:
*YourDeveloperFriend* left a comment (YourDeveloperFriend/choochoo#232)
<#232 (comment)>
This is actually really cool, but the refactor of the validator is really
scary. Do you mind if I ask you to engage in a little bit of rigor here to
make sure we don't break the validator? I would recommend doing the
following:
1. Setting up more robust tests for the validator.
2. Refactor the validator (verifying that the tests don't break)
3. Then set up the searcher with improvements.
WDYT? Am I asking for too much?
—
Reply to this email directly, view it on GitHub
<#232?email_source=notifications&email_token=AAAEMHZSJ62AK5ZU7K5IP634RXAUTA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTIMJQGEZDMMJTGI22M4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L#issuecomment-4101261325>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAEMH2AYDS4KWHCLZA35634RXAUTAVCNFSM6AAAAACWI3VMWOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHM2DCMBRGI3DCMZSGU>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds 24 unit tests covering validateEnd, validatePartial, validatePartialForSearch, findRoutesFromLocation, and findRoutesToLocation. Tests use the same InjectionHelper/resettable infrastructure as BuildValidator. The key invariant verified: validatePartialForSearch skips route-ownership checks (the O(n^2) optimization) while enforcing all other constraints (locomotive limits, goods presence, duplicate stops, transit rules). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MoveSearcher Performance Optimization
Closes #149
The MoveSearcher algorithm was experiencing severe performance bottlenecks that prevented real-time route calculation in some game scenarios. Route searches were taking 10-20+ minutes for complex maps, making the feature unusable. This PR addresses the root causes and achieves 12-59x performance improvements across test fixtures.
Root Cause Analysis
Through comprehensive performance profiling with three game fixtures of increasing complexity, we identified two O(n²) bottlenecks hidden in recursive call stacks:
Performance Test Infrastructure
Test Fixtures
We created regression test baselines by exporting live game data from the production API:
How we obtained sample data:
GET /api/games/{gameId}gameDatato objectssrc/e2e/goldens/move_perf_*.json*.routes.json) for regression testingThe fixtures enable reproducible testing without live game dependencies, allowing us to validate each optimization doesn't regress route correctness.
Optimizations Implemented
Optimization 1: Remove O(n²) Duplicate City Checking
File:
src/engine/move/validator.tsProblem: The
validatePartialmethod was checking for duplicate cities in two ways:Solution: Removed redundant nested loop, kept Set-based O(n) check
Result:
Optimization 2: Remove O(n²) Route Validation from Search (PRIMARY FIX)
Files:
src/engine/move/validator.ts,src/engine/move/searcher.tsProblem: During recursive path search,
validatePartial()was re-validating the entire accumulated path on every call:For a game with 1,982 routes across deep search trees, this caused massive cumulative overhead (O(n³) in worst case for deep trees).
Root Cause: The validator was checking if each route in the path was valid. However, the MoveSearcher only calls
validatePartialwith routes that are already known to be valid (returned from the validator's route list). Re-validating them was redundant.Solution:
validatePartialForSearch()method that skips the O(n) route validation loopvalidatePartial()for user action validationWhy this is safe:
validatePartial()Result:
Verification & Testing
Regression Testing:
All route content verified identical before and after optimization using deterministic signatures:
Unit Tests:
Performance Metrics Summary:
Impact
Files Changed
src/engine/move/validator.ts- Added lightweight search validation, removed duplicate O(n²) checkssrc/engine/move/searcher.ts- Call lightweight validation during searchTesting Instructions
Validate improvements with included test fixtures:
Each test verifies: