World-space positions, arena-local coordinates, minimap coordinates, camera projection, and UI pixels are related but not interchangeable. A mod that draws positions or follows entities must declare which coordinate space it owns at every step.
Coordinate spaces
| Space | Owner | Typical use | Common mistake |
|---|---|---|---|
| World space | BigWorld space and live entities | Entity positions, matrices, collision and camera-relative transforms. | Using a stale entity matrix after teardown or replay seek. |
| Arena-local map space | Arena metadata and map geometry | Normalized tactical positions and map-bound calculations. | Assuming every map has the same origin, orientation, or playable bounds. |
| Minimap space | Battle UI component | Icons, range indicators, and tactical overlays. | Drawing in pixels before resolving map scale and visible bounds. |
| Screen space | Camera projection and UI layout | Labels, projected markers, and HUD elements. | Mixing physical pixels, CSS pixels, and interface scale. |
| Model or attachment space | Entity appearance and node providers | Points attached to a vehicle, gun, turret, or scene object. | Keeping a provider after its owning model is destroyed. |
Snapshot versus provider
A vector copied into local state is a snapshot. A matrix provider or entity property may be live and can change between reads. Use a snapshot for history, logs, and deduplication. Use a provider only while its owner is alive and the feature intentionally wants live movement.
def read_vehicle_position(vehicle):
if vehicle is None or not hasattr(vehicle, 'position'):
return None
pos = vehicle.position
# Copy plain values for storage; do not keep a destroyed entity object.
return (float(pos.x), float(pos.y), float(pos.z))
Arena bounds and normalization
Map overlays should derive their scale from the active arena metadata instead of hard-coding one map size. Keep the normalized position separate from the rendered pixel position so UI resizing or minimap scaling can be recalculated without changing the underlying battle data.
# Illustrative pattern; verify the exact map-bounds source in the target build.
def normalize_position(x, z, bounds):
min_x, min_z, max_x, max_z = bounds
width = max(max_x - min_x, 1.0)
height = max(max_z - min_z, 1.0)
return ((x - min_x) / width, (z - min_z) / height)
Projection to screen
Camera projection is valid only for the current camera, viewport, and frame. A battle transition, death camera, spectator mode, replay seek, or resolution change can invalidate cached projection data. Re-resolve the active camera and viewport at render time.
- Reject projections when the entity or camera is not ready.
- Hide or clamp markers whose projected depth is behind the camera.
- Apply interface scale consistently when feeding a Wulf or Gameface surface.
- Do not use projection output as a stable identifier.
Minimap and UI rendering
The minimap is a UI surface with its own bounds, scaling, rotation, and visibility rules. Keep tactical state arena-owned and let the renderer convert it into the current minimap rectangle each frame or each relevant resize.
| Data | Store as | Render as |
|---|---|---|
| Observed entity position | Arena ID + entity generation + normalized coordinate | Icon position inside the current minimap rectangle |
| Range or radius | World distance using map units | Scaled radius in minimap pixels |
| Direction | World yaw or normalized vector | Rotated marker after map-orientation rules are applied |
| Last known state | Timestamped snapshot | Faded or expired UI element |
Owned minimap entries
When extending the native minimap, prefer adding entries that your feature owns instead of replacing native entries that the game already updates. Store entry IDs per live minimap plugin instance, hide entries when the plugin enters observer or postmortem states, and delete them when the plugin stops.
- Use the current minimap bounding box or owner API to initialize arena size for range visuals.
- For range rings, keep world distance as the source value and let the minimap entry handle scaling to the current map surface.
- For direction or sweep visuals, combine an attached-vehicle translation provider with an owned rotation provider rather than caching one stale matrix.
- Cancel every update callback tied to an entry before deleting or hiding that entry.
Static collision queries are measurements, not physics
The common current-client form
BigWorld.wg_collideSegment(spaceID, start, end, skipFlags) tests a finite directed
segment against static scene geometry. A miss returns None; current callers read
closestPoint, normal, and matKind from a hit.
- Direction matters: reversing the endpoints can select a different surface.
- A long vertical probe may hit a bridge, roof, canopy, or other stacked surface before the intended terrain.
skipFlagsexcludes triangle categories. It does not request collision with only those categories.- The query reports contact; it does not create a collider, apply gravity, prevent penetration, or resolve motion.
def probe_ground(space_id, origin, distance):
start = origin + Math.Vector3(0.0, distance, 0.0)
end = origin - Math.Vector3(0.0, distance, 0.0)
hit = BigWorld.wg_collideSegment(
space_id, start, end,
CollisionFlags.TRIANGLE_PROJECTILENOCOLLIDE)
if hit is None:
return None
return Math.Vector3(hit.closestPoint), Math.Vector3(hit.normal)
Related helpers query static geometry, dynamic collision, sphere sweeps, or water, but they still do not supply continuous collision response or a tracked-vehicle solver. Verify native signatures and optional arguments from current call sites.
Whole-vehicle grounding is a separate mechanism
Several point probes can estimate a static pose, but they do not become suspension or rigid-body
physics. The current battle appearance configures WGVehicleFilter from descriptor
width, speed, ground-normal limits, and carrying triangles, then exposes root, body, ground-
placing, turret, and stabilized matrices. Those matrices have different consumers and are not
interchangeable.
A simulated vehicle receives its descriptor-configured filter only after its asynchronous
appearance-loaded boundary. Any experimental placement must wait for that event, verify entity
generation and space, and compare the resulting matrices before choosing a saved transform.
WGTankPhysics and WGWheeledPhysics require a larger live-vehicle owner
contract and are not safe drop-in solvers for arbitrary lobby models.
Replay and reconnect
Replay rewind and reconnect can recreate entities without repeating every normal live-battle event. Clear cached entity objects and providers, then rebuild snapshots from the current visible state. If the renderer uses callbacks, bind them to a generation token and cancel or ignore callbacks from previous arena generations.
Verification checklist
- Find the source of the map bounds or coordinate conversion in the current client.
- Log source coordinates, normalized coordinates, render rectangle, interface scale, and final pixel coordinates together.
- Test map loading, death/spectator mode, minimap resizing, interface-scale changes, replay seeking, and return to garage.
- Ensure cleanup removes owned icons, providers, callbacks, and cached entity references.
Minimap local plane and grid contracts
The inspected minimap local helpers use a 210×210 reference plane, scale by arena bounding-box extent and invert local Y into world Z. One helper returns a center-relative offset; the matrix helper also translates by the bounds center. Omitting that translation shifts asymmetric maps. This plane is not a physical-pixel contract for Flash mouse input.
The classic ten-division grid uses column-major IDs with rows toward decreasing Z. Labels skip I and the final column uses zero, so corner labels run A1 through K0. Cell zero is valid. Position-to-cell clamps just inside bounds, while its controller can reject out-of-bounds input before calling it; a clamped helper result does not prove the source point was inside the map.
Cell-to-position returns a corner rather than center and is not a general exact roundtrip at internal boundaries. Also distinguish valid integer zero from False and None sentinels before labelling a cell. These Python helper contracts were checked with controlled coordinates, not native mouse hit testing.
A ground-query miss can fall back to Y=0, which is not evidence of terrain height. Keep coordinate conversion, bounds admission, collision outcome and UI-target selection as separate stages.