feat: matchmaking#3
Conversation
| function Game:handle_received_data(message) | ||
| local data = json.decode(message.match_data.data) | ||
| local user_id = message.match_data.presence.user_id | ||
| local opcode = data.opcode |
There was a problem hiding this comment.
Towers are drawn twice per frame
Medium Severity
Towers are stored at integer keys (via table.insert in load_towers) and chars at string keys. The pairs loops in Game:draw() iterate ALL entries including towers, calling card:draw() on them. Then Game:draw_towers() iterates the same towers again via ipairs and draws them a second time. The newly added enemy tower loop in draw_towers (lines 150-152) extends this double-rendering to enemy towers.
Additional Locations (1)
| if type(users) ~= 'table' then | ||
| return nil | ||
| end | ||
|
|
There was a problem hiding this comment.
Nil enemy ID causes deferred crash
Medium Severity
get_enemy_user_id returns nil when users isn't a table, which sets Constants.ENEMY_ID = nil — removing the key entirely. Any later access to Constants.ENEMY_ID (e.g., in Game:load()) triggers the __index error metamethod from Constants, producing a confusing crash far from the root cause. The guard returns silently but lets the join flow proceed without a valid enemy ID.
Additional Locations (1)
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit bda50ba. Configure here.
| self.cards[Constants.USER_ID][pending.card_id] = nil | ||
| end | ||
| self.pending_spawns[data.client_intent_id] = nil | ||
| end |
There was a problem hiding this comment.
Reject intent skips hand rollback
High Severity
When the server sends reject_intent, the client removes the predicted field unit but does not undo the hand changes from Deck:mousepressed. The played card keeps is_card_loading and cooldown, and rotate_deck is not reversed, so the hand no longer matches the rejected spawn.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit bda50ba. Configure here.


Note
Medium Risk
Moderate risk because it replaces the in-match networking protocol and client entity lifecycle (spawn/update/remove), which can cause desyncs or stuck entities if server/client opcodes or versions mismatch.
Overview
Moves the match flow to an authoritative server model: the client now sends
spawn_intent(withclient_intent_id) and updates local state from serverstate_snapshot/entity_*events, including intent rejection and match end handling.Adds client-side prediction for spawned cards and smoother authoritative reconciliation in
Game:apply_entity_state, plus death/removal queuing so entities play a death animation before being deleted. Updates deck interaction to spawn viaGame:spawn_card_intentinstead of directly broadcasting card spawn/action.Includes related rendering/UX tweaks (scaled deck drawing + cooldown overlay, char preview/lifebar rendering, tower IDs + scaling, RNG seeding, image loading retry with optional bearer auth) and removes unused legacy card modules/config/UI files.
Reviewed by Cursor Bugbot for commit bda50ba. Bugbot is set up for automated code reviews on this repo. Configure here.