Commit Graph

5 Commits

Author SHA1 Message Date
64bc6cfe8f target single-building transfers by hovering, not by footprint coincidence
Coincidence required the ghost's anchor to land exactly on the target's
anchor, so on a 2x2 assembler three of its four tiles read as an ordinary
overlap and went red: you had to find the top-left tile before the copy
gesture would fire. A single-building blueprint now finds its target by
hit-testing the cursor instead -- any body tile, any facing -- and its ghost
snaps onto the target so it shows what the click will act on.

A constellation is unchanged: it is placed as a layout, so its ghosts stay
where the blueprint puts them and still need coincidence plus a matching
rotation. The old "single-building blueprints ignore target rotation"
carve-out is gone, subsumed by hovering.

blueprintHoldsOneBuilding becomes std::optional<QPoint> hoverTile, which
carries both facts in one parameter: set means "single-building blueprint,
cursor here". BlueprintGhostResolved gains ghostAnchor/ghostRotation so the
snap comes out of the classifier both callers already share, rather than
the renderer working it out separately.

Note the shipyard, which I had cited as the motivating case, is not one: its
mask needs A cells on asteroid and S cells in space, and the asteroid edge
is vertical, so a rotated shipyard can never be placed at all. Rotation was
never the real problem here; alignment was.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
2026-08-06 20:24:32 +02:00
49e1c0ef20 let any blueprint transfer configuration, not just single-building ones
Implements 0593f48. A coinciding building of a configurable type now takes
the blueprint's settings whatever the blueprint's size, so dropping a
constellation over a partial copy of itself configures what is already
standing there instead of leaving it blank.

The whole behaviour change is one branch in resolveBlueprintGhost: at a
matching rotation, a coinciding building transfers if its type has settings
and is a compatible overlap otherwise; at a differing rotation only a
single-building blueprint still transfers. GameWorldView and WorldRenderer
needed nothing -- they already switch on the action, already exclude
transfers from the cost, and already tint them.

blueprintHoldsOneBuilding survives only as that rotation carve-out, and the
header now says so.

Note for anyone testing this by hand: the carve-out is narrower than it
reads. It is only reachable for footprints that survive rotation -- 1x1
bodies like the splitter, or fully filled symmetric ones. A miner's body is
L-shaped ("AA" / "A>"), so a rotated miner ghost covers different tiles and
coincides with nothing at all. That is the coincidence test doing its job,
not these rules, but it cost one wrong test before it was noticed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
2026-08-06 19:59:41 +02:00
3cf35b669e implement blueprint compatible overlap and configuration transfer
Implements the two rules specified in 128bf81, which restore the
copy-settings capability removed in 648241b as a property of blueprint
placement: press C on one configured building, then click same-type
buildings to stamp its settings across the factory.

The ghost colour and the click path already shared one predicate,
canPlaceBuilding, which is what kept preview and outcome from disagreeing.
The new rules make that answer four-valued, so the shared predicate becomes
a shared classifier: resolveBlueprintGhost in PlacementRules returns
PlaceNew / CompatibleOverlap / Transfer / Invalid, and both callers switch
on it. Putting it in lib rather than in the view is the whole testability
story -- src/ui is off the test include path.

findRotateInPlaceTarget is now the tunnel guard plus a shared
findCoincidingSameTypeBuilding core, so the two rules cannot drift apart;
its existing tests pass untouched. Blueprint placement no longer emits
RotateInPlaceCommand at all. Builder mode and the belt drag are unchanged.

transferConfigTo sends every field unconditionally, so a field the
blueprint stores nothing for clears the target's rather than leaving it.
The simulation's unchanged-value guards absorb the no-op case, which is
what keeps clicking an already-matching building free of buffer and
production-progress loss.

drawBuildingGhost's bool valid becomes GhostTint{Normal,Invalid,Transfer};
the transfer colour is taken at full RGB like the invalid one so it does
not double-dim against the ghost opacity. visuals.toml regains the overlay
colour under the name config_transfer, with its VisualsConfig field and
loader line -- overlay keys are mandatory, so the three move together.

The six new resolveBlueprintGhost cases failed on first run because the
test buildings were anchored in space: BuildingSystem::place skips the
terrain rules, resolveBlueprintGhost applies them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
2026-08-06 19:20:34 +02:00
dc83add5c6 move two placement queries out of the view into PlacementRules 2026-08-05 22:14:54 +02:00
d87d063b10 move the placement rules and the config-dependent queries off BuildingSystem 2026-08-05 06:49:49 +02:00