move two placement queries out of the view into PlacementRules

Prerequisite for the WorldRenderer extraction: both the renderer and the click
path need these, so neither can own them.

GameWorldView::isValidPlacement was isPlacementValid plus the occupancy and
rotate-in-place rule - the gap PlacementRules.h already names in its own comment
("Tile occupancy is NOT checked here"). It becomes canPlaceBuilding there, named
for what it adds rather than colliding with isPlacementValid.

resolveBeltDragPath was a pure function of the path, the factory state and the
config, living in the view only because the view happened to draw the ghosts.
It moves next to the other placement rules, taking its BeltTileAction and
BeltDragResolved types with it out of the GameWorldView class body. It stays
shared for the reason it always was: the previewed ghosts and the placement on
release must not disagree about which tiles are affordable.

The view keeps two one-line wrappers that bind its own simulation, so call sites
still read canPlaceBuildingHere(type, anchor, rotation).

Behaviour is unchanged; both are now testable without a widget.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
This commit is contained in:
2026-08-05 18:55:21 +02:00
parent 286116d2b1
commit d54bf3587b
4 changed files with 129 additions and 84 deletions

View File

@@ -52,6 +52,7 @@
#include "CommandManager.h"
#include "EntitySelectionChangedEvent.h"
#include "GameConfig.h"
#include "PlacementRules.h"
#include "Rotation.h"
#include "SelectionController.h"
#include "Tick.h"
@@ -213,7 +214,8 @@ private:
// both edges move with asteroid expansion and with pushes (REQ-GW-SCROLL-LIMIT).
ScrollBounds getScrollBounds() const;
bool isValidPlacement(BuildingType type, QPoint anchor, Rotation rot) const;
// canPlaceBuilding (PlacementRules) against this view's simulation.
bool canPlaceBuildingHere(BuildingType type, QPoint anchor, Rotation rot) const;
std::optional<BuildingId> buildingAtTile(QPoint tile) const;
std::optional<BuildingId> siteAtTile(QPoint tile) const;
// Ids of all buildings and construction sites whose footprint intersects
@@ -276,19 +278,10 @@ private:
const WorldCoordinates& coordinates);
// Belt drag placement (REQ-BLD-BELT-DRAG).
// Per-path-tile decision, shared by ghost drawing and release-time placement.
enum class BeltTileAction { PlaceNew, RotateInPlace, Invalid };
struct BeltDragResolved
{
BeltTileAction action;
bool affordable; // meaningful only for PlaceNew
std::optional<BuildingId> rotateId; // set only for RotateInPlace
};
// Recomputes the drag path from its anchor to cursorTile using the current ghost
// orientation, and stores it on the build mode controller.
void recomputeBeltDragPath(QPoint cursorTile);
// Classifies each path tile against the current sim state, applying cumulative
// affordability to the PlaceNew tiles.
// resolveBeltDragPath (PlacementRules) against this view's simulation.
std::vector<BeltDragResolved> resolveBeltDragPath() const;
// Enqueues placements and rotate-in-place commands for the resolved path.
void applyBeltDragPath();