Skip to content

fix: prevent unresponsive behavior on oversized cities#443

Open
libdefi wants to merge 1 commit intoamilich:mainfrom
libdefi:codex/fix-large-city-unresponsive
Open

fix: prevent unresponsive behavior on oversized cities#443
libdefi wants to merge 1 commit intoamilich:mainfrom
libdefi:codex/fix-large-city-unresponsive

Conversation

@libdefi
Copy link
Contributor

@libdefi libdefi commented Mar 5, 2026

Summary

This PR fixes browser unresponsiveness caused by oversized cities by adding hard limits and reducing expensive per-frame work.

Problem

When city size became too large, the app could hit heavy main-thread workloads (simulation + pathfinding + spawn scans), leading to "Page Unresponsive" in the browser.

Changes

  • Added shared grid limits:
    • MAX_GRID_SIZE = 250
    • MIN_GRID_SIZE = 50
    • GRID_EXPANSION_STEP = 15
  • Added expansion guard in game logic:
    • expandCity() now returns boolean
    • Expansion is blocked when next size exceeds max
  • Added safe-load normalization for oversized saves:
    • On load/import/restore, oversized maps are shrunk to supported size
    • Simulation speed is set to paused after forced normalization
  • Updated UI controls:
    • Disable expand/shrink actions when limit reached
    • Show user-facing messages for max/min size limits
  • Optimized pedestrian/transport spawn scans:
    • Added per-grid-version caching for expensive finder results
    • Reused cached residential/destination/bus-stop/beach lookups

Behavioral Notes

  • Existing oversized saved cities are automatically reduced to supported size during load.
  • Outer-area tiles may be removed when normalization occurs.

Validation

  • npx tsc --noEmit passes.
  • Target files pass ESLint checks run locally.
  • Manual verification: app remains responsive after loading large cities and expansion actions are correctly blocked at max size.

Note

Medium Risk
Touches core state loading and grid resize logic, which can affect save compatibility and city integrity if edge cases are missed; changes are scoped but impact critical gameplay state.

Overview
Adds shared grid constraints via gameLimits and enforces them end-to-end: expandCity() now returns boolean, refuses to grow past MAX_GRID_SIZE, and UI entry points (sidebar, settings, mobile toolbar) disable expand/shrink actions and surface limit messaging.

Introduces load-time normalization that automatically shrinks oversized saved/imported states down to the supported maximum (and pauses simulation after forced resizing) to keep legacy/oversized saves playable.

Optimizes vehicleSystems spawn logic by caching expensive full-grid finder results per gridVersion, avoiding repeated O(n²) scans during batch spawns; also updates expandGrid/shrinkGrid defaults and minimum-size checks to use the shared constants.

Written by Cursor Bugbot for commit c9eec72. This will update automatically on new commits. Configure here.

@assert-app
Copy link

assert-app bot commented Mar 5, 2026

@vercel
Copy link
Contributor

vercel bot commented Mar 5, 2026

@libdefi is attempting to deploy a commit to the andrew-4640's projects Team on Vercel.

A member of the Team first needs to authorize it.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

}, [multiplayer?.connectionState, multiplayer?.roomCode, multiplayer?.initialState]);
const m = useMessages();
const canExpandCity = gridSize + GRID_EXPANSION_STEP * 2 <= MAX_GRID_SIZE;
const canShrinkCity = gridSize > MIN_GRID_SIZE;
Copy link

Choose a reason for hiding this comment

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

Shrink button enabled when operation will fail

Medium Severity · Logic Bug

canShrinkCity checks gridSize > MIN_GRID_SIZE (i.e. > 50), but shrinkGrid rejects when gridSize - GRID_EXPANSION_STEP * 2 < MIN_GRID_SIZE (i.e. gridSize < 80). For desktop's default grid size of 70, the shrink button appears enabled but the operation silently fails. The same mismatch exists in MobileToolbar and SettingsPanel. The condition needs to account for the full GRID_EXPANSION_STEP * 2 reduction.

Additional Locations (2)

Fix in Cursor Fix in Web

gridSize: result.newSize,
services: createEmptyServices(result.newSize),
speed: 0, // Start paused after forced resize to avoid immediate heavy simulation.
};
Copy link

Choose a reason for hiding this comment

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

Normalization doesn't update bounds after grid shrink

Medium Severity · Logic Bug

normalizeLoadedGridSize updates grid, gridSize, services, and speed after shrinking, but does not update bounds. Both expandCity and shrinkCity always set bounds to { minX: 0, minY: 0, maxX: newSize - 1, maxY: newSize - 1 } when changing grid size. After forced normalization of an oversized save, bounds will reference coordinates beyond the actual grid dimensions, which could cause out-of-bounds issues downstream.

Fix in Cursor Fix in Web

Copy link

@xkonjin xkonjin left a comment

Choose a reason for hiding this comment

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

Quick review pass:

  • Main risk area here is UI state transitions, empty/error states, and interaction regressions.
  • I didn’t see targeted regression coverage in the diff; please add or point CI at a focused test for the changed path in Sidebar.tsx, SettingsPanel.tsx, vehicleSystems.ts (+5 more).
  • Before merge, I’d smoke-test the behavior touched by Sidebar.tsx, SettingsPanel.tsx, vehicleSystems.ts (+5 more) with malformed input / retry / rollback cases, since that’s where this class of change usually breaks.

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.

2 participants