Skip to content

feat: matchmaking#3

Merged
ropoko merged 16 commits into
mainfrom
feat/matchmaking
May 28, 2026
Merged

feat: matchmaking#3
ropoko merged 16 commits into
mainfrom
feat/matchmaking

Conversation

@ropoko
Copy link
Copy Markdown
Collaborator

@ropoko ropoko commented Mar 27, 2026

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 (with client_intent_id) and updates local state from server state_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 via Game:spawn_card_intent instead 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.

Comment thread src/states/lobby.lua
Comment thread src/entities/cards/char.lua Outdated
Comment thread src/entities/cards/char.lua
Comment thread src/states/game.lua
Comment thread src/entities/deck.lua
Comment thread src/states/game.lua
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

Comment thread src/states/lobby.lua
if type(users) ~= 'table' then
return nil
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

Copy link
Copy Markdown

@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 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ 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.

Comment thread src/states/game.lua
self.cards[Constants.USER_ID][pending.card_id] = nil
end
self.pending_spawns[data.client_intent_id] = nil
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit bda50ba. Configure here.

@ropoko ropoko merged commit 75ed3c5 into main May 28, 2026
3 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.

1 participant