Skip to content

Grid Placement v6.0

Placement Rules

Configure runtime placement validation rules and understand why placement succeeds or fails.

Status
Draft
Version
v6.0
Updated
Development docs generated from GDScript source

This is unreleased documentation in active development. APIs, class names, and behavior may change before the final release.

Grid Placement validates placement in two layers:

  1. Core placement validation — dimensional correctness owned by the plugin, such as occupancy, GRID mounts, SMOOTH footprints, and 3D support/slope checks.
  2. Game-authored rules — optional project policy such as costs, unlocks, protected zones, or game-owned world facts.

Do not re-create core 3D CELL/EDGE/FACE/CORNER/TOP/slope validation in a custom 2D tile rule.

Core validation by workflow

Workflow Core checks include
2D object placement target/bounds, footprint/indicator checks, occupancy/collision, configured rules
2D terrain painting terrain target/brush validation, paint gates/hooks
3D GRID footprint occupancy, CELL/EDGE/FACE/CORNER/TOP mount validity, snap/provider rules, ground/support/slope policy
SMOOTH world footprint occupancy, optional environment/physics checks, optional sockets
2D stacking a second object onto another's cell opt-in per catalog entry via ScenePlacementEntry.stacks_on_surface / surface_size_2d_cells; no shipped 2D catalog entry declares one, so a 2D cell seats one object by default. See Mounting a second object and Physics support or surface mount

The UI should consume the current placement result/report rather than duplicate any of these checks.

Mounting a second object

2D and 3D have different mounting models. The difference is structural, not something an anchor setting closes.

  • 2D seats one object per cell by default. ScenePlacementEntry.cell_anchor_mode picks where the root sits inside that cell — it is a seat offset, not a stacking height. Stacking onto another object's surface is a separate, opt-in contract declared per catalog entry: the host sets surface_size_2d_cells (and optionally surface_height_2d for extra levels), and the small item sets stacks_on_surface. No shipped 2D catalog entry declares either half, so out of the box you get one object per cell. The entries must declare both halves; the 2D preview/commit path then resolves sockets automatically.
  • 3D selects the mount through PlacementSnapProfile3D.SnapMode: CELL, EDGE, FACE, CORNER, or TOP. TOP puts a roof on a wall-top or a ridge on a cell, and a wall-top can host another TOP piece (an upper wall or a roof).

If the question is whether a small object can sit on top of another placed object: in 3D that is SnapMode.TOP with a TOP entry in your catalog, covered in 3D Object Placement. In 2D the equivalent is the opt-in surface contract above — the host's surface_size_2d_cells plus the item's stacks_on_surface — not an anchor mode, and it takes entries declaring both halves.

TOP is keyed to a host wall edge, not to the top face of an arbitrary prop, and a TOP placement is rejected when that edge holds no host wall. So in 3D there is no mount that seats a small item on the surface of, say, a 2x3 table; the 2D equivalent is the separate opt-in surface contract above.

For the beginner-facing version of this choice — when a collision support rule (like the platformer box's must-be-on-Buildable check) is right, when an explicit surface slot is right, and when one entry should carry both — see Physics support or surface mount. That section also pins the lifecycle boundaries: preview/commit agreement, save/load, slot freeing, and the explicitly unsupported behaviors (no auto-parenting, no carry, no cascading removal, no physical simulation).

Game-authored rule classes

For the current 2D rule pipeline:

Base class Use when
PlacementRule The rule checks game/session state and does not need per-cell indicator positions.
TileCheckRule The rule needs 2D covered cells/indicator-specific feedback.
RuleResult Carries pass/fail issues from rule validation.

TileCheckRule is a 2D tile/indicator concept. It is not the extension point for 3D mount or slope algorithms.

See Custom Placement Rules for authoring examples.

Where rules are configured

Location Scope
PlacementSettings.placement_rules Shared/default rules.
PlacementProfile.placement_rules Rules shared by a category/profile.
ScenePlacementEntry.placement_rules Rules unique to one entry.

Start with the narrowest scope that matches the requirement. Promote a rule to a profile/global setting only when several entries genuinely share it.

Base-rule overrides

Profiles/entries can opt out of inherited base rules where the public configuration allows it. Use that deliberately.

Good reason: a decoration category intentionally has different overlap policy.

Bad reason: bypassing a failing core placement check instead of fixing the configuration/collision/support problem.

Costs and inventory

Grid Placement does not include an inventory. SpendMaterialsRuleById, SpendByIdRule and RefundService work with any object your game provides that implements three id-keyed methods (see PlacementCostProvider):

  • get_count_by_id(id: StringName) -> int
  • try_remove_by_id(id: StringName, amount: int) -> int
  • try_add_by_id(id: StringName, amount: int) -> int

The rule checks availability during validation and spends at the successful lifecycle stage. Do not subtract the same materials again from UI/success handlers.

Limits of spending through the plugin

The plugin cannot see where your inventory ends, so these rules are a convenience for prototypes and simple games:

  • It calls one provider per rule. It does not know about reservations, multiple containers, crafting queues, or items another system is about to consume.
  • A spend is made atomic by refunding with try_add_by_id when a later id falls short. If your inventory can reject that add (stack caps, full bags), the rollback is incomplete.
  • There is no transaction, save, or network authority. In multiplayer, the plugin's spend is not the server's spend.
  • Refunds on demolish add through the same protocol, and whatever the provider refuses is logged and lost.

For a shipped game, integrate costs in your own inventory code. Either write a small PlacementRule subclass that calls your inventory's typed API directly in validate_placement() and apply(), or leave cost rules out and spend and refund in your game code when a placement succeeds or an object is demolished (the placement report and the manipulation result tell you which entry was placed or removed). Your inventory then stays the single authority for what the player owns.

World facts

When placement must respect facts owned by your game rather than plugin collision/occupancy, expose those facts through the appropriate provider/bridge instead of importing game classes into plugin rules.

The shipped PlacementWorldFactsProvider2D / ProviderCellBlockRule2D path is specifically for 2D cell facts such as reserved or externally occupied cells.

See Placement World Facts Provider.

Collision and indicators

For 2D tile/indicator rules, collision shapes can define the covered footprint used by tile checks. Make sure collision layers/masks match what the rule/targeting path is expected to see.

Simple mental model:

  • layer = what an object is on;
  • mask = which layers a query checks.

If a 2D collision rule does not see an object, inspect the layer/mask setup before weakening the rule.

Validation must be side-effect free

Preview validation can run repeatedly. Do not spend inventory, save progression, spawn permanent objects, or trigger irreversible game state from validation.

Perform irreversible effects only after the placement action succeeds through the supported lifecycle/apply/result path.

Placement vs manipulation vs terrain

These workflows do not all use identical rule sources:

  • New object placement uses placement/profile/entry rules plus core dimensional validation.
  • Manipulation revalidates the moved/rotated object's supported placement state through the manipulation path.
  • 2D terrain uses terrain validation/paint gates/hooks rather than pretending terrain cells are scene-object rules.

Keep policy in the workflow that owns the state being changed.

UI rule

UI displays placement outcomes. It should not become a second validator.

Good:

  • show report issues;
  • show locked/unavailable entries from session state;
  • refresh inventory after successful placement.

Avoid:

  • re-running collision/support logic in UI;
  • spending resources in both rules and UI;
  • turning a green preview into an assumption that commit already succeeded.

Related guides