The One-Line Stub That Let Tanks Climb Cliffs
How a D3D11 shim that returned FALSE unconditionally removed every cliff from the pathfinder, and how it was caught.
Video length target: 5:00
Recording setup:
- Game at 1080p, windowed.
- VS2026 side-by-side with a map that has a dramatic cliff face. USA01 or any GLA map with ridged terrain works.
- Inspector overlay with pathfinder cell visualization on for the before/after shots.
Prereqs:
- Dev build.
- Two branches or two commits cued up: one with the stubbed
isCliffCell, one with the fix. - A save at the top of a map with a sheer cliff and a tank selected.
Cold open (0:00-0:30)
VISUAL: In-game. A Crusader tank. Player right-clicks the base of a near-vertical cliff. Tank wanders to the base, then casually drives straight up the wall like it's a gentle slope. Cut to a slow zoom on the tank climbing.
Say: "That's a tank. On a cliff. Driving up the cliff."
Say: "This should be impossible. The engine has an entire pathfinding classification for cliff cells. Units without a cliff locomotor are supposed to refuse to path through them."
Say: "But for weeks, after the D3D11 port, every map was flat to the pathfinder. No cliffs anywhere. One line of code did that."
VISUAL: Cut to black. Title card.
Act 1 — How cliffs are supposed to work (0:30-1:30)
VISUAL: Open Core/GameEngineDevice/Source/W3DDevice/GameClient/WorldHeightMap.cpp. Scroll to the cliff detection pass.
Say: "Cliffs start as a bit in the terrain logic heightmap. For every quad on the map, the engine samples the four corner heights. Gets the min and max. If the spread is bigger than 9.8 world units, that quad is a cliff."
VISUAL: Highlight PATHFIND_CLIFF_SLOPE_LIMIT_F at 9.8f. Highlight the maxZ - minZ > cliffRange comparison. Highlight setCliffState(xIndex, yIndex, isCliff).
Say: "9.8. Not a mesh normal. Not a slope angle. A raw height spread in world units, tuned for Generals infantry scale."
Say: "That bit lives on the WorldHeightMap owned by terrain logic. Anything that wants to know if a world position is a cliff has to walk back through that map."
VISUAL: Open GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameLogic/W3DTerrainLogic.h line 58. Then GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h line 244.
Say: "TheTerrainLogic->isCliffCell is the public entry point. A virtual on TerrainLogic, overridden on W3DTerrainLogic. Every system that cares about cliffs — pathfinder, physics, partition manager, the minefield generator, the command translator — calls it."
Act 2 — The classify pass (1:30-2:30)
VISUAL: Open GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp. Jump to line 4606, Pathfinder::classifyMapCell.
Say: "When a map loads, the pathfinder classifies every cell. For each cell it grabs the top-left corner, converts to world coordinates, and calls TheTerrainLogic->isCliffCell."
VISUAL: Highlight line 4622: if (TheTerrainLogic->isCliffCell(topLeftCorner.x, topLeftCorner.y)) setting type = PathfindCell::CELL_CLIFF.
Say: "If it comes back true, that cell gets CELL_CLIFF. Water corners override it. Object footprints get layered on later. That's the whole classification."
VISUAL: Scroll down to line 4724.
Say: "Then the expansion pass. This part matters. Cliff regions get intentionally thickened."
VISUAL: Highlight the three loops around lines 4724-4761. Each one reads CELL_CLIFF, marks neighbors as pinched, then converts pinched clear cells back into cliff cells, then adds a border of pinched cells around the result.
Say: "Mark neighbors as pinched. Convert pinched clear cells into cliff. Add a border of pinched around the whole thing. Three passes. The pathfinder wants cliffs to be a little fatter than the raw height data says."
Say: "Which means the whole system cascades off one call returning true."
Act 3 — The bug (2:30-3:30)
VISUAL: Open GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/D3D11Shims.cpp. Scroll to around line 1188. Show the git log on the left with the pre-fix commit checked out.
Say: "So here's where it went wrong. This file, D3D11Shims.cpp, holds all the leftovers from the D3D8 to D3D11 port. When the port ripped out BaseHeightMapRenderObjClass, every function that forwarded through it had to be re-homed."
VISUAL: git show or diff view of the old version. Show the stub: a W3DTerrainLogic::isCliffCell override that just return FALSE;.
Say: "Here. W3DTerrainLogic::isCliffCell. Three lines. Returns FALSE. No comment. No TODO. No assert."
VISUAL: Open GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameLogic/W3DTerrainLogic.cpp line 338. Show the original body: return TheTerrainRenderObject->isCliffCell(x,y);.
Say: "The original forwarded to TheTerrainRenderObject — that was the BaseHeightMapRenderObjClass instance. Port removed the instance. The forward stopped compiling. Someone dropped in a FALSE stub to unblock the build and moved on."
Say: "Nothing crashed. Nothing logged. The pathfinder dutifully called isCliffCell on every cell on every map. Got FALSE every time. Marked zero cells as CELL_CLIFF. The expansion pass had nothing to expand."
VISUAL: In-game again. Pathfinder visualization overlay. Whole map is green, no red cliff cells anywhere, even on near-vertical terrain.
Say: "From the pathfinder's point of view, the entire world was a parking lot."
Act 4 — The fix (3:30-4:30)
VISUAL: Check out the fix commit. Open D3D11Shims.cpp at line 1188.
Say: "The fix is a port of the original adapter. BaseHeightMapRenderObjClass::isCliffCell was always a thin wrapper around TheTerrainVisual->getLogicHeightMap(). So we do that directly."
VISUAL: Walk through the code.
- Line 1196: null-check
TheTerrainVisual. - Line 1199:
WorldHeightMap* hm = TheTerrainVisual->getLogicHeightMap(); - Lines 1203-1204: world to heightmap indices via
MAP_XY_FACTOR. - Lines 1205-1206: apply the border-size offset.
- Lines 1207-1210: clamp into valid extent.
- Line 1211:
return hm->getCliffState(iX, iY);.
Say: "That's it. Same arithmetic BaseHeightMapRenderObjClass::isCliffCell did in Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp at line 1240. Just skips the dead middleman."
VISUAL: Back in-game. Same save. Same tank. Same cliff. Right-click the top of the cliff.
Say: "Now watch."
VISUAL: Tank routes along the base, finds the long way around. Doesn't try the wall.
Say: "It refuses. As it should. The pathfinder marked that strip CELL_CLIFF, pinched the border around it, and the Crusader has no cliff locomotor."
VISUAL: Toggle pathfinder overlay. Red cliff cells hug the ridge.
Act 5 — The second-order damage (4:30-5:00)
VISUAL: Open GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp. Jump to line 1813.
Say: "Here's the part that scared me. PhysicsBehavior::testStunnedUnitForDestruction. When a unit is stunned — knocked back by an explosion, crushed, whatever — physics checks whether it landed somewhere it shouldn't be."
VISUAL: Highlight the check: if (TheTerrainLogic->isCliffCell(pos->x, pos->y) && !aiInt->hasLocomotorForSurface(LOCOMOTORSURFACE_CLIFF)) then obj->kill().
Say: "If a stunned unit without cliff locomotor ends up on a cliff cell, the engine kills it. That rule has been there since the original. It's how you stop tanks from getting wedged into impossible terrain by gameplay effects."
Say: "With the stub returning FALSE, that path was dead code. Stunned units that got blown onto sheer walls just sat there. No death, no cleanup. Engine-level behavior silently broken, no error message."
Say: "PartitionManager.cpp line 3822 uses the same call for ground-layer reasoning. GenerateMinefieldBehavior.cpp line 198 uses it to refuse to place mines on cliffs. CommandXlat.cpp line 2433 uses it when the cursor hovers over terrain. All of them were quietly wrong, and none of them complained."
Outro
VISUAL: Side-by-side split: before on the left, tank climbing. After on the right, tank refusing.
Say: "Lesson I keep re-learning on this port: a stub that returns FALSE is worse than a function that crashes."
Say: "A crash gets noticed on the first run. A silent FALSE goes out in a build, ships to testers, and looks like 'AI feels a bit off on some maps.' Nobody opens isCliffCell because nothing points at it."
Say: "If you're porting anything that used to forward through a removed type, don't leave the stub. Either rewrite it against the live data — the logic heightmap was sitting right there the whole time — or delete the override and let the base class assert. Silent wrong beats loud wrong every time, and not in a good way."
B-roll suggestions
- Pathfinder overlay animation toggling between the FALSE stub build and the fixed build. Shows the red cliff regions popping into existence.
- Slow pan across a map that has the 9.8 height spread in several places. Highlight which tiles do and don't qualify.
- Quick montage of all the call sites:
AIPathfind.cpp:4622,PhysicsUpdate.cpp:1813,PartitionManager.cpp:3822,GenerateMinefieldBehavior.cpp:198,CommandXlat.cpp:2433. One second each. Emphasize the blast radius of one bad return. - A minefield placement attempt on a ridge before and after, to show a gameplay system other than pathing that was broken.
- A
git blameon the original stub, pausing on the commit message, for honesty.
Takeaway
One return FALSE; in D3D11Shims.cpp disabled every cliff classification in the engine. The pathfinder stopped marking CELL_CLIFF, the cliff expansion pass had nothing to work on, the physics stunned-unit death check became dead code, and minefield placement, partition reasoning, and the command cursor all drifted off spec. No crash, no log, no assert. The fix was eleven lines that query TheTerrainVisual->getLogicHeightMap() directly, mirroring what BaseHeightMapRenderObjClass::isCliffCell used to do before the port removed it. When a forward target disappears, rewrite the body against the real data or fail loudly. Do not leave a quiet FALSE in its place.