The problem
A target marker needs to answer a deceptively ordinary question: where is this world object on the player's screen, and should it be shown at all?
That means dealing with camera position and yaw, viewport projection, screen bounds, view distance, NPC height, bounding boxes, and the distinction between “visible to the camera” and “not occluded by geometry.”
The first implementation
CamHelper did not begin as a guessed universal camera library. It lived inside T4rg3t5 as application code for lock-on and target presentation.
That is the right place to discover the contract. A real consumer reveals which questions recur and which details are accidental. Commit 3b1f8a75 then migrated the camera helper into H3 and removed the T4-specific copy.
The result is an installed interface with a small vocabulary: isPositionBehindCamera, targetPosition, and objectIsOnscreen. The H3 CamHelper reference documents the boundary; the T4rg3t5 interface shows the production consumer.
The reduction
The reusable question is not “give every mod a camera framework.” It is:
Given this object, where should its marker go, or should it be absent?
That contract hides the repeated camera interpretation while keeping important semantics visible. objectIsOnscreen returns a normalized viewport position or nil; it does not pretend to perform occlusion testing. NPC height adjustment can use a previously captured offset when animation-driven bounds would cause jitter.
The provider is camHelper.lua. It is player-scoped because the camera and viewport are player concerns.
Extraction is not the end
Once the common boundary existed, real use continued to improve it:
8d8f2025rewrote the helper around its actual hot path;334d7493corrected a semantic inconsistency in onscreen checks;70c895ccremoved an unnecessary allocation;02a00845stopped repeatedly querying NPC bounding boxes by stabilizing the target offset.
That sequence matters. Application-specific implementation became a reusable interface, then measured hot-path work exposed both performance costs and semantic assumptions worth correcting.
What to steal
Do not start by guessing what will be reusable. Build one useful thing. Reuse it. Extract the part that proved stable.
Then keep optimizing behind the semantic boundary. A consumer should not have to know whether the answer came from a camera binding, a bounding-box query, a cached offset, or a Rubic0n-assisted math operation.
Read Engine Boundaries before optimizing a wrapper, and Measure First before declaring the wrapper expensive.
When not to use this
If one caller performs a small, one-off projection and does not share the camera semantics with anything else, it may not have an extraction boundary yet. Do not build a camera interface merely to avoid four lines of direct OpenMW code.