Skip to main content

Map Markers v1

Status: contract locked for implementation.

This document defines the v1 marker contract that implementation should follow. It is not yet a shipped stable Lua runtime API, but the contract below should now be treated as locked unless implementation reveals a concrete issue.

Current implementation note:

  • authoritative server-side marker enter/exit/hit tracking has now started against this contract
  • this still does not mean the full public scripting API is declared stable yet

Scope​

Markers are authored map elements with controlled visual and trigger data.

Markers are not:

  • arbitrary Godot node graphs
  • arbitrary mesh/material/shader property bags
  • a replacement for checkpoints or race pickups

Markers should remain a distinct family because they are lighter-weight than checkpoints and pickups and can exist without gameplay progression/effect semantics.

Marker Types​

  • cylinder
  • arrow
  • corona
  • ring

Explicitly not marker types:

  • checkpoint
  • race_pickup

Current intended client visual mapping:

  • cylinder -> MeshInstance3D using CylinderMesh
  • arrow -> MeshInstance3D using cone-shaped CylinderMesh
  • corona -> Sprite3D or billboarded quad
  • ring -> MeshInstance3D using TorusMesh

These are presentation details. The authored and baked APIs stay marker-domain specific.

Authority Split​

Server-authoritative:

  • marker existence
  • marker transform and size
  • marker inclusion in authoritative baked trigger/query data
  • future marker hit/effect semantics when gameplay uses them

Client-only:

  • mesh/sprite choice
  • shader/material setup
  • other purely visual effects

Authored API​

Marker v1 exposes one distinct authored family:

  • kind = "marker"
  • markerType = "cylinder" | "arrow" | "corona" | "ring"

Marker v1 does not overload markerType with checkpoint or pickup semantics.

Authored source should expose a marker record under the map source package.

Current working editor source still uses the flat primitives[] shape. The v1 target authored shape is:

{
"id": "marker_01",
"kind": "marker",
"markerType": "cylinder",
"position": { "x": 0.0, "y": 2.0, "z": 0.0 },
"scale": { "x": 4.0, "y": 4.0, "z": 4.0 },
"color": { "r": 0.98, "g": 0.72, "b": 0.18, "a": 0.88 },
"visible": true,
"collision": true,
"render": true,
"style": {
"opacity": 1.0,
"emission": 1.0,
"billboard": false
}
}

Authored field meanings:

  • id: stable marker id inside the map package
  • kind: always marker
  • markerType: one of the supported marker visual families
  • position: authored local position
  • rotation: authored local visual rotation in degrees
  • scale: authored visual/trigger scale
  • color: authored display tint in 0.0..1.0
  • visible: editor/runtime visibility intent
  • collision: whether the marker participates in authoritative baked query data
  • render: whether the marker has a baked client render descriptor
  • style: optional visual-only presentation parameters

Required fields:

  • id
  • kind
  • markerType
  • position
  • scale
  • rotation
  • visible
  • collision
  • render

Optional fields:

  • color
  • style

Validation rules:

  • kind must be marker
  • markerType must be one of cylinder, arrow, corona, ring
  • scale.x, scale.y, and scale.z must be positive numbers
  • rotation.x, rotation.y, and rotation.z, when present, must be numbers in degrees
  • color channels, when present, must be in 0.0..1.0
  • style.opacity, when present, must be >= 0.0
  • style.emission, when present, must be >= 0.0
  • style.billboard, when present, must be boolean
  • style remains presentation-only and must not affect authoritative gameplay behavior

Defaults when optional fields are omitted:

  • color = { r = 0.98, g = 0.72, b = 0.18, a = 0.88 }
  • style.opacity = color.a
  • style.emission = 1.0
  • style.billboard = (markerType == "corona")

Baked Authoritative API​

The authoritative baked marker descriptor belongs in baked runtime query/collision payloads. This descriptor is authoritative and server-owned.

V1 descriptor:

{
"markerId": "marker_01",
"markerType": "cylinder",
"position": { "x": 0.0, "y": 2.0, "z": 0.0 },
"size": { "x": 4.0, "y": 4.0, "z": 4.0 },
"enabled": true
}

V1 rules:

  • only markers with collision = true are included in the authoritative baked marker table
  • authoritative baked data must stay independent from Godot-specific mesh/material choices
  • markerType is carried for family/type discrimination only
  • checkpoint progression and pickup effects must not be encoded as generic marker runtime data
  • v1 authoritative marker data does not include effect metadata

Authoritative runtime meaning:

  • position and size define the marker query volume
  • server authority decides whether a player/vehicle is inside the marker
  • clients must not authoritatively decide marker overlap

Runtime Marker Semantics​

V1 marker semantics are deliberately narrow:

  • markers are authoritative query/trigger volumes
  • markers do not carry built-in gameplay effects in v1
  • marker events are observation events, not effect-definition objects
  • if game logic wants teleport/speed/etc., that will be layered later on top of markers or separate gameplay families

V1 overlap rules:

  • the server evaluates marker overlap against the controlled player/vehicle state
  • a marker becomes entered when an entity was outside on the previous authoritative update and inside on the current authoritative update
  • a marker becomes exited when an entity was inside on the previous authoritative update and outside on the current authoritative update
  • hit in v1 is defined as the same moment as entered
  • no repeated hit spam while remaining inside the same marker

V1 scope limits:

  • no built-in cooldown field
  • no built-in one-shot consumption field
  • no marker-owned effect fields
  • no checkpoint progression semantics
  • no pickup semantics

Baked Render API​

The render descriptor belongs in map/map_render.json.

V1 descriptor:

{
"id": "marker_01",
"markerType": "cylinder",
"position": { "x": 0.0, "y": 2.0, "z": 0.0 },
"rotation": { "x": 0.0, "y": 0.0, "z": 0.0 },
"scale": { "x": 4.0, "y": 4.0, "z": 4.0 },
"visible": true,
"effectiveVisible": true,
"color": { "r": 0.98, "g": 0.72, "b": 0.18, "a": 0.88 },
"opacity": 1.0,
"emission": 1.0,
"billboard": false
}

V1 rules:

  • only markers with render = true should emit a render descriptor
  • markerType is the only marker-family discriminator in v1 render data
  • render descriptors may grow presentation-only fields without changing authoritative gameplay meaning
  • corona remains a sprite/billboard presentation concern in render data, not an authored engine-component contract
  • billboard is allowed as a render hint only

Required baked render fields:

  • id
  • markerType
  • position
  • scale
  • visible
  • effectiveVisible
  • color
  • opacity
  • emission
  • billboard

Current internal implementation note:

  • internal bake currently emits marker presentation data into a dedicated markers array in map/map_render.json
  • that internal shape is now largely aligned with this v1 contract
  • this page still describes the intended public contract, not an exposed stable runtime API

Runtime Events​

V1 external runtime event direction:

  • map:marker_hit
  • map:marker_enter
  • map:marker_exit

These events should come from authoritative runtime logic, not from client-local mesh overlap tests.

V1 event payload contract:

{
"mapId": "editor_smoke_01",
"markerId": "marker_01",
"markerType": "cylinder",
"playerId": 1,
"vehicleEntityId": 42,
"position": { "x": 0.0, "y": 2.0, "z": 0.0 },
"size": { "x": 4.0, "y": 4.0, "z": 4.0 }
}

Payload rules:

  • mapId is the authoritative loaded map id
  • markerId is the authored marker id
  • markerType is the baked authoritative marker type
  • playerId is the authoritative player id if known
  • vehicleEntityId is the authoritative controlled vehicle/entity id if known
  • position and size are the authoritative baked descriptor values

Delivery rules:

  • map:marker_hit and map:marker_enter are emitted on the same authoritative transition into the marker
  • map:marker_exit is emitted only when leaving a previously entered marker
  • event ordering is authoritative per entity/marker pair
  • event delivery should happen on the server-side runtime API first
  • client-facing presentation reactions should be driven from authoritative state, not local-only overlap guesses

Not Shipped Yet​

Not implemented as stable public API yet:

  • stable marker render instancing contract
  • stable marker authoring schema migration from current flat source records to the planned nested shape
  • final public Lua event binding surface
  • final query/helper methods for script-side marker inspection

Current Internal Status​

Current internal editor/runtime status:

  • internal marker authoring currently supports:
    • cylinder
    • arrow
    • corona
    • ring
  • internal save/load and bake for those marker subtypes exist
  • internal baked client presentation now also exists for those marker subtypes
  • internal authored marker style fields now exist:
    • opacity
    • emission
  • this is still internal editor/runtime behavior, not a stable external public contract yet

Current internal implementation note:

  • current editor source still uses flat records in primitives[]
  • current public plan uses the cleaner nested shape shown above
  • migration from the current internal source format to the planned public-facing shape is still pending
  • map_gameplay_plan.mdx