Generated banner for Implement Fog Of War
Generated logo for Implement Fog Of War

Implement Fog Of War

Meng ToCommunityCreated by Meng To

  • Skill
  • Tool integration
  • Software development
  • MIT
  • No issues found

From the author

Implement, tune, debug, or validate soft wall-aware fog of war and gameplay perception in Three.js action games. Use for orthographic or isometric visibility masks, obstacle-aware line of sight, player and enemy vision ranges, hidden-enemy targeting rules, fog shader artifacts such as spokes or seams, mobile ray budgets, lifecycle and menu-state integration, and deterministic fog-of-war tests.
— the description by Meng To

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured approach to building a fog of war system in Three.js. It emphasizes a clear separation between the simulation layer, which handles deterministic perception and obstacle intersection, and the presentation layer, which uses a shader-based overlay to render radial and wall-aware fog. The system relies on a fixed-size lookup texture to store angular visibility data, ensuring that gameplay logic remains decoupled from visual effects.

What it does

  • Defines a deterministic perception model for player and enemy vision ranges.
  • Calculates obstacle-aware line of sight using vertical and horizontal footprint intersections.
  • Generates a 1D lookup texture to store angular visibility distances.
  • Renders a soft fog overlay using a single full-screen shader material.
  • Integrates visibility state with gameplay mechanics like targeting and enemy rendering.
  • Provides guidelines for managing fog lifecycle, telemetry, and state transitions.

When to use it

  • When implementing visibility systems for orthographic or isometric action games in Three.js.
  • When you need to synchronize enemy perception with visual fog of war.
  • When building a system that requires consistent visibility rules across different gameplay states.
  • When you need to optimize visibility calculations for both desktop and mobile environments.

Advantages

  • Maintains a single source of truth for both gameplay logic and visual presentation.
  • Prevents visual artifacts like spokes or seams through angular smoothing.
  • Ensures consistent behavior by separating simulation presence from rendering visibility.
  • Uses a bounded architecture that avoids unnecessary render targets or complex scene graph modifications.

Usage examples

“How do I define a new sight blocker for the fog of war system?”
You define a SightObstacle object with an ID, XZ coordinates, radius, and vertical span, then add it to the active obstacle collection used by the perception system.
“How should I handle enemy visibility in the fog?”
Use the perception result to toggle the enemy's presentation layer visibility while keeping the actor root active for simulation purposes.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/mengto/implement-fog-of-war/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip implement-fog-of-war.zip -d .agents/skills

Documentation by Meng To

Implement Fog of War

Build fog of war as a shared perception system with a restrained presentation layer.

Preserve the architecture

Keep these responsibilities separate:

  1. Let deterministic CPU perception own ranges, obstacle intersections, and gameplay visibility.

  2. Encode the player's angular visible distances into one fixed-size lookup texture.

  3. Let one full-screen shader turn that lookup into soft radial and wall-aware fog.

  4. Let gameplay consume perception results directly; never infer combat truth from fog pixels.

Read mechanics.md before implementing or changing the algorithm. It records the proven obstacle model, shader composition, calibrated values, state rules, enemy behavior, lighting constraints, and telemetry.

Inspect before editing

  • Locate the authoritative player position, camera, obstacles, scene lifecycle, frame loop, targeting, enemy perception, and render layers.

  • Confirm the gameplay ground model. The proven corner-unprojection method assumes a locally horizontal plane; use authoritative surface or terrain reconstruction when elevation varies across the visible area.

  • Find existing scene fog, post-processing, renderer creation, review modes, menus, captures, transitions, and disposal paths.

  • Read the current tests and recent fog/perception commits before changing constants or ownership.

  • Confirm whether obstacle state changes at runtime. Reuse the same active obstacle collection used by navigation or collision when its geometry is suitable for sight.

  • Check the working tree early and preserve unrelated changes.

Implement in dependency order

1. Define perception truth
  • Represent each sight blocker with a stable footprint, vertical span, and active state.

  • Intersect a 3D sight segment or ray with both the horizontal footprint and vertical span.

  • Ignore inactive blockers and cover below the eye-to-target sight line.

  • Expose:

    • point-to-point perception for enemies, targeting, and attacks;

    • a deterministic angular distance fill for the fog lookup.

  • Use explicit player and enemy ranges. Allow them to differ when the encounter design needs enemies to acquire beyond the player's reveal edge.

2. Build the visibility lookup
  • Allocate the distance array, byte pixels, and data texture once.

  • Cast a fixed number of evenly spaced horizontal rays around the player's eye point.

  • Normalize hit distances by the player vision radius and quantize them into a one-row red-channel texture.

  • Use linear filtering, horizontal repeat wrapping, no mipmaps, and a bounded mobile/desktop ray budget.

  • Refresh after meaningful player motion or a short maximum interval. Do not allocate in the frame loop.

3. Render one soft overlay
  • Use one clip-space plane and one transparent shader material.

  • Reconstruct each fragment's ground-plane position from the four camera-corner intersections.

  • Combine:

    • a clear inner radius and soft outer radial falloff;

    • a feathered wall boundary sampled from the angular lookup.

  • Smooth obstacle visibility across neighboring angles. Weight wall fog below the outer radial fog so blockers read as atmosphere, not opaque wedges.

  • Combine radial and obstacle fog with max, then cap final opacity.

  • Disable depth test, depth write, and tone mapping; render after the world.

  • Do not introduce a second renderer, render target, composer, or per-ray meshes.

4. Integrate gameplay
  • Keep simulation presence separate from presentation visibility.

  • Hide unrevealed enemy renderables through layers or a presentation-only mechanism; do not toggle the actor root if root visibility also controls lifecycle or simulation.

  • Exclude unrevealed enemies from target lock, aim selection, nearest-target search, HUD counts, and player hit eligibility.

  • Hide telegraphs that would leak an unseen enemy.

  • Let enemy acquisition and attacks use their own range plus the same line-of-sight truth.

  • Clear an active target lock as soon as perception invalidates it.

  • Cache perception within a simulation phase and refresh after movement when downstream systems need current positions.

5. Integrate state and lifecycle
  • Enable fog only during active gameplay.

  • Disable it for menus, inventory, review/capture modes, paused or non-playing states, and stage transitions unless the product explicitly requires otherwise.

  • Disable it before any early-render branch so a previous frame cannot leak into another state.

  • Keep atmospheric scene fog separate from fog of war.

  • Dispose the overlay geometry, material, and lookup texture on teardown.

  • Publish compact telemetry for ray budget, enabled state, ranges, visible enemies, target visibility, line of sight, and measured distance.

Tune in the right order

  1. Validate obstacle geometry and eye height.

  2. Validate player and enemy ranges.

  3. Set the fully clear inner radius.

  4. Set the outer radial falloff and maximum opacity.

  5. Set wall-edge softness.

  6. Add angular smoothing until spokes disappear.

  7. Reduce obstacle darkness until walls feel like occlusion rather than black rays.

  8. Adjust ray budgets only after visual correctness and measured performance.

Do not hide bad obstacle data with extra blur. Do not make the entire scene darker to compensate for weak visibility boundaries.

Validate

Read validation.md before claiming completion. Run:

  • pure perception and opacity tests;

  • structural render-budget contracts;

  • integration tests for targeting, layers, lighting, and state;

  • project lint and build;

  • real desktop and mobile browser checks in the Codex browser.

Use normal gameplay for visual verification when review modes intentionally disable fog. Judge motion, camera movement, wall edges, gates, seams, and menu transitions—not only a still frame.

Protect the proven qualities

  • Keep the inner play area readable.

  • Keep the maximum darkness restrained.

  • Keep walls soft and free of visible radial spokes.

  • Keep the angle seam invisible.

  • Keep dynamic gates synchronized with sight.

  • Keep enemy behavior fair even when enemy vision exceeds player reveal.

  • Keep one authoritative visibility model and one bounded overlay.

Files

references/mechanics.md

Fog-of-war mechanics

Contents

  1. System contract

  2. Proven Vesperfall source map

  3. Obstacle and perception model

  4. Angular visibility lookup

  5. Full-screen overlay

  6. Proven calibration

  7. Frame update and lifecycle

  8. Gameplay mechanics

  9. Lighting and atmosphere

  10. Telemetry

  11. Failure modes

System contract

Use one source of truth:

active obstacle proxies
  -> point-to-point perception -> AI, attacks, target lock, presentation
  -> angular visible distances -> 1D lookup texture -> fog shader

The shader visualizes perception. It does not decide whether an enemy can see, attack, or be targeted.

The proven implementation uses an orthographic Three.js camera, but the ownership model applies to other camera types. Only the ground reconstruction changes.

Its four-corner reconstruction assumes that the visible gameplay region is locally horizontal at the player's ground Y. For sloped or multi-level terrain, reconstruct world position from the authoritative surface, a depth buffer, or terrain intersection instead of stretching one plane across the screen.

Proven Vesperfall source map

Use the current repository as authority before reusing this snapshot:

SurfaceResponsibility
app/vesperfall-perception.tsShared obstacle intersection, point perception, angular distance fill, and vision constants
app/vesperfall-fog-of-war.tsLookup texture, ground reconstruction, shader, throttled update, and disposal
app/vesperfall-estate-world.tsAuthoritative active obstacle proxies, including dynamic gate blockers
app/VesperfallCampaign.tsxCamera and scene integration, state gate, enemy visibility, targeting, telemetry, and lifecycle
app/vesperfall-lighting.tsShadowless world-light constraint that leaves visibility to fog and line of sight
tests/fog-of-war.test.tsFog math, calibration, spoke prevention, and bounded-renderer contracts
tests/perception.test.tsRange, wall, gate, low-cover, extended enemy vision, and deterministic-ray contracts
tests/vfx-lighting.test.tsLighting ownership, single renderer, and cleanup contracts

The implemented sequence was:

  1. Add wall-aware fog and shared enemy perception.

  2. Extend enemy sight beyond the player's reveal edge while retaining wall occlusion.

  3. Feather angular wall fog and reduce its weight until visible spokes disappeared.

Obstacle and perception model

Represent a sight blocker as:

type SightObstacle = {
  id: string;
  x: number;
  z: number;
  radius: number;
  minY: number;
  maxY: number;
  active: boolean;
};

The circular XZ footprint is a conservative proxy for walls, pillars, props, and gate segments. The vertical span prevents knee-high cover from blocking an eye-level sight line.

For each ray or segment:

  1. Skip inactive obstacles.

  2. Expand the radius and vertical span by a small padding.

  3. Solve the horizontal ray-circle quadratic.

  4. Test both near and far non-negative roots.

  5. Evaluate the ray height at each candidate distance.

  6. Accept only hits inside the obstacle's vertical span.

  7. Return the nearest hit or the requested maximum distance.

Handle these edge cases explicitly:

  • a ray with almost no horizontal direction;

  • an origin already inside the footprint and vertical span;

  • a zero-length point-to-point query;

  • a negative range or padding;

  • a hit exactly at the target distance;

  • inactive gates.

Use one point-to-point API:

type PerceptionResult = {
  distance: number;
  withinRange: boolean;
  lineOfSightClear: boolean;
  visible: boolean;
};

Set visible = withinRange && lineOfSightClear. Keep the component booleans because AI, attacks, and telemetry need to distinguish range loss from occlusion.

Angular visibility lookup

Allocate a Float32Array with one element per ray. For index i of N:

const angle = (i / N) * Math.PI * 2;
const directionX = Math.cos(angle);
const directionZ = Math.sin(angle);

Cast from the player's eye height with zero vertical direction. Store the first obstacle distance or player vision radius.

Encode the normalized distances into a one-row THREE.DataTexture:

  • pixels: Uint8Array;

  • format: THREE.RedFormat;

  • type: THREE.UnsignedByteType;

  • min and mag filter: THREE.LinearFilter;

  • horizontal wrap: THREE.RepeatWrapping;

  • vertical wrap: THREE.ClampToEdgeWrapping;

  • mipmaps: disabled.

Horizontal repeat wrapping makes the 0/2π seam continuous. Linear filtering interpolates adjacent ray distances but does not remove all spoke artifacts on its own.

Reuse the array, pixels, and texture. Set needsUpdate only after rewriting pixels.

Full-screen overlay

Use a plane spanning clip space:

gl_Position = vec4(position.xy, 0.0, 1.0);

For each of the four NDC corners:

  1. Unproject a near point at z = -1.

  2. Unproject a far point at z = 1.

  3. Intersect the segment with a horizontal plane at the player's current ground Y.

  4. Store the resulting world XZ coordinate in a uniform.

In the fragment shader, bilinearly interpolate the four ground corners from the plane UV. Compute:

vec2 offset = worldXZ - playerWorldXZ;
float distanceToPlayer = length(offset);
float angle = fract(atan(offset.y, offset.x) / TWO_PI + 1.0);
Radial component

Keep the inner radius clear, then fade visibility to zero at the player vision radius:

radialVisibility = 1 - smoothstep(clearRadius, visionRadius, distance)
radialFog = 1 - radialVisibility
Obstacle component

Sample the lookup distance at the fragment angle and feather around that hit distance:

obstacleVisibility =
  1 - smoothstep(hitDistance - edgeSoftness,
                 hitDistance + edgeSoftness,
                 distance)

Use five angular taps around the fragment angle:

offsets: -2, -1, 0, +1, +2
weights: .08, .24, .36, .24, .08

Choose the angular step as the larger of one texture texel and a world-space softness projected to angular space:

max(1 / rayCount,
    angularSoftness / max(distanceToPlayer, 2) / (2 * TWO_PI))

This widens angular smoothing near the player, where sparse rays create the most obvious spokes, while keeping distant boundaries legible.

Composition

Use:

obstacleFog = (1 - obstacleVisibility) * obstacleDarkness
alpha = maximumOpacity * max(radialFog, obstacleFog)

Do not use the original hard composition:

alpha = maximumOpacity * (1 - min(radialVisibility, obstacleVisibility))

The hard composition gives wall occlusion the same darkness as the outer radial boundary and exposes each angular ray as a dark wedge.

Configure the overlay:

  • transparent;

  • depth test off;

  • depth write off;

  • tone mapping off;

  • frustum culling off;

  • very late render order;

  • one geometry and one shader material.

Proven calibration

The current Vesperfall calibration is:

ParameterValuePurpose
Player vision radius14Outer reveal range
Enemy vision radius18Lets enemies acquire beyond player reveal
Player eye height1.18Sight origin above ground
Obstacle padding0.08Stabilizes edge contacts
Clear radius11.25Fully readable inner play area
Edge softness1.35Soft radial and wall boundary
Angular softness1.8Removes visible spokes
Obstacle darkness0.32Keeps wall fog subtle
Maximum opacity0.38Restrains outer darkness
Desktop rays192Higher-detail angular lookup
Mobile rays128Mobile budget
Allowed ray clamp32–256Prevents pathological settings
Update interval0.08 sCaps stationary refresh cost
Movement threshold0.08 unitsRefreshes promptly after movement
Fog color0x07101aDark blue-black overlay

These values are a proven baseline, not universal constants. Preserve their relationships:

  • desktop rays exceed mobile rays;

  • player clear radius is below player vision radius;

  • enemy vision may exceed player vision but still respects walls;

  • obstacle fog remains much lighter than maximum radial fog;

  • maximum opacity stays readable rather than approaching black.

At the proven values, full wall occlusion contributes about 0.38 * 0.32 = 0.1216 alpha while the outer range reaches 0.38.

Frame update and lifecycle

On every enabled frame:

  1. Update overlay visibility.

  2. Update the player XZ uniform.

  3. Update camera matrices.

  4. Recompute the four ground-plane corner uniforms.

  5. Accumulate non-negative delta time.

  6. Recompute angular distances if the player moved at least 0.08 units or 0.08 seconds elapsed.

  7. Normalize, quantize, and upload the lookup.

Update the player and camera uniforms even when the CPU lookup is throttled. The overlay still has to follow camera and player motion smoothly.

Enable fog only when all gameplay conditions pass. The proven state gate requires:

  • menu hidden;

  • inventory closed;

  • no review mode;

  • game state playing;

  • no active stage transition.

Hide the overlay at the start of the frame before early menu or capture rendering. This prevents a previously enabled frame from appearing over a transparent character-select render.

Keep atmospheric THREE.Fog or THREE.FogExp2 separate. Atmospheric fog affects world materials by distance from the camera; fog of war is a player-centered visibility mask.

On teardown:

  • remove the overlay from the scene;

  • dispose geometry;

  • dispose shader material;

  • dispose data texture.

Gameplay mechanics

Player and enemy asymmetry

Evaluate player reveal with the player range. Evaluate enemy acquisition with the enemy range. A monster may begin pursuing from outside the player's reveal edge when it has a clear sight line. Walls must still block both.

This asymmetry produces pressure at the edge of visibility without making walls meaningless.

Presentation versus simulation

Keep actor-root visibility for stage membership, death, spawning, and lifecycle. Hide unseen renderable descendants with render layers or another presentation-only system. Preserve each descendant's base layer mask so it restores exactly.

Include meshes, lines, points, sprites, and lights. Hide enemy telegraphs when the enemy is unrevealed.

Never write perception into enemy.root.visible when simulation or roster code consumes that property.

Targeting and combat

Require player perception for:

  • lock-on target validity;

  • nearest-target acquisition;

  • pointer or ray target eligibility;

  • retaliation target selection;

  • player-facing enemy counts;

  • player attacks that require an exposed target.

Clear target lock when a target becomes occluded or leaves player range.

Require line of sight for enemy attacks and acquisition. Use enemy range for pursuit. Continue to simulate unrevealed enemies so pursuit, return, death, and damage state remain coherent.

Per-frame caching

Cache one perception sample per enemy within a simulation phase. Clear and recompute after movement if later systems require final-frame positions. This avoids repeated obstacle scans while preventing stale targeting.

Lighting and atmosphere

Do not let a second visual system compete with fog of war:

  • keep broad moon or world key lighting shadowless when projected shadows create distracting visibility bands;

  • use motivated local lights for readability;

  • let fog of war and line of sight own gameplay visibility;

  • do not add decorative radial light rays that resemble the fog lookup.

Lighting should make the visible area readable without revealing hidden enemies through attached lights or telegraphs.

Telemetry

Publish compact, DOM-readable diagnostics:

  • fog enabled state;

  • selected ray count;

  • player vision radius;

  • enemy vision radius;

  • visible enemy IDs;

  • target player-visible state;

  • target enemy-can-see-player state;

  • target line-of-sight state;

  • target distance;

  • target return reason.

Telemetry makes browser validation deterministic without coupling production logic to the test harness.

Failure modes

Visible spokes

Cause: one angular lookup sample controls a fully dark wall wedge.

Fix: increase wall-edge softness, add five-tap angular smoothing, and reduce obstacle darkness. Preserve full radial darkness only at the outer vision range.

Angle seam

Cause: clamped horizontal texture wrapping or inconsistent angle normalization.

Fix: use repeat wrapping and normalize angles with fract(angle + 1).

Fog slides during camera motion

Cause: screen-space radius or stale ground-corner uniforms.

Fix: reconstruct world XZ from the camera corners every enabled frame.

Fog distorts across elevation

Cause: one player-height plane is being stretched over sloped or multi-level terrain.

Fix: reconstruct against the authoritative walkable surface, terrain, or depth data. Do not ship the flat-plane shortcut where gameplay elevation is visible.

Gates remain opaque after opening

Cause: perception uses a copied or disconnected blocker list.

Fix: consume the authoritative runtime obstacle objects and toggle their active state with the gate.

Enemies freeze or disappear permanently

Cause: actor-root visibility is being used as player perception.

Fix: preserve actor-root lifecycle visibility and hide only renderable descendants.

Low props block sight

Cause: the intersection checks only XZ.

Fix: evaluate hit Y against the obstacle's vertical span.

Fog leaks into menus or captures

Cause: the overlay is disabled after an early render branch.

Fix: hide it before branches and gate it explicitly by state.

Performance regresses

Cause: per-ray meshes, per-frame allocations, a second renderer, or post-processing.

Fix: retain one fixed array, one byte texture, one full-screen shader, and a movement/time-throttled CPU fill.

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Meng To
Publisher
Meng To (Community)
Source
MengTo/Skills · agent-skills/codex/implement-fog-of-war · commit 5ea1661
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Meng To.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download implement-fog-of-war.zip