1eca61e934257e7e9134fa809ddb6f58e2dfe21a
The selection rules were written out twice - once for point clicks and once for box drags - and the two copies had already drifted. Unlike the earlier seams on this branch this is a real de-duplication, not a relocation. SelectionController owns all three categories and the rules for moving between them: buildings win over field objects, so selecting a building clears actors and debris and selecting either of those clears buildings, while actors and debris coexist. It also publishes the change events, so callers never emit by hand. A point click and a box drag now differ only in the SelectionMode they pass and in how many hits they pass. That names the difference that was previously implicit in two separate loops: Ctrl+click toggles, so dragging back over a selected building would deselect it, whereas Ctrl+box adds and never deselects. Hit-testing stays in GameWorldView, which is what keeps the controller free of any simulation dependency and therefore testable - including the published events, via a spy handler. That coverage is the point: these rules had none, and the two copies were the kind of thing that drifts silently. One behaviour difference, deliberate. Clearing the building selection was guarded by "only if non-empty" in three of the four places and unguarded in the fourth (the empty-box drag), which emitted a redundant SelectionChangedEvent with an empty list. The unified rule is the guarded one. Subscribers re-read from Simulation on every event, so dropping a redundant no-op refresh cannot change what any of them display. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
Description
No description provided
Languages
C++
96%
CMake
2.2%
Python
1.2%
C
0.6%