tilemap-parserv5.2.0GITHUB

Technical Notes

Edge conventions, scale rules and performance facts that the guides assume. All of these are the library's actual behavior, stated precisely.

EDGE CONVENTIONS

  • Polygon queries are half-open on right and bottom edges. A sprite sitting exactly on a tile's right or bottom boundary does not count as colliding; the left and top edges are inclusive. This is what lets two solids rest flush without false positives.
  • Object-manager AABB checks are inclusive: touching bounding boxes in ObjectCollisionManager count as overlapping, so resolve() pushes apart with zero overlap.

RENDER_SCALE

  • TilemapData.render_scale is adopted by PhysicsWorld and by the runner on attach; rendering and collision stay in the same pixel space.
  • Effective tile size is tile_size × render_scale; tile (col, row) occupies (col × eff_w, row × eff_h). A render_scale ≤ 0 raises ValueError in both the renderer and the runner.
  • Polygon vertices are scaled by render_scale when transformed to world space; author collision in tile-local pixels, scale for free.

BODY MODES ARE PROMISES

"static" and "kinematic" do not imply physics-engine dynamics. Velocity is scripted, Godot StaticBody2D/CharacterBody2D style. A kinematic body moves only when your loop drives it through a move_* call with explicit velocity.

ONE-WAY, PRECISELY

  • Only move_platformer and move_platformer_with_slide honor one_way polygons (block from above, pass from below).
  • The flag is authored in the collision JSON, per polygon — never in the map JSON, and never auto-detected. Control is authoring-level only: there is no runtime per-sprite "make platforms solid" override. Your levers are the JSON flag and the movement method you call.
  • Gating is approach-based: a one-way polygon blocks only when the sprite is falling (vy > 0) and its previous bottom was above the platform top — fast falls can't tunnel, and jumping up through always passes.
  • The horizontal phase never sees one-way polygons (they can't wall you); the landing snap re-includes them with from-above gating.
  • move_grounded treats one-way polygons as plain solid geometry.
  • Bodies are never one-way; they block from every direction.
  • The renderer doesn't know the flag exists: one-way tiles draw like any other tile. For dashed-edge overlays, query world.tile_map + the collision tileset at runtime (see the Map Parsing page).

VELOCITY CONTRACT, RECAP

Physics modes with velocity=(vx, vy): skip gravity/input/jump, adopt the velocity onto the sprite, zero vx on wall hit, zero vy on landing. Displacement modes (move_and_slide and move_rpg) never read or write vx/vy.

PERFORMANCE NOTES

  • Movement queries are zero-allocation on the hot path. _collides_at iterates tiles and shapes inline and exits on the first hit; get_nearby_tile_shapes (which allocates) exists for your own queries, not for movement.
  • The runner reuses one CollisionResult. Fields are reset per call. Read the return value in the same frame.
  • Rendering is chunk-culled (32×32-tile chunks), with per-layer z_index sorting and a tile-variant cache. warm_cache() pre-bakes variants and frees the source map data.
  • Object collision uses a uniform spatial grid rebuilt per check_all_collisions(); single-object queries are a linear scan. cell_size (default 128) is the tuning knob; benchmark with examples/comparison/spatial-cell-size-tuning.py.
  • Circle/capsule bodies are polygon-approximated (16-edge ngon / stepped capsule) only for slide-mode normal computation; the tile resolver works on polygon edges.

CAMERA FACTS

  • mode="centered": target always at viewport center (lerp if lerp_speed > 0).
  • mode="deadzone": camera only moves when the target leaves a centered box.
  • shake(duration, intensity) adds a random per-frame offset inside offset; it does not move the target. bounds = (min_x, min_y, max_x, max_y) clamps the camera position.

THE WORLD OWNS, THE RUNNER RESOLVES

One space, one runner. Tiles and bodies are solids in the same space; the runner resolves movement against the union. The world does not simulate sprite-vs-sprite contact; that's ObjectCollisionManager. If you can draw it, you can move it; if you can move it, it can be pushed.

GID OWNERSHIP IS A RANGE TEST, NOT A SUBTRACTION

With use_gids=True, tile ids are firstgid + local_variant per grid resource. Collision files stay local-keyed. Resolving a gid therefore never does a blind gid - firstgid_of_owner: the world first finds which grid resource's window [firstgid, firstgid+count) owns the id, rejects ids owned by non-collision resources (decoration grids), and only then subtracts. This kills cross-tileset aliasing — e.g. gid 1813 - 90 = 1723 must not light up jungle-local key 1723. See GID routing on the runner page.