Conversation
Co-authored-by: Andrew Milich <milichab@gmail.com>
|
Cursor Agent can help with this pull request. Just |
|
Your pull request is now ready for review with Assert. Stop waiting for your code to break. Ship with confidence using Assert. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| const TILE_PATH: Set<string> = new Set( | ||
| PATH_CELLS.map((cell) => `${cell.x},${cell.y}`) | ||
| ); |
There was a problem hiding this comment.
🔴 TILE_PATH only contains waypoint cells, not intermediate path cells — towers can be placed on the path
TILE_PATH is built from PATH_CELLS which only lists the 7 waypoint corners of the path (e.g. (0,2), (3,2), (3,7), etc.). The isOnPath() function checks against this set, but the actual enemy path includes all intermediate cells between waypoints.
Root Cause and Impact
At line 70-72, TILE_PATH is constructed by mapping only the waypoint cells:
const TILE_PATH: Set<string> = new Set(
PATH_CELLS.map((cell) => `${cell.x},${cell.y}`)
);The path goes from (0,2) → (3,2) horizontally, meaning cells (1,2) and (2,2) are on the enemy route. But TILE_PATH only has 7 entries (the waypoints), while the actual path covers 25 cells. The 18 intermediate cells are missing.
This causes two problems:
- Towers can be placed on the path (line 436):
isOnPath(col, row)returnsfalsefor intermediate cells like(1,2), so the placement check passes and a tower is placed directly on the enemy route. - Visual mismatch (line 183): Intermediate path cells are not highlighted with the yellow background, making the path appear as disconnected dots rather than a continuous route.
Placing towers on the path doesn't block enemies (they move by distance along the path line, not by grid cells), so the tower just wastes coins while enemies walk right through it.
Prompt for agents
In src/app/tower-defense/page.tsx, lines 70-72, TILE_PATH is built from only the waypoint cells in PATH_CELLS. It needs to include ALL cells along the path segments between waypoints. Replace the TILE_PATH construction with code that iterates over consecutive pairs of PATH_CELLS and adds every intermediate cell. For each pair of consecutive waypoints, if they share the same x coordinate, iterate over all y values between them; if they share the same y coordinate, iterate over all x values between them. Add each intermediate cell as a string 'x,y' to the Set. This will fix both the tower placement check in isOnPath() at line 133 and the visual path highlighting at line 183.
Was this helpful? React with 👍 or 👎 to provide feedback.
| requestAnimationFrame(gameLoop); | ||
| }; | ||
|
|
||
| const handle = requestAnimationFrame(gameLoop); | ||
| return () => { | ||
| cancelAnimationFrame(handle); |
There was a problem hiding this comment.
🔴 Game loop continues running after component unmount due to uncancelled recursive requestAnimationFrame
The cleanup function in the game loop useEffect only cancels the initial requestAnimationFrame handle, but once the callback fires it schedules a new frame whose handle is never stored or cancelled.
Root Cause and Impact
At lines 492-508, the game loop is set up as:
const handle = requestAnimationFrame(gameLoop);
return () => {
cancelAnimationFrame(handle);
};Inside gameLoop (line 500), requestAnimationFrame(gameLoop) is called recursively, returning a new handle that is never stored. When the cleanup runs, cancelAnimationFrame(handle) cancels the original handle (which has already fired and is a no-op), while the most recently scheduled frame continues to fire. This causes the game loop to keep running after the component unmounts, calling setGameState and setHudTick on an unmounted component indefinitely.
Impact: Memory leak and wasted CPU cycles. On every frame after unmount, simulate() runs and React state setters are called on a stale component. If the user navigates away and back, multiple game loops will stack up.
The standard fix is to use a ref or a mutable cancelled flag that the cleanup sets to true, and check it at the top of gameLoop before scheduling the next frame.
| requestAnimationFrame(gameLoop); | |
| }; | |
| const handle = requestAnimationFrame(gameLoop); | |
| return () => { | |
| cancelAnimationFrame(handle); | |
| frameHandle = requestAnimationFrame(gameLoop); | |
| }; | |
| let frameHandle = requestAnimationFrame(gameLoop); | |
| return () => { | |
| cancelAnimationFrame(frameHandle); |
Was this helpful? React with 👍 or 👎 to provide feedback.
Implement a new standalone tower defense game accessible at
/tower-defense.