Use PlacementWorldFactsProvider2D when 2D TileMapLayer placement must respect world facts owned by your game rather than Grid Placement.
Examples:
- reserved build cells;
- occupancy stored in another simulation/registry;
- protected zones;
- server/game-state restrictions represented by cells.
This provider is specifically a 2D cell-facts bridge. Do not treat it as the 3D slope/mount validation API; 3D core support is documented separately.
Why this exists
Many games already know which cells are off-limits before Grid Placement enters the picture: a town sim owns its plots, a server owns build permissions, a strategy layer owns territory. That knowledge lives outside the plugin, and it should stay there.
Without a provider you have three bad options: copy that truth into the plugin (now two authorities can drift), write a full custom rule for what is really a per-cell yes/no question, or build a whole game bridge for a veto. The provider is the fourth option: implement two methods — is this cell blocked, and why — and the prebuilt ProviderCellBlockRule2D feeds your answers into normal preview and commit validation for both object placement and terrain painting. No duplicated state, no custom rule class, no bridge.
Mental model
game-owned world data
→ PlacementWorldFactsProvider2D
→ ProviderCellBlockRule2D
→ normal preview/final placement validationThe game owns the data. The provider translates it into the small question Grid Placement needs: is this cell blocked, and why?
Minimal provider
class_name MyWorldFactsProvider
extends PlacementWorldFactsProvider2D
var blocked_cells:Dictionary = {}
func is_cell_blocked(cell:Vector2i)-> bool:
return blocked_cells.has(cell)
func get_block_reason(cell:Vector2i)-> String:
return String(blocked_cells.get(cell,""))The provider can read a dictionary, occupancy registry, ECS/simulation, physics integration, server state, or another game-owned source. Keep source-specific dependencies inside your project adapter, not inside generic addon rules.
Connect it to the active session
Set the provider on the targeting state's world_facts_provider_2d field, then add ProviderCellBlockRule2D to the settings/profile/entry that should respect it.
Start with the rule on the single entry you are testing. Promote it to a profile or settings only once the policy proves global — a rule attached too broadly blocks placements you have not thought about yet.
Failure reasons
Return short actionable reasons such as:
"Reserved for another structure""Occupied by town hall""Protected build zone"
The normal placement result/report should carry this reason to preview/commit UI. Do not repeat the provider query in UI code.
Provider behavior
A provider should be:
- read-only during validation;
- deterministic for the current world snapshot;
- cheap enough to query during preview;
- independent from the addon implementation;
- safe when its backing game service is unavailable.
Choose fail-open or fail-closed behavior deliberately for your game. If missing provider data would make placement unsafe, return a blocking fact rather than silently pretending the cell is free.
When not to use this
Do not add a provider when built-in placement facts already answer the question.
Examples:
- normal Grid Placement object occupancy;
- 3D CELL/EDGE/FACE mount occupancy;
- 3D slope/support;
- SMOOTH footprint overlap.
Duplicating those facts in a game provider creates two authorities that can drift.
Checklist
- Basic placement works before the custom provider is added.
- Known blocked/allowed cells return the expected results.
- Provider is assigned to the active session's 2D targeting path.
-
ProviderCellBlockRule2Dis attached at the intended scope. - Preview and final commit show the same provider result.
- UI displays the placement issue instead of re-running the world query.