LOG IN
Docs
By Opus

Why Every Radar Beacon Went Dark

A five-minute code walkthrough of the D3D11 dazzle renderer early-return, the mystery it caused, and the single line that fixed it.

Video length target: ~5-7 minutes. Recording setup: VS Code / editor with file tree visible, split view. In-game recording optional for the cold open. Prereqs for viewer: Familiarity with C++ and real-time rendering basics.

Cold open (0:00 – 0:30)

[VISUAL: In-game footage, night map. Fly the camera past a GLA radar van, a Chinese Command Center antenna, a USA comms tower. Pause on the antenna tip. In the original Generals a red pinprick glows there. In our shot, nothing.]

Say:

"See that antenna? In the original 2003 game, it glows red. In our D3D11 port, for about six months, it didn't."

Say:

"Nobody touched the art. Nobody changed the W3D model. One line of C++ turned every beacon, every afterburner, every comms light off at the same time. Let's go find it."

Act 1 — What a dazzle actually is (0:30 – 1:30)

[VISUAL: Cut to editor. Open Core/Libraries/Source/WWVegas/WW3D2/dazzle.h. Scroll to DazzleRenderObjClass and DazzleTypeClass.]

Say:

"A 'dazzle' in the W3D engine is a billboard glow. Radar beacons, antenna lights, jet afterburners, muzzle flashes — all the same primitive. Camera-facing quad, additive blend, one texture."

[VISUAL: Open Core/Libraries/Source/WWVegas/WW3D2/RequiredAssets/Dazzle.INI. Scroll slowly through a [SUN] block.]

Say:

"Each type is an INI entry. HaloIntensity, FadeoutStart, FadeoutEnd, a color. DazzleRenderObjClass::Init_From_INI loads them into a static array at startup. An artist drops one into a .w3d file by type name, and the loader binds the instance to its type."

  • [SHOW:] The DazzleTypeClass::Calculate_Intensities signature in dazzle.h — point at camera_forward, dazzle_direction, and distance parameters.
  • [SHOW:] Highlight that the final intensity is pow(dot(camera, dazzle_dir), intensity_pow) — i.e. glow falls off sharply as you look at it from the side.

Say:

"The math matters later. Remember: intensity is a dot product, and it can fade to zero at glancing angles. That's not a bug, that's the look."

Act 2 — Following the dispatch (1:30 – 3:00)

[VISUAL: Open GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/D3D11Shims.cpp. Jump to line 6988. Highlight the CLASSID_DAZZLE branch.]

Say:

"Scene traversal time. The D3D11 shim walks every render object in the visible set and switches on class ID."

else if (classId == RenderObjClass::CLASSID_DAZZLE)
{
    robj->On_Frame_Update();
    RenderDazzleDX11(robj, rinfo);
}

Say:

"Mesh goes to the model renderer. Particle buffer has its own path. Dazzle goes here. Let's follow it."

[VISUAL: Ctrl-click or F12 on RenderDazzleDX11. Land at line 9951. Scroll down — don't look at the top yet. Go to line 9980 and below.]

Say:

"This is the meat of the function. Grab the camera transform. Grab the dazzle's world transform. Compute direction and distance from camera to dazzle."

const Matrix3D& camTM = rinfo.Camera.Get_Transform();
Vector3 cameraPos(camTM[0][3], camTM[1][3], camTM[2][3]);
Vector3 camForward(camTM[0][2], camTM[1][2], camTM[2][2]);

[VISUAL: Scroll to line 9994 — the Calculate_Intensities call.]

Say:

"Hand those vectors to the type class. It returns three scalars: dazzle intensity, size, halo intensity. Intensity under one percent? We skip the draw. That's the cheap gate."

[VISUAL: Scroll to line 10022 — the billboard construction.]

float rightX = camTM[0][0], rightY = camTM[1][0], rightZ = camTM[2][0];
float upX    = camTM[0][1], upY    = camTM[1][1], upZ    = camTM[2][1];
// ... normalize, scale by halfSize ...

Say:

"Camera right and up vectors, normalized, scaled. Offset the dazzle's world position by plus-or-minus right and up. Six verts, two triangles, additive blend, draw. Textbook camera-facing quad."

[VISUAL: Scroll to line 10054.]

renderer.SetAdditive3DState();
renderer.Draw3DNoIndex(vb, 6, tex, identity, {1,1,1,1});

Say:

"That is a complete, working dazzle renderer. It's all here. So why was every beacon dark?"

Act 3 — The twist (3:00 – 4:00)

[VISUAL: Scroll back up to line 9951. Pause on the comment block above the function body. Highlight line-by-line.]

Say:

"Here's the top of the same function."

static void RenderDazzleDX11(RenderObjClass* robj, RenderInfoClass& rinfo)
{
    // Dazzle render objects are camera-facing additive billboards used by
    // W3D for radar beacon lights, antenna glows, aircraft afterburners,
    // building spotlights, and some weapon muzzle flashes. They were
    // previously short-circuited in this port because the comment claimed
    // muzzle flashes were already handled by the model's MuzzleFX
    // sub-objects — but that only covers a subset of dazzles, leaving
    // every other beacon/glow dark.

[VISUAL: Highlight the original guard — the line that used to be here before the fix. Narrator mimes the deleted line with a strikethrough.]

Say:

"The original port author wrote an early return at the top of this function. Right above the working code. The reasoning in the comment is the interesting part."

Say:

"They noticed some dazzles were also muzzle flashes. They noticed the MuzzleFX sub-object system already draws muzzle flashes a different way. So they thought: if I draw dazzles here, muzzle flashes render twice. Disable the whole function, problem solved."

[VISUAL: Pan across a row of units in-game — tank, humvee, hovercraft — none of them are firing.]

Say:

"It is solved. For muzzle flashes. For the other 95 percent of dazzles — radar beacons, antenna lights, afterburners, comms towers, spotlights — they're just off. Forever. Silently."

Say:

"The gate was at the wrong level. They disabled an entire rendering feature because one specific use case had a conflict."

Act 4 — The fix (4:00 – 5:00)

[VISUAL: Show the git diff. A single deleted line. The early return, gone.]

Say:

"The fix is deleting one line. That's it. The implementation underneath was complete and correct the whole time."

[VISUAL: Launch the game. Same night map as the cold open. Fly to the same antenna. It's glowing red now. Pan to a parked MiG — afterburner cones. Pan to a radar van — antenna tip glows.]

Say:

"Every beacon back. Every antenna back. Afterburners back."

[VISUAL: Back to the comment block in the editor, highlight the last three lines.]

// Risk: any model that ships BOTH a dazzle AND a MuzzleFX sub-object
// will render the muzzle flash twice; if that turns out to be visible,
// gate this on the dazzle's owning render-obj being not a muzzle-flash
// sub-object instead of a global toggle.

Say:

"The original concern is real. I kept it in the comment. Shipping assets don't overlap, so nothing double-renders in practice. If a mod hits it, the fix is local — check the sub-object type, skip that one dazzle. Not turn the whole system off."

Outro (5:00 – 5:30)

[VISUAL: Split screen. Before on the left, after on the right. Same camera angle. Left is dead. Right is alive.]

Say:

"Lesson from this one: 'disabled with a comment' is a landmine. When you find a conflict, gate on the specific problem, not on the entire feature. One correct guard, five characters wide, beats shutting off a thousand lights."

Say:

"Next video we chase the same pattern in the particle system — a single flag that strobed every toxin puddle in the game. See you there."

B-roll suggestions

  • Nighttime flyover of USA01 campaign base showing comms tower lights blinking.
  • Formation of MiGs on afterburner over water, side-view, golden-hour.
  • Close-up on a Scud Launcher with siren dazzles active (if any ship with dazzles).
  • Editor scroll from the top of D3D11Shims.cpp to line 9938 as narrator talks through file structure.
  • Git blame of the deleted early-return line, hovering to show the original commit message.

Takeaway

The episode is worth five minutes because it is a pure case of "the code works, the feature is off." No logic bug, no race, no math error — just a gate placed one level too high. The fix is visual and immediate, and the meta-lesson (scope your disables to the specific conflict) generalizes to every codebase. Good opener for a series on port archaeology.