This is an attempt to document and explain some of the geometric concepts, algebraic models, and implementation procedures I used for data-driven entity positioning, multi-threat perception, ballistic targeting, and squad cohesion in my tank game.
To keep things approachable, I’m focusing here on the Geometric Foundations & Tactical Intuition, the algebra and 2D geometry (distances, angles, triangles, and lines of sight) to explain the why, the tactical logic, and my thought processes behind the mechanics without getting bogged down in dense matrix equations.
I am far from being a mathematician but designing autonomous entities in a 2D environment has been a great motivator to learn some practical math and geometry.
Geometric Foundations & Tactical Intuition
1. Sensory Perception: Vision Cones, Circles, and Obstacles
First of all, I wanted to implement a semi-realistic field-of-view (FOV) system for enemy tanks, but quickly realized that a single vision cone was insufficient for a vehicle with an independently rotating turret. Instead, I split spatial awareness between an abstraction of what the hull sees, where the gun is aimed, and what the crew can hear through the metal plating.
A. The Optical Vision Cone (Geometry of Angles & Distances)
To prevent the computer from cheating, tank vision is non-omniscient. Rather than a global radar map, vision is modeled as a 2D wedge (a sector of a circle):
- The turret gun sight and aim direction covers a total field of view, or angle spread, of ( to the left and to the right of where the cannon barrel points).
- An enemy tank cannot spot targets across the entire map as optical targeting cuts off at an arbitrary maximum distance ().
- Using Euclidean distance between the turret and target :
If and the target’s relative bearing lies within that arc, the optical sight acquires the target.
Note: Yes yes I know square root distance is expensive, but I use Euclidean distance (rl.Vector2Distance) because I require normalization and direction, projectile flight time prediction, linear scoring and clamping for other systems. In my testing, using the cheaper squared distance (rl.Vector2DistanceSqr) saved ~0.3µs or (3/10,000 of a ms) using Odin and Raylib, and this is an acceptable trade-off.
B. Close-Proximity Blindspots & Engine Hearing
Real tanks have severe blindspots directly behind their rear engine decks. If player tank sneaks up behind the turret outside its cone, the gunner won’t see them.
However, tank engines are loud, tracks rumble across dirt, and commanders have small cupola vision blocks. To simulate this without complex acoustic physics, I abstracted auditory detection with a simple circular area:
- Hearing Radius () so if an enemy vehicle closes within of the chassis center, the crew immediately detects them regardless of where the turret is facing.
Together, being detected is modeled as “can it see you?” or “can it hear you?”. But we are not quite done yet, because tank should not be able to see through solid obstacles.
C. Line-of-Sight (LOS) Obstacle Checks
Even if a player is sitting inside the optical wedge and within range, the tank cannot see through solid obstacles such as buildings or trees.
Before confirming detection, the game draws a straight line segment connecting the turret center to the target center. If this line intersects the edges of any obstacle polygon on the map, line of sight is broken.
I use linear interpolation to do this where , here as the percentage of the distance along the line, is any value within 0.0 and 1.0:
How I check whether obstacles block this line segment comes down to two geometric tests:
- With Tree Trunks (Solid Circles), we find the closest point along the segment to the tree trunk’s center by projecting the tree position onto the line and clamping to . If the distance from the tree center to that closest point is less than or equal to the trunk’s radius, the trunk blocks line of sight.
- With Buildings & Other Tank Hulls (Rotated Rectangles), designed as oriented bounding boxes (OBBs), we rotate the line segment’s start and end points into the obstacle’s local, unrotated coordinate frame (instead of dealing with messy angled edge-intersection math in world space). From there, it’s a classic 2D “slab test” where we calculate where the line enters and exits the box’s width and height bounds. If the line passes through the box while is between and , line of sight is broken.
- Not to get too too complicated but this essentially tests the OBB against the Separating Axis Theorem (SAT) in 2D.
- Tank Traps are low-profile iron obstacles, they stop tank treads, but they don’t block visual line of sight or gun fire, so the check simply passes right through them.
2. Multi-Threat Encirclement & Escape Arc Detection
Now that computer can detect threats via dual-FOV and close-proximity hearing, I wanted to implement a system that allows algorithm to detect encirclement or crossfire situations and compute an escape route. This prevents computer tank from freezing in place during an ambush and gives it a chance to break out.
A. The Flaw of Adding Directional Arrows
Initially, I implemented a naive threat model: calculate a “threat pull” by adding together arrows pointing (using directional vectors) toward every visible enemy.
For those with more experience than i have, the issue with this would have been apparent. When three, or really any number of, tanks surround computer tank symmetrically (say, spaced evenly at angles around it), what happens when you add those three equal opposing forces together?
They cancel each other out to zero:
The DIRECTIONAL vectors (it was in the name all along!!) cancelled out completely! Computer’s math concluded that zero net threat existed, leaving tank unit paralyzed while my tanks peppered it from all sides.
B. The 360° Protractor: Finding the Biggest Gap
Instead of adding arrows together, I mapped the bearing of every threat onto a standard protractor centered on the tank:
[0°][30°][60°][90°][120°][150°][180°][210°][240°][270°][300°][330°]
Every nearby enemy within gets assigned a clock angle (from to ) relative to our tank.
Once we list the threats in order around the circle, we measure the angular gap (the “pizza slice”) between each neighboring pair of enemies. Because a circle always adds up to , all gaps combined equal :
For example, if two enemies are at and :
- Gap between Threat 0 and Threat 1: (where the enemy fire is concentrated).
- Remaining gap around the back: (wide open space).
The Threat Span is simply the total arc covered by incoming fire:
C. Detecting Crossfires & Finding the Exit Heading
If the incoming fire spans more than around the tank, algorithm flags a Crossfire Alert.
To escape, the tank should want to drive straight through the middle of the widest open gap. I divide the angle in half (bisecting the angle) to find the escape heading:
In our example with Threat 0 () and Threat 1 (), the open gap starts at Threat 1 () and spans around the back. Bisecting this arc:
Notice how this creates equal clearance on both sides: (Due East) is exactly away from Threat 0 () and away from Threat 1 (), directing the tank straight out of the crossfire.
D. Checking Ahead for Walls
Before committing full throttle down that escape heading, algorithm tests whether that path actually leads anywhere. It casts probe points ahead along the escape heading and slightly to the left and right ().
- If the primary gap is blocked by a solid wall, algorithm tests the second-largest gap.
- It steps through smaller gaps in descending order until it finds an open exit corridor.
- If every single exit is walled off, the tank activates the Hedgehog Defense (see below).
3. Multi-Threat Cover Search & Scoring
Once an open escape corridor is found, the enemy tank doesn’t just drive blindly in a straight line, it wants to find a spot that puts solid cover between itself and the attackers while traveling the shortest distance possible.
A. Sampling Radar Rings
The computer samples candidate hiding spots at set distances along the escape path, like expanding ripples on water:
- Distances tested: , , , and .
- At each distance, it tests points directly ahead, slightly to the flanks, and wide to the sides.
To calculate where those candidate spots land in world space, some trigonometry is required.
We take our tank’s position, pick one of the four radii (, , , or ), and fan out across seven relative angle offsets:
- Straight down the escape heading ()
- Gentle flank angles ( or )
- Wide flank angles ( or )
- Perpendicular beam angles ( or )
Then we compute the coordinate for each candidate spot:
Testing gives us candidate hiding spots evaluated in a fraction of a millisecond. If a spot lands inside an obstacle wall, we toss it out; if it’s open, we run it through the cover scoring formula.
B. The Cover Score Formula
For each candidate spot, algorithm runs a simple point system:
- +1000 Points for every enemy whose line of sight is broken by a wall from that spot.
- -0.5 Points per Pixel of driving distance from the tank’s current position (to penalize driving across the map if nearby cover exists).
The tank picks the spot with the highest score. If two spots block all enemies, the closer one wins.
C. Dead-End Fallback: The Hedgehog Defense (Back-to-the-Wall)
What happens if the tank is cornered in a cul-de-sac and no position can block enemy line of sight ()?
Rather than doing donuts or spinning in confusion, algorithm tells the tank NPCs to engage in the Hedgehog Defense:
- Algorithm locates the closest obstacle directly behind its hull.
- Tank NPC backs its vulnerable engine deck flush against the wall (
chosen_gear = .Reverse), physically shielding its rear. - Then tank NPC angles its thicker frontal hull armor at roughly (the angle of the glacis plate) toward the nearest attacker to maximize deflection chance while returning fire.
4. Ballistic Prediction & Weakpoint Targeting
To make enemy tanks feel dangerous without letting them instantly snipe the player across the map, the firing solution uses a kinematic intercept model.
A. Physics 101: Speed, Time, and Distance
This section was particularly fun to implement because I remember sleeping through this exact chapter in high school physics class, yet here we are! lol At any rate, in game, shells travel at a constant muzzle speed ( for the test Panzer IV).
I use basic physics calc:
-
The gunner measures the straight-line Euclidean distance directly from the turret pivot to the target’s center :
(This is another reason I stick with standard rl.Vector2Distance here. I needed the actual pixel distance to divide by bullet speed and find the flight duration!)
Dividing distance by gives the flight time: for a target away, the shell takes exactly to arrive.
-
Then gunner attempts to predict the intercept point. If the player is driving straight at , where will they be in ?
Algorithm then aims at that future spot rather than where the player is currently sitting.
B. Difficulty Tuning (Ballistic Lead Scalar)
To support different difficulty levels, we scale the calculated lead distance:
- Recruit ( lead): Aims directly at the player’s current location with zero lead. (Easy to dodge by just driving sideways!)
- Veteran ( lead): Leads partially, enough to punish overwhelmed commanders but still dodgeable with lateral movement.
- Ace ( lead): Calculates the full intercept position, some micro management required to dodge.
C. Weakpoint Targeting (Flank Penetration)
Because I previously implemented a soft z-layer (initially purely for z-layering of visual elements), the computer can target the rear engine compartment of the player’s tank even if the front segment is pointed toward the enemy. I don’t think this is too unfair being the computer tank lacks the ingenuity of a human player.
When attacking from an offset angle, especially when executing a flanking maneuver, the computer shifts its aimpoint from the target’s center of mass toward the rear engine compartment (which carries a damage multiplier) 😜:
5. Sloped Armor & Hull Angling Mechanics
On top of their bias to aim for the player’s weakpoints, computer-driven tanks also angle their hulls to maximize effective armor thickness and deflection probability.
A. Right Triangles & Effective Armor
Why is sloped armor so effective? It comes down to basic right-triangle trigonometry.
Imagine a flat armor plate with thickness . If an incoming shell strikes perpendicularly (), it punches straight through of steel.
Now tilt that plate at an angle . The shell no longer travels straight through; it must punch along the hypotenuse of the right triangle:
In any right triangle:
- At obliquity: effective.
- At obliquity: ( protection!).
- At obliquity: (double the armor!).
When the impact angle exceeds , shells glance off the hull, creating either relief or dread depending on which side your own but regardless, dealing virtually zero damage.
B. The 24° Hull Angling Trick
To maximize deflections on its thick frontal glacis plate, algorithm angles its hull off-center relative to incoming fire. This sweet spot slopes the front armor enough to trigger ricochets while preventing the thin side tracks from being exposed to direct fire.
6. Squad Formations & Battlefield Traffic
After studying Boids or Bird-oid flocking algorithms, I implemented a 3-tank formation system that allows a squad leader to maintain cohesion while maneuvering through the battlefield (I later changed this to allow for sections of even just one tank).
A. Relative Formation Offsets
Squad mates position themselves relative to the commander’s lead tank using simple coordinate offsets (forward/backward and left/right):
- Wedge Formation (Triangle):
- Leader at the apex.
- Wingman 1: behind, to the left.
- Wingman 2: behind, to the right.
- Line Abreast (Assault Row):
- Tanks drive shoulder-to-shoulder, spaced apart to the left and right.
- Column Formation (Roads & Narrow Passes):
- Single-file march: second tank follows ~ behind, third tank follows ~ behind.
During development, this was all relative to the test tank’s dimensions so when movement was necessary, members would not collide with each other.
B. Leashes & Tactical Exceptions
- If a wingman lags more than away from its slot, the commander throttles back to a crawling speed of , mimicing a leash, until the squad reforms.
- Tanks actively executing a flank or evading encirclement are given full tactical autonomy, tank ignore the leash entirely and do not slow the commander down.
C. Traffic Rules & Chokepoint Queuing
To prevent tanks from piling into each other or jamming roads:
- Tank destinations enforce a strict minimum clearance of ( tank length).
- Rather than erratic swerving that throws tanks into walls, tanks follow longitudinal speed controls. If closing within tank lengths of a teammate ahead, the trailing tank gently throttles down to matching crawl speed.
- When two tanks converge on a narrow opening in a bocage hedgerow, the tank that is closer gets the right-of-way. The trailing tank waits outside the throat until the opening clears, forming a smooth single-file passage.
7. Hierarchical Two-Section Platoon Command (The Hammer & Anvil)
As fun as 3-tank formations were, things got chaotic once I scaled platoon battles up to 8 tanks. Trying to cram 8 tanks into a single rigid wedge or column created massive traffic jams, and having all 8 enemy tanks focus-fire on a single player tank felt both silly and absurd.
To solve this, I split the platoon into two coordinated tactical elements: Section Alpha (Base-of-Fire / Anvil) and Section Bravo (Maneuver Element / Hammer).
A. Procedural Section Slots (1 to 4 Tanks Each)
Instead of hardcoding a fixed squad size, each section procedurally lays out slots for however many tanks it currently has ( to tanks):
- Wedge / Diamond formation has Slot 0 at the apex, Wingmen 1 & 2 stepped back and to the flanks, and Slot 3 tucking behind as a diamond rear guard.
- Line Abreast formation has tanks spread out shoulder-to-shoulder with lateral spacing for assault sweeps.
- Column formation has tanks rolling in a straight line with longitudinal spacing between hulls for road marches and narrow defiles.
B. The Hammer & Anvil: Orbital Crescent Flanking
When unengaged, Section Bravo acts as flank guard trailing Section Alpha in echelon. But once Section Alpha engages the enemy and gets pinned down, Section Bravo switches to an aggressive Flanking Hook.
Instead of all Bravo tanks rushing toward the same single spot behind the player (which just causes bumper-car pileups), Section Bravo fans out along an orbital crescent arc:
- Algorithm computes an anchor circle around the player platoon’s center at a standoff distance of .
- A 2D cross product automatically detects which side Section Bravo is already closer to, ensuring they swing around that flank rather than driving across Alpha’s line of fire.
- Bravo wingmen fan out along the arc separated by roughly (). Along an circle, this creates about of breathing room between tanks ( tank lengths).
The result is a smooth, wide crossfire crescent that envelops the player without tanks bumping fenders or jamming roads. Although at the map edges, the orbital arc is truncated and the tanks pin themselves against the wall, they still engage player in a very elegant arcing pattern.
C. Combat Pinning & Holding Cover
In real combat, tanks trading fire from behind a stone wall don’t suddenly expose their side armor to drive across an open field just because the commander drove forward.
I added a Combat Pinning rule: if a tank has direct line-of-sight to an enemy, is within effective gun range (), and its section is engaged, it enters a pinned stance (hold_position = true). It holds its hull-down ground and trades fire rather than abandoning good cover to chase a shifting formation slot.
D. Anti-Overkill (Target Saturation Penalty)
If 8 enemy tanks all acquire the lead player tank, you get vaporized in half a second while the rest of your squad sits completely unpressured.
To create balanced, tactical engagements, target selection includes an anti-saturation penalty:
- Every time a tank locks onto a target, it reports it to the squad brain.
- If or more enemy guns are already aimed at the same player tank, subsequent tanks apply a heavy penalty to that target in their scoring formula.
- Those tanks will deliberately choose secondary targets (wingmen or flanking player tanks) if visible.
This naturally distributes incoming fire across the player’s platoon, forcing you to manage multiple duels across the battlefield instead of surviving a single focused firing squad.