Appearance
aowlspt_regproj.h
Source: abi/aowlspt_regproj.h — 323 lines, 20 file-scope functions.
What this header owns
Reproduced verbatim from the header's own banner comment — these notes are frequently the only written record of why the subsystem is shaped the way it is.
text
aowlspt_regproj.h -- THE HOST'S PROJECTOR FOR THE SHARED REGION.
`abi/aowlspt_region.h` has always had a projector SEAT and nothing has ever
sat in it. `aowl_region_project` / `_project_ex` refuse with
AOWL_REGION_REFUSE_NOPROJ, every consumer falls back, and `mods/maps` has
been honestly reporting that for as long as it has existed:
projector INCONCLUSIVE -- N indicator(s) drawn as BEARING RINGS; no
projector is installed (nothing calls aowl_region_set_projector), so
they are a bearing and not a projected screen position.
This file is the thing that calls it. It is the difference between a radar
(a bearing on a ring) and a map (a contact at the pixel it really occupies).
------------------------------------------------------------------------
WHAT IT IS MADE OF, AND WHY NOTHING HERE IS NEW
------------------------------------------------------------------------
Not one RVA, prologue signature, calling convention or matrix convention is
introduced here. Every one of them is ALREADY MEASURED, disassembled and
documented in `abi/aowlspt_admin.h`, which this file includes and uses:
UnityEngine.Camera::get_main @ 0x5260400 (static)
UnityEngine.Camera::get_worldToCameraMatrix @ 0x525F2A0 (sret)
UnityEngine.Camera::get_projectionMatrix @ 0x525F380 (sret)
EFT.CameraControl.CameraManager::get_Instance @ 0x1263BD0 (static)
CameraManager.<Camera>k__BackingField @ 0x70 (field read)
with 16-byte prologue signatures for each (AOWL_ADM_SIG_*), the recorded
measurement that `Camera.main` is NULL for a whole Tarkov raid because the
FPS camera carries no "MainCamera" tag, and the CameraManager fallback that
`mods/fov` proves. Duplicating any of that here would give the project two
answers to one question, which is exactly what `aowlspt_region.h`'s own
header text forbids. So: ONE answer, in `aowlspt_admin.h`, USED from here.
Because everything in `aowlspt_admin.h` is `static`, "using it" means being
compiled into the SAME translation unit -- `host/.../region.nim`, the one TU
in the host that defines AOWL_REGION_HOST. That is also what makes reading
`aowl_adm_cam_vp` directly legal below, and it is why this header `#error`s
if either of its two prerequisites is missing rather than silently compiling
against a second copy of the state.
------------------------------------------------------------------------
THE SPLIT ACROSS THE FRAME -- and why the draw path stays cheap
------------------------------------------------------------------------
`mods/maps` measures its whole draw pass at 6us against a 400us budget, over
216,649/216,649 validated position reads. A projector that called into Unity
once per contact would put an il2cpp call inside that loop and destroy it.
So the work is split exactly the way `aowlspt_admin.h` already splits it:
ONCE PER FRAME, UNITY THREAD -- `aowl_regproj_tick()`, driven from
`regionFired` immediately before dispatch. It calls
`aowl_admin_cam_sample()`: get the Camera, pull worldToCamera and
projection through their sret getters, multiply into VP, run the
finished-state self-test, publish 16 floats. Three il2cpp calls a frame,
total, no matter how many contacts there are.
PER CONTACT -- `aowl_regproj_project()`, the installed AowlRegionProjFn.
Pure arithmetic over those 16 floats: 12 multiplies, one divide, no
il2cpp call, no allocation, no pointer hop into the game. It cannot
fault on game memory because it never touches any.
------------------------------------------------------------------------
THE GUARD (CLAUDE.md 5, rule 3) -- ONE, AND IT IS NOT HERE
------------------------------------------------------------------------
`aowl_p_p_seh` has a single thread-local jmp_buf and clears `aowl_seh_active`
on the INNER return, so a nested guard does not add protection, it silently
removes the outer one. Therefore:
* `aowl_regproj_tick()` DOES open one, around the camera sample, because
it calls into Unity and `regionFired` deliberately holds no guard at that
point. It is entered and exited before `aowl_region_frame` runs -- which
matters, because the dispatcher REFUSES outright if it finds a guard
already active on the thread.
* `aowl_regproj_project()` opens NONE, and that is deliberate rather than
an omission. It is called from inside a participant callback, which the
dispatcher has already wrapped in THE guard. A guard here would nest
inside that one and disarm it for the rest of the callback -- turning a
safety addition into a safety deletion, for a function whose entire body
is arithmetic on our own static float array.
------------------------------------------------------------------------
THE BEHIND-CAMERA DECISION
------------------------------------------------------------------------
clip = VP * (x,y,z,1). For Unity's perspective projection, clip.w is the
camera-space forward distance in metres: positive in front, negative behind.
That is the ONLY thing the sign test uses, and it is what is reported through
the `depth` out-parameter -- so a test can assert on a measured number rather
than on a flag this code could have set by accident.
A point BEHIND still gets pixels written, because the perspective divide by a
negative w flips both axes and the result is a usable BEARING once the caller
is told. That is precisely the contract `aowl_region_project_ex` documents
and precisely what `mm_indicator(..., behind, ...)` in `mods/maps` already
implements. `aowl_region_project` (the five-argument form) keeps returning 0
for those, so an un-updated caller silently becomes correct rather than
drawing a confident mirror image.
FLAG-GATED, DEFAULT OFF: `regionProjector` in `aowlspt-host.json`. With the
flag off nothing is sampled and nothing is installed, projection keeps
refusing with REFUSE_NOPROJ, and every consumer keeps its bearing-ring
fallback -- which is why turning this on degrades rather than blanks if it
goes wrong.
SELF-DISABLE. Two independent limits, because they have different fixes:
`aowl_admin_cam_sample` already self-disables after AOWL_ADM_CAM_MAXFAIL
(240) consecutive refusals, and the tick below UNINSTALLS the projector after
AOWL_REGPROJ_MAXFAULT (8) sample calls that actually FAULTED through the
guard. Uninstalling is the honest failure: consumers go straight back to
bearing rings and say INCONCLUSIVE, instead of drawing marks from a matrix
that stopped being refreshed.Constants
AOWLSPT_REGPROJ_HAOWL_REGPROJ_MAXFAULT
Functions
| Signature | Line |
|---|---|
void aowl_regproj_bump(int64_t* c) | 148 |
int32_t aowl_regproj_project(float wx, float wy, float wz, float* sx, float* sy, int32_t* flags, float* depth) | 159 |
void aowl_regproj_sample_body(void* unused) | 255 |
void aowl_regproj_tick(void) | 268 |
int32_t aowl_regproj_is_installed(void) | 303 |
int32_t aowl_regproj_fault_count(void) | 304 |
int32_t aowl_regproj_is_disabled(void) | 305 |
int64_t aowl_regproj_tick_count(void) | 306 |
int64_t aowl_regproj_sample_count(void) | 307 |
int64_t aowl_regproj_call_count(void) | 308 |
int64_t aowl_regproj_infront_count(void) | 309 |
int64_t aowl_regproj_behind_count(void) | 310 |
int64_t aowl_regproj_nocam_count(void) | 311 |
int64_t aowl_regproj_noscreen_count(void) | 312 |
int64_t aowl_regproj_nonfinite_count(void) | 313 |
int32_t aowl_regproj_cam_bind(void) | 316 |
int32_t aowl_regproj_cam_route(void) | 317 |
int32_t aowl_regproj_cam_gen(void) | 318 |
int32_t aowl_regproj_cam_stale(void) | 319 |
int32_t aowl_regproj_cam_selftest_fails(void) | 320 |

