Skip to content

Claim bridges square by square instead of all at once - #34

Merged
tomluchowski merged 1 commit into
tomluchowski:shaders-improvementfrom
Upabjojr:split/bridge-claim-per-tile
Aug 21, 2026
Merged

Claim bridges square by square instead of all at once#34
tomluchowski merged 1 commit into
tomluchowski:shaders-improvementfrom
Upabjojr:split/bridge-claim-per-tile

Conversation

@Upabjojr

@Upabjojr Upabjojr commented Aug 6, 2026

Copy link
Copy Markdown

Proposal 3 of the set following the mechanics discussion in #23 — Tom's "I prefer the bridge is to be taken square by square".

Until now a bridge held a single claim pool for the whole room (RoomBridge::mClaimedValue): a worker dancing on any one square drained the shared pool, and the tick it hit zero the entire bridge — every tile, however long — flipped to the claimer at once. That is the "claim a whole bridge while standing on one square" behaviour Tom noticed.

With this PR each bridge tile carries its own claim progress (BridgeTileData::mClaimedValue, mirroring how traps already do per-tile claiming):

  • An enemy worker dances a single square down; when it falls, only that square changes hands. It moves out of the victim's bridge room into a bridge owned by the claimer (new one-tile room, immediately merged with any adjacent bridge of theirs via the existing checkForRoomAbsorbtion).
  • If the claimed square cuts the victim's bridge in two, the recently merged room splitting (Split a room in two when it stops holding together #23) takes care of the rest — each stretch becomes its own room, so the fronts on both sides stay independent.
  • Since the tile stays a bridge tile throughout, pathing/flood-fill is unaffected for every seat; the bridge never stops being crossable while contested.
  • Claiming back and forth works: the same square can be danced back tile-by-tile.

Save compatibility: the stream format still carries one claim value per bridge (now the sum of the per-tile values, distributed evenly on load), so existing level files and saves load unchanged.

Deliberately not changed: portals (RoomPortal/RoomPortalWave) keep their whole-room claim — a portal is one logical entity, and taking it square-by-square would mean two players sharing a portal, which seems like its own design discussion.

Validated: builds and the full test suite passes via ctest. The tile-transfer path reuses the same machinery the merged room splitting already exercises (aa-TestRoomSplit); a dedicated dancing-worker scenario test would need a new level fixture and is left for when the mechanic is agreed on.

🤖 Generated with Claude Code

A bridge held a single claim pool for the whole room: an enemy worker
dancing on any one square drained the shared pool and, the tick it hit
zero, every tile of the bridge changed hands at once -- however long
the bridge and wherever the worker stood.

The claim progress now lives per tile (BridgeTileData, mirroring how
traps already track per-tile claims). A worker dances one square down
and only that square changes hands: it moves into a bridge owned by the
claimer, merging with an adjacent bridge of theirs through the existing
checkForRoomAbsorbtion, and if the loss cuts the victim's bridge in two
the room splitting takes care of the halves. The tile stays a bridge
tile throughout, so pathing is never interrupted and flood fill does
not change.

The stream format still carries one claim value per bridge (the sum of
the per-tile values, shared out evenly on load), so existing level
files and saves load unchanged. Portals keep their whole-room claim.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread source/rooms/RoomBridge.cpp
// bridge gets a copy of the tile data, this one keeps the original marked
// destroyed so seats that still think this bridge covers the tile can keep
// asking it. The tile stays a bridge tile throughout, so pathing across it
// is never interrupted for anybody and no flood fill has to change.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Still I don't get it why we should mark orignal Tile as destroyed .... WHY ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Because the tile is not destroyed for the old bridge's owner's knowledge of the world — it is destroyed for the old room's bookkeeping, and that distinction is exactly what the rest of the codebase uses mCoveredTilesDestroyed + mHP = 0 for whenever a building loses a tile without disappearing:

  1. Fog of war. The enemy seat may have seen the bridge earlier but have no vision of the square right now. That seat's view still says "this tile is covered by that bridge", and it keeps asking that room about it. The destroyed entry keeps those queries valid, and when the old bridge is eventually removed, Room::removeFromGameMap walks mCoveredTilesDestroyed to send each seat notifyBuildingRemovedFromGameMap — without the entry, a stale-vision seat would never be told and would keep a dangling reference to a dead room.
  2. Save/load. Building::exportToStream serialises mCoveredTiles + mCoveredTilesDestroyed — the destroyed list is part of the room's persistent footprint, so a game saved mid-claim round-trips.
  3. It is the established handover protocol, not something new in this PR. Building::removeCoveredTile does the identical three lines (erase from covered, push to destroyed, mHP = 0); Room::absorbRoom marks every absorbed tile destroyed with the in-code comment "It will get removed from gamemap when enemy vision will be cleared"; and checkForSplit() from Split a room in two when it stops holding together #23 — already merged — hands tiles to the split-off room the same way. This PR just follows the same convention for the claim handover.

Note the tile itself is fine throughout — it stays a walkable bridge tile, covered by the new bridge. "Destroyed" here only means "no longer mine, but remember I used to cover it until everyone has been told".

@tomluchowski

Copy link
Copy Markdown
Owner

ForgottenTreasures2.level.txt
Please try loading and tinkering with this particular level : before applying this commit one can see the Bridge Tiles meshes, after applying commit they are gone for some reason...

@tomluchowski

Copy link
Copy Markdown
Owner

You might need to remove the last filetype extension -- 'txt'

@Upabjojr

Upabjojr commented Aug 8, 2026

Copy link
Copy Markdown
Author

I will be without computer for two weeks. Cannot reply right now.

@Upabjojr

Copy link
Copy Markdown
Author

Reproduced with your level, tracked it down — the bridges aren't gone, they've stopped rendering, and the cause is a pre-existing client bug that this PR's per-tile claiming makes visible: #36.

What happens: on that map each keeper's wooden bridge sits right next to the other keeper's portal, so enemy workers start claim-dancing on bridge squares within the first minute. Every claimed square triggers a tile recolour on the client, and colourizeMaterial() has been cloning the already-colourized material on every recolour since the ## separator was commented out in the Z pre-pass work — the material name grows one Color_N_ suffix per refresh (WoodBridgeWoodBridgeColor_1_WoodBridgeColor_1_Color_2_ → …), and a few clones down the chain the material stops rendering, so the planks wink out square by square. I verified the server keeps sending the right thing throughout (hasBridge=1, WoodBridge.mesh, water visual) — with the old whole-bridge claiming you only ever got one recolour per tile, which is why this never showed before.

With #36 applied on top, your level behaves as intended: loading it in the editor and in skirmish looks identical to the old code, and when the enemy workers claim the bridge the squares change to the claimer's colour one at a time, all 27 planks staying visible through 14 handovers in my test run.

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.

2 participants