Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
b42c659
ARE ambient-animation detection + fpSEAM point effects; remove tile-s…
TheForgotten69 Jul 17, 2026
4609c20
fpSEAM: flatten point-effect loop into main; add ALIGN-mode point mar…
TheForgotten69 Jul 17, 2026
d1aab40
Point effects: night-adaptive intensity boost; immediate 1x latch for…
TheForgotten69 Jul 17, 2026
51d2709
Point effects v2: textured flame/smoke replacements + CGameStatic::Re…
TheForgotten69 Jul 18, 2026
1994d96
ci: add iee_effects_noise.rgba to the release-bundle allow-list
TheForgotten69 Jul 18, 2026
3beab79
Point effects: authored-size flames with palette colors; wispier smok…
TheForgotten69 Jul 18, 2026
d24a4a7
Point effects: authored BAM draw-box geometry; overlay art keeps its …
TheForgotten69 Jul 18, 2026
07eb724
Point effects: palette id moved to a plain slot-B float; elliptical d…
TheForgotten69 Jul 18, 2026
4fe81fb
Point effects: case-insensitive geometry/palette lookup; hearth glow …
TheForgotten69 Jul 18, 2026
7427b4a
Point effects: anchor is the flame base; remove heat-shimmer UV disto…
TheForgotten69 Jul 18, 2026
12b6d4b
Point effects: render from CGameObject::m_pos with the authored frame…
TheForgotten69 Jul 18, 2026
5ce679c
Point effects: engine-native flame geometry from CVidCell::m_pFrame
TheForgotten69 Jul 18, 2026
6707f86
Point effects: anchor flame at the object position, no positional offset
TheForgotten69 Jul 18, 2026
49975b2
Point effects: subtract the m_posZ elevation from the flame anchor
TheForgotten69 Jul 18, 2026
9dff696
Update build.yml
TheForgotten69 Aug 26, 2026
62b22e2
Support cross-compiling the Windows DLL with mingw-w64
TheForgotten69 Aug 26, 2026
a3423fb
Fix authored flame replacement anchors
TheForgotten69 Aug 27, 2026
3c2b6a6
Keep authored effects visible in alignment mode
TheForgotten69 Aug 27, 2026
73474b4
Constrain flame body to authored BAM envelope
TheForgotten69 Aug 27, 2026
ce063dd
Taper procedural flame tips beyond core envelope
TheForgotten69 Aug 27, 2026
3a055a2
docs: add graphics uplift feasibility roadmap (2026-10-03)
TheForgotten69 Oct 3, 2026
8ed5e12
Point effects: place fire/smoke from the BAM frame table
TheForgotten69 Oct 3, 2026
e99372e
tests: avoid initializer-list vector assignment (GCC 13 -Werror false…
TheForgotten69 Oct 3, 2026
8f42cb5
Add sprite movement smoothing between logic ticks
TheForgotten69 Oct 3, 2026
291e12e
Point effects: do not bound the BAM frame lookup by m_nFrameList
TheForgotten69 Oct 3, 2026
04f4315
Sprite smoothing: restore the logic position only if unchanged
TheForgotten69 Oct 3, 2026
b0e1961
Sprite smoothing: cover BAM projectiles and forget stale keys
TheForgotten69 Oct 3, 2026
a08c076
Sprite smoothing: cover every moving object type
TheForgotten69 Oct 3, 2026
098f8f1
Add draw-time animation interpolation from expanded BAMs
TheForgotten69 Oct 3, 2026
948f69f
Animation interpolation: all-or-nothing per creature, guarded restore…
TheForgotten69 Oct 3, 2026
5879249
Animation interpolation: accept the BAM empty-slot marker in lookup t…
TheForgotten69 Oct 3, 2026
a01139c
docs: record the FSR-family decision and the revised phase order
TheForgotten69 Oct 4, 2026
30d518f
docs: soft fog of war and bloom design and plan
TheForgotten69 Oct 7, 2026
180d62b
World post: sizing logic for blur chains and zoom scale
TheForgotten69 Oct 7, 2026
2b79279
Config: soft fog of war and bloom settings
TheForgotten69 Oct 7, 2026
694d364
Manifest: CInfinity::RenderFog and DrawFlush_GL for 2.7.3
TheForgotten69 Oct 7, 2026
29dc3eb
GL: framebuffer and vertex-array entry points, pass state guard
TheForgotten69 Oct 7, 2026
f200e38
World post: soft fog of war and threshold bloom behind the RenderFog …
TheForgotten69 Oct 7, 2026
e021baa
docs: record the engine draw queue and the soft fog/bloom status
TheForgotten69 Oct 7, 2026
5d89d50
docs: state the exact GL state the world post passes restore
TheForgotten69 Oct 7, 2026
af50b75
Bloom: keep bright pixels at full strength; sample INI keys for soft …
TheForgotten69 Oct 7, 2026
d04fda2
Bloom: glow and light spill from the engine's additive draws instead …
TheForgotten69 Oct 7, 2026
d0cd02d
World post: drifting soft fog edge; light spill with a falloff curve …
TheForgotten69 Oct 7, 2026
8334c3d
World post: smooth the fog over time so it no longer steps on the log…
TheForgotten69 Oct 7, 2026
a64abeb
World post: heat shimmer above warm light sources
TheForgotten69 Oct 7, 2026
c55f987
Smooth rain, snow, sparkles and floating text between logic ticks; lo…
TheForgotten69 Oct 7, 2026
a9716ae
Sprite smoothing: FSR1 upscale of the sprite atlas before the engine …
TheForgotten69 Oct 7, 2026
0ee147b
World pass gate: tolerate repeated frame boundaries (bloom and sprite…
TheForgotten69 Oct 7, 2026
bc09968
Sprite upscaling: field log of the first texture uploads and the atla…
TheForgotten69 Oct 7, 2026
9c25aba
Sprite upscaling: log flush and upload counters and the atlas's pendi…
TheForgotten69 Oct 7, 2026
e03e175
Manifest: TexSubImage_GL pattern was in the particle slots (sprite up…
TheForgotten69 Oct 7, 2026
20a93a0
Sprite smoothing: selectable filters (Catmull-Rom, EASU, and 4x combi…
TheForgotten69 Oct 7, 2026
e2c7fb9
Revert "Sprite smoothing: selectable filters (Catmull-Rom, EASU, and …
TheForgotten69 Oct 7, 2026
2b61ad4
docs: record the sprite filter comparison and the owner's choice of FSR1
TheForgotten69 Oct 7, 2026
a16cab9
Sprite smoothing: keep the atlas corner untextured draws sample (sele…
TheForgotten69 Oct 8, 2026
41b0b58
docs: selection circle fix confirmed in game
TheForgotten69 Oct 8, 2026
99da435
docs: shader effect replacement design, blood first
TheForgotten69 Oct 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,9 @@ jobs:
"override/M_IEEE.lua"
"override/fpSEAM.glsl"
"iee-textures/README.md"
"iee-textures/iee_effects_noise.rgba"
"iee-textures/iee_water_dudv.rgba"
"iee-textures/iee_water_foam.rgba"
"iee-textures/iee_water_foam.rgba"
"iee-textures/iee_water_normal.rgba"
) | Sort-Object
$actual = Get-ChildItem $root -File -Recurse | ForEach-Object {
Expand Down
11 changes: 10 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ Operational instructions for AI coding agents working in this repository.
and `8x`. Other values fail closed to the existing fallback path.
- `CResTileSet::h` is optional. Standard tilesets can have `header == null` while still exposing a valid 12-byte PVR entry table through `pData`. A null header does not imply missing deterministic metadata.
- Deterministic detection order for this build is:
`TIS header -> PVR entry table coordinate-grid GCD -> legacy heuristic fallback`
`TIS header -> PVR entry table coordinate-grid GCD -> fail closed to 1x`
(the legacy UV/texture-id heuristic produced false 4x detections on vanilla
areas and was removed — do not reintroduce it).
- `+0x1DC` is only the current linear-tiles tone flag for this build.

## Runtime Facts — Renderer / GL
Expand All @@ -42,6 +44,9 @@ Operational instructions for AI coding agents working in this repository.
- The engine compiles nine named GLSL programs (`fpSEAM`, `fpSprite`, `fpSELECT`, `fpCatRom`, ...). The verified slot table and shader-replacement strategy live in the graphics roadmap spec (see Docs).
- The engine does **zero** lighting work. Day/night is a swap of authored assets. `CGameArea` holds authored per-area bitmaps (`m_bmLum` +0x260, `m_pbmLumNight` +0x380, `m_bmHeight` +0x388) usable as static lookup data only.
- Prefer `SDL_GL_SwapWindow` (SDL2.dll export) over `DrawFlip` patterns for the frame boundary.
- The engine queues every draw (`DrawEnd_GL` only appends to `gl.cmds`) and submits the whole frame in `DrawFlush_GL` (2.7.3 RVA `0x42B350`), normally once at flip, and earlier whenever its 1024x1024 sprite atlas fills. Binding a framebuffer around an engine render function captures nothing, and an immediate GL draw of ours lands under the still-queued world. Flush first (see `features/world_post.*`), and restore every GL state touched: the flush issues changes relative to the engine's cached `gl.hwState`.
- Each queued draw command is 12 bytes (state, first vertex, count) in `gl.cmds`, counted by `gl.n`. The state word carries the blend factors (source bits 9-12, destination bits 13-16, index 1 = `GL_ONE`). The engine draws its light-emitting art (sprite render flags `0x8` / `0x200`: fires, spell effects, glows) with destination `GL_ONE`, so "additive command" is a reliable "this emits light" signal; bloom is fed from it (`game/draw_queue.*`, `detour_draw_flush`).
- `gl.pp.enabled` is forced false in 2.7.3 (`DrawInit_GL`): the engine's own offscreen target and `fpCatRom` are dead.

## Runtime Facts — Hooking Safety

Expand All @@ -59,6 +64,10 @@ Operational instructions for AI coding agents working in this repository.
- `src/iee/game/build_manifest.*` holds build-specific offsets, patterns, and callsites.
- `src/iee/game/tis_runtime.*` holds explicit runtime views.
- `src/iee/game/tile_upscale.*` holds scale selection logic.
- `src/iee/game/are_animations.*` + `src/iee/game/object_statics.*` classify
the active area's authored ARE ambient animations (fire/smoke/fountain/light
point sources) from the live CGameStatic objects; see
`docs/are-animation-detection.md`. Water bodies stay on the WED overlay path.
- `docs/` contains the architecture and reverse-engineering notes future agents should read first.
- `docs/threading-model.md` defines callback ownership, GL-thread rules, and ABI exception boundaries.
- `docs/superpowers/specs/2026-06-10-graphics-enhancement-roadmap-design.md` is the graphics roadmap: four feature pillars, validation gates (V1-V6), and the full evaluated/dropped/rejected idea ledger. Read it before proposing any rendering feature — most ideas have already been evaluated there.
Expand Down
24 changes: 20 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,23 @@ add_library(iee_common STATIC
src/iee/core/config.cpp
src/iee/core/logger.cpp
src/iee/core/pattern_scanner.cpp
src/iee/game/animation_interp.cpp
src/iee/game/are_animations.cpp
src/iee/game/area_texture.cpp
src/iee/game/build_manifest.cpp
src/iee/game/dds_texture.cpp
src/iee/game/draw_queue.cpp
src/iee/game/game_addrs.cpp
src/iee/game/object_statics.cpp
src/iee/game/resref_runtime.cpp
src/iee/game/shader_override.cpp
src/iee/game/sprite_motion.cpp
src/iee/game/tile_liquid.cpp
src/iee/game/tile_upscale.cpp
src/iee/game/tis_palette.cpp
src/iee/game/tis_runtime.cpp
src/iee/game/wed_runtime.cpp
src/iee/game/world_post_plan.cpp
)

target_include_directories(iee_common
Expand Down Expand Up @@ -145,6 +151,7 @@ if(IEE_BUILD_WINDOWS_DLL)
src/iee/dll_main.cpp
src/iee/exports.cpp
src/iee/features/tile_render.cpp
src/iee/features/world_post.cpp
src/iee/frame_hook.cpp
src/iee/hooks.cpp
src/iee/shader_diagnostics.cpp
Expand Down Expand Up @@ -189,17 +196,26 @@ if(IEE_BUILD_WINDOWS_DLL)

set(IEE_RELEASE_BUNDLE_DIR "${CMAKE_BINARY_DIR}/release-bundle")
set(IEE_SYMBOLS_DIR "${CMAKE_BINARY_DIR}/symbols")
add_custom_target(release_bundle
set(IEE_BUNDLE_COMMANDS
COMMAND "${CMAKE_COMMAND}" -E rm -rf "${IEE_RELEASE_BUNDLE_DIR}"
COMMAND "${CMAKE_COMMAND}" -E rm -rf "${IEE_SYMBOLS_DIR}"
COMMAND "${CMAKE_COMMAND}" -E make_directory "${IEE_RELEASE_BUNDLE_DIR}"
COMMAND "${CMAKE_COMMAND}" -E make_directory "${IEE_SYMBOLS_DIR}"
COMMAND "${CMAKE_COMMAND}" -E copy "$<TARGET_FILE:InfinityEngine-Enhancer>" "${IEE_RELEASE_BUNDLE_DIR}/InfinityEngine-Enhancer.dll"
COMMAND "${CMAKE_COMMAND}" -E copy "$<TARGET_PDB_FILE:InfinityEngine-Enhancer>" "${IEE_SYMBOLS_DIR}/InfinityEngine-Enhancer.pdb"
COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_SOURCE_DIR}/tools/InfinityEngine-Enhancer.sample.ini" "${IEE_RELEASE_BUNDLE_DIR}/InfinityEngine-Enhancer.sample.ini"
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/assets/override" "${IEE_RELEASE_BUNDLE_DIR}/override"
COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_SOURCE_DIR}/tools/M_IEEE.lua" "${IEE_RELEASE_BUNDLE_DIR}/override/M_IEEE.lua"
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/assets/game-textures" "${IEE_RELEASE_BUNDLE_DIR}/iee-textures"
)
if(MSVC)
# TARGET_PDB_FILE only resolves for linkers that emit a PDB. mingw-w64
# cross builds have no equivalent, so keep symbol export MSVC-only.
list(APPEND IEE_BUNDLE_COMMANDS
COMMAND "${CMAKE_COMMAND}" -E rm -rf "${IEE_SYMBOLS_DIR}"
COMMAND "${CMAKE_COMMAND}" -E make_directory "${IEE_SYMBOLS_DIR}"
COMMAND "${CMAKE_COMMAND}" -E copy "$<TARGET_PDB_FILE:InfinityEngine-Enhancer>" "${IEE_SYMBOLS_DIR}/InfinityEngine-Enhancer.pdb"
)
endif()
add_custom_target(release_bundle
${IEE_BUNDLE_COMMANDS}
DEPENDS InfinityEngine-Enhancer
VERBATIM
)
Expand Down
Binary file added assets/game-textures/iee_effects_noise.rgba
Binary file not shown.
209 changes: 209 additions & 0 deletions assets/override/fpSEAM.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,16 @@ uniform highp vec2 uIeeZoom; // physical px per world px, per axis
uniform highp vec2 uIeeViewport; // physical px (w, h)
uniform highp vec2 uIeeWorldSizeInv; // 1 / world px
uniform highp vec3 uIeeWaterTint; // authored water color (avg of the area's water overlay tile)
uniform highp float uIeePointCount; // 0..32 classified ambient-animation points
// Two vec4 slots per point i: [2i] = (baseX, baseY, kind, heightPx) where the
// kind fraction is a palette id (.0 warm body / .1 blue body / .2 glow only);
// [2i+1] = (halfWidthPx, reserved...).
uniform highp vec4 uIeePoints[64];
uniform lowp sampler2D uIeeAreaMask; // unit 2: one liquid mode per 64px WED cell
uniform lowp sampler2D uIeeNormalMap; // unit 3: tiling water normal map
uniform lowp sampler2D uIeeDudvMap; // unit 4: tiling DuDv distortion map
uniform lowp sampler2D uIeeFoamMap; // unit 5: tiling foam mask
uniform lowp sampler2D uIeeNoiseMap; // unit 6: tiling FBM octaves (R/G smooth, B high-freq, A blobs)

varying highp vec2 vTc;
varying highp vec2 vRef;
Expand Down Expand Up @@ -266,9 +272,184 @@ void main()
vec4 base = art * vColor;
gl_FragColor = vec4(base.rgb, base.a);
}

// Point-placement markers: a filled dot plus a 40px ring at every fed
// effect point — orange = fire, grey = smoke, yellow = light. Absent
// rings in ALIGN mode mean the point feed (not the effect math) is
// broken.
if (uIeePointCount > 0.5 && vColor.a > 0.9)
{
float ring = 0.0;
vec3 ringColor = vec3(0.0);
for (int i = 0; i < 32; ++i)
{
if (float(i) >= uIeePointCount) { break; }
vec4 p = uIeePoints[2 * i];
float d = length(worldPos - p.xy);
float m = smoothstep(4.0, 1.5, abs(d - 40.0)) + smoothstep(6.0, 2.0, d);
if (m > ring)
{
ring = m;
if (p.z < 1.5) { ringColor = vec3(1.0, 0.45, 0.05); }
else if (p.z < 2.5) { ringColor = vec3(0.75, 0.75, 0.85); }
else { ringColor = vec3(1.0, 0.95, 0.20); }
}
}
if (ring > 0.02)
{
gl_FragColor = vec4(mix(gl_FragColor.rgb, ringColor, clamp(ring, 0.0, 1.0)),
gl_FragColor.a);
}
}
return;
}

// Ambient point effects. Base pass only; the WATER_ALPHA secondary pass
// and fades stay vanilla. Kept as flat locals (no struct/function) for
// maximum GLSL-frontend compatibility.
vec3 fxGlow = vec3(0.0);
vec3 fxFlame = vec3(0.0);
float fxFlameA = 0.0;
float fxHaze = 0.0;
if (uIeeEnabled > 0.5 && vColor.a > 0.9 && uIeePointCount > 0.5)
{
float fxT = uIeeTime;
for (int i = 0; i < 32; ++i)
{
if (float(i) >= uIeePointCount) { break; }
vec4 p = uIeePoints[2 * i];
vec4 pb = uIeePoints[2 * i + 1];
vec2 offs = worldPos - p.xy;
float kind = p.z;
float strength = clamp(p.w / 76.0, 0.05, 1.4);

if (kind < 1.5) // fire: textured flame body + cast light + shimmer
{
// Authored geometry from the BAM frame table: p.w = height,
// pb.x = half-width, position already at the flame's
// bottom-center; pb.y = palette (0 warm / 1 blue / 2 glow-only).
float fh = max(p.w, 5.0);
float fw = max(pb.x, 1.5);
float palette = pb.y;
float blue = (palette > 0.5 && palette < 1.5) ? 1.0 : 0.0;
bool bodyless = palette > 1.5;

// --- flame body (replaces the engine's BAM flame) ---
// Keep the luminous core inside the authored BAM envelope: p.xy is
// the proven bottom attachment point, so no body pixels belong below
// it. A low-opacity procedural tip may taper above the authored core;
// evaluate beyond fh so the animation never clips at a hard top edge.
if (!bodyless && offs.y <= 0.0 && offs.y > -fh * 1.3 && abs(offs.x) < fw * 2.6)
{
float v = clamp(-offs.y / fh, 0.0, 1.3); // 0 base -> 1 authored tip
float widthAt = fw * (1.05 - 0.60 * min(v, 1.0));
float xr = offs.x / max(widthAt, 1.0);
float radial = 1.0 - clamp(xr * xr, 0.0, 1.0);
float base = 1.0 - smoothstep(-2.0, 0.0, offs.y);
float tip = 1.0 - smoothstep(0.72, 1.24, v);
float column = radial * base * tip;
// Rising noise erodes the column: calm base, ragged tip.
// Sample scale follows the flame size so small flames keep
// visible structure.
float ns = max(fh, 14.0);
float seed = p.x * 0.61 + float(i) * 19.0;
float nA = texture2D(uIeeNoiseMap,
vec2((seed + offs.x) / (ns * 0.62),
(offs.y + fxT * ns * 1.35) / (ns * 0.9))).r;
float nB = texture2D(uIeeNoiseMap,
vec2((seed * 0.37 - offs.x) / (ns * 0.36),
(offs.y + fxT * ns * 2.1) / (ns * 0.52))).b;
float intensity = column * (1.15 - v * 0.5)
- (nA * 0.78 + nB * 0.60) * (0.36 + 0.95 * v);
intensity = clamp(intensity * 1.75, 0.0, 1.0);
if (intensity > 0.02)
{
// Ramp: deep -> mid -> bright -> hot core (kept small).
vec3 c0 = mix(vec3(0.42, 0.02, 0.0), vec3(0.01, 0.06, 0.42), blue);
vec3 c1 = mix(vec3(1.0, 0.28, 0.02), vec3(0.08, 0.38, 0.95), blue);
vec3 c2 = mix(vec3(1.0, 0.70, 0.16), vec3(0.45, 0.75, 1.0), blue);
vec3 c3 = mix(vec3(1.02, 0.95, 0.72), vec3(0.85, 0.95, 1.05), blue);
vec3 flame = mix(c0, c1, smoothstep(0.02, 0.38, intensity));
flame = mix(flame, c2, smoothstep(0.38, 0.74, intensity));
flame = mix(flame, c3, smoothstep(0.80, 0.97, intensity));
float a = smoothstep(0.03, 0.30, intensity);
if (a > fxFlameA)
{
fxFlameA = a;
fxFlame = flame;
}
}
}

// --- cast light on the surroundings ---
// Elliptical footprint (isometric floor projection) with
// noise dapple so the pool reads as firelight, not a disc.
vec2 g = offs;
// Glow-only overlays (hearths) anchor at the art's floor
// edge; center their light well up onto the coals.
g.y += fh * (bodyless ? 0.85 : 0.35);
g.y *= 1.9;
float radius = 40.0 + 80.0 * strength;
float d = length(g);
if (d < radius)
{
float fall = 1.0 - d / radius;
fall *= fall;
float fi = float(i) * 7.31;
float flick = 0.80 + 0.14 * sin(fxT * 9.7 + fi)
+ 0.06 * sin(fxT * 23.0 + fi * 1.7);
float dapple = 0.72 + 0.55 * texture2D(uIeeNoiseMap,
(worldPos + vec2(fxT * 5.0, -fxT * 3.0)) / 72.0).r;
vec3 glowColor = mix(vec3(1.0, 0.45, 0.15), vec3(0.30, 0.55, 1.0),
step(0.5, blue));
fxGlow += glowColor * (fall * (0.20 + 0.38 * strength) * flick * dapple);
}
}
else if (kind < 2.5) // smoke: textured plume replacing the BAM puffs
{
float height = max(p.w, 20.0);
float rise = -offs.y; // px above the source
if (rise > -10.0 && rise < height)
{
float prog = rise / height;
float seed = p.x * 0.37 + float(i) * 11.0;
// Slow lateral wander that grows with altitude.
float sway = (texture2D(uIeeNoiseMap,
vec2(seed / 64.0 + fxT * 0.045, prog * 1.7)).a - 0.5)
* (8.0 + 34.0 * prog);
float widthPx = max(pb.x, 6.0) + 26.0 * prog;
float lateral = (offs.x - sway) / widthPx;
float across = exp(-lateral * lateral * 1.9);
// Two scrolling octaves shape the billows.
float dA = texture2D(uIeeNoiseMap,
vec2((offs.x + seed) / 96.0, (offs.y + fxT * 42.0) / 96.0)).g;
float dB = texture2D(uIeeNoiseMap,
vec2((offs.x - seed) / 48.0, (offs.y + fxT * 66.0) / 48.0)).r;
float density = clamp(dA * 0.80 + dB * 0.55 - 0.38, 0.0, 1.0);
float along = smoothstep(-10.0, 12.0, rise) * (1.0 - prog);
fxHaze += across * along * density * 0.62;
}
}
else if (kind > 3.5 && kind < 4.5) // light: steady soft glow
{
float radius = max(p.w, 10.0);
float d = length(offs);
if (d < radius)
{
float fall = 1.0 - d / radius;
fall *= fall;
float breathe = 0.92 + 0.08 * sin(fxT * 2.1 + float(i) * 3.3);
fxGlow += vec3(1.0, 0.74, 0.40) * (fall * 0.40 * breathe);
}
}
}
fxHaze = clamp(fxHaze, 0.0, 0.62);
}

// No heat-shimmer UV distortion here: offsetting the atlas coordinate
// can cross a tile boundary and sample unrelated atlas content (a
// vertical smear-seam in the art). Shimmer belongs to a future
// world-space pass, not the tile pass.
vec4 texColor = seamSample(vTc);

// Inside flagged cells the engine draws the
Expand Down Expand Up @@ -368,6 +549,34 @@ void main()
texColor.a = max(texColor.a, waterMask);
}

// Firelight, candle glow, and smoke haze over the (possibly water-graded)
// background art, in linear light so the warm lift does not band.
if (fxHaze > 0.004 || fxFlameA > 0.004 || fxGlow.r + fxGlow.g + fxGlow.b > 0.004)
{
vec3 lin = ieeSrgbToLinear(texColor.rgb);
float sceneLuma = dot(lin, vec3(0.2126, 0.7152, 0.0722));
// True black means void/unexplored: no art exists there, so cast
// light must not paint it (the additive term is luma-gated; the
// multiplicative term is naturally zero on black).
float hasArt = smoothstep(0.0015, 0.012, sceneLuma);
// Night-adaptive cast light: dark scenes get real light, bright day
// scenes only a whisper.
float castLight = (0.04 + 0.30 * (1.0 - clamp(sceneLuma * 4.0, 0.0, 1.0))) * hasArt;
lin = lin * (vec3(1.0) + fxGlow * 1.2) + fxGlow * castLight;
// Smoke veil: moonlit smoke over dark roofs, shadowy over bright
// ground. Not art-gated — a per-texel luma gate mottles dark stone,
// and smoke drifting over the night sky reads naturally.
vec3 hazeTarget = vec3(0.40, 0.40, 0.45) * (0.40 + 0.55 * sceneLuma) + vec3(0.02);
lin = mix(lin, hazeTarget, fxHaze);
// Flame body composites over everything with its own self-glow;
// flames are visible against the void (a fire at a cave mouth), so it
// is deliberately not art-gated.
lin = mix(lin, fxFlame, fxFlameA);
lin += fxFlame * fxFlameA * 0.35;
texColor.rgb = ieeLinearToSrgb(lin);
texColor.a = max(texColor.a, fxFlameA);
}

texColor = texColor * vColor;

float grey = dot(texColor.rgb, vec3(0.299, 0.587, 0.114));
Expand Down
18 changes: 18 additions & 0 deletions cmake/toolchains/mingw-w64.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Cross-compile the Windows DLL target from Linux with mingw-w64.
# Usage:
# cmake -S . -B build-mingw -G Ninja -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64.cmake \
# -DIEE_BUILD_WINDOWS_DLL=ON -DBUILD_TESTING=OFF
# cmake --build build-mingw --target release_bundle

set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)

set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)

set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
Loading
Loading