BuildingPlacementController.approve() commits a building placement fully on the client before the server has actually confirmed anything:
grid.occupy(placing, position.x, position.z, placing.getFootprint());
placing.finishMoving();
...
persistPlacement(placing, position); // just sends the event, does not wait
windowManager.hideBuildConfirm();
windowManager.toast(Localization.getString("BUILDING_PLACED"));
placing = null;
persistPlacement sends SCENE_EVENT_CHANGE_BUILDING_POS to the server and returns immediately, it does not wait for or check the result. On the server side, WorldHandlers.ts calls world.placeBuilding(...) and pushes the result code back wrapped in a generic SCENE_EVENT (400 out of bounds, 403 not yours, 409 cell occupied, 200 success).
I went looking for where the client reads that result back and I don't think it exists. UserSceneSocketBridge.onSceneEvent switches on the scene event type, and it only handles SCENE_EVENT_VERSION, CHAR_MESSAGE, SCENE_EVENT_GET_BUILDING, and SCENE_EVENT_CHANGE_CAMERA_POS. SCENE_EVENT_CHANGE_BUILDING_POS isn't one of the cases, so it falls into the default branch and just gets logged as "Unhandled scene event."
So if the server ever rejects a placement (say the grid race condition in the other issue lets two placements land on the same cell and the second one gets a 409), the player already saw "Building placed", the local grid already marked those cells occupied, and there's no code path that walks any of that back. The client and server end up disagreeing about where that building is (or whether it's placed at all), and the player won't find out until the world reloads from the server and the building isn't where they left it.
Feels like persistPlacement needs some way to get the ack back, and SCENE_EVENT_CHANGE_BUILDING_POS needs a case in onSceneEvent that rolls back grid.occupy and the building's position/toast when the result code isn't 200.
Files: core/src/com/focus/kingdom/world/building/BuildingPlacementController.java (approve(), around line 170-192) and core/src/com/focus/kingdom/world/sync/UserSceneSocketBridge.java (onSceneEvent, around line 196-223).
BuildingPlacementController.approve()commits a building placement fully on the client before the server has actually confirmed anything:persistPlacementsendsSCENE_EVENT_CHANGE_BUILDING_POSto the server and returns immediately, it does not wait for or check the result. On the server side,WorldHandlers.tscallsworld.placeBuilding(...)and pushes the result code back wrapped in a genericSCENE_EVENT(400 out of bounds, 403 not yours, 409 cell occupied, 200 success).I went looking for where the client reads that result back and I don't think it exists.
UserSceneSocketBridge.onSceneEventswitches on the scene event type, and it only handlesSCENE_EVENT_VERSION,CHAR_MESSAGE,SCENE_EVENT_GET_BUILDING, andSCENE_EVENT_CHANGE_CAMERA_POS.SCENE_EVENT_CHANGE_BUILDING_POSisn't one of the cases, so it falls into the default branch and just gets logged as "Unhandled scene event."So if the server ever rejects a placement (say the grid race condition in the other issue lets two placements land on the same cell and the second one gets a 409), the player already saw "Building placed", the local grid already marked those cells occupied, and there's no code path that walks any of that back. The client and server end up disagreeing about where that building is (or whether it's placed at all), and the player won't find out until the world reloads from the server and the building isn't where they left it.
Feels like
persistPlacementneeds some way to get the ack back, andSCENE_EVENT_CHANGE_BUILDING_POSneeds a case inonSceneEventthat rolls backgrid.occupyand the building's position/toast when the result code isn't 200.Files:
core/src/com/focus/kingdom/world/building/BuildingPlacementController.java(approve(), around line 170-192) andcore/src/com/focus/kingdom/world/sync/UserSceneSocketBridge.java(onSceneEvent, around line 196-223).