<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
	<channel>
		<title><![CDATA[Esenthel Forum - All Forums]]></title>
		<link>https://esenthel.com/forum/</link>
		<description><![CDATA[Esenthel Forum - https://esenthel.com/forum]]></description>
		<pubDate>Sun, 21 Jun 2026 03:03:44 +0000</pubDate>
		<generator>MyBB</generator>
		<item>
			<title><![CDATA[GPU Mesh Shader Grass GoT Style]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11827</link>
			<pubDate>Fri, 19 Jun 2026 11:32:44 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11827</guid>
			<description><![CDATA[<span style="font-size: x-large;">Best seen in video not pictures:<br />
<br />
<br />
<a href="https://streamable.com/hu3bzs" target="_blank">https://streamable.com/hu3bzs</a></span><br />
<br />
<br />
<br />
<br />
<a href="https://postimg.cc/FkfNNN4X" target="_blank"><img src="https://i.postimg.cc/tJkTd4vJ/Screenshot-From-2026-06-19-07-09-33.png" border="0" alt="[Image: Screenshot-From-2026-06-19-07-09-33.png]" /></a><br />
<br />
Grass density can be set per terrain material in Titan Editor:<br />
<br />
<a href="https://postimg.cc/zHL8881P" target="_blank"><img src="https://i.postimg.cc/3N1Rjxf7/Screenshot-From-2026-06-19-07-25-00.png" border="0" alt="[Image: Screenshot-From-2026-06-19-07-25-00.png]" /></a><br />
<br />
AI written summary:<br />
<br />
GPU Grass (Ghost-of-Tsushima-style) for Titan Engine — feature + tutorial<br />
<br />
I've added a GPU-driven grass system to my Esenthel/Titan fork and wanted to share it. It generates grass entirely on the GPU each frame using Vulkan mesh shaders, writing straight into the deferred G-buffer — so the engine's normal lighting, shadows, SSR, and fog apply for free. It's a parallel path that bypasses the shader/material system (the shader compiler has no mesh/task stage), and it's purely additive — the legacy CPU GrassObj foliage still works untouched.<br />
<br />
What it does<br />
<br />
- Deferred, lit grass on the GPU — VK_EXT_mesh_shader; blades generated per frame, no vertex/index buffers. Vulkan-only (inert on GL/DX, which keep the legacy grass).<br />
- Terrain-following + per-material density — a camera-centered control texture samples terrain height + a density built from terrain slope × the material's grass_density. Blades drape the heightfield, thin on slopes, and grow only where you author them.<br />
- World-anchored + smooth LOD — blades are seeded by absolute world position (no "swim" as the field re-centers), and density fades smoothly with distance instead of popping.<br />
- Interaction:<br />
  - trample(pos, radius, strength) — footstep flatten that springs back; a decoupled, world-anchored field uploaded dirty-region only.<br />
  - blast(pos, radius, strength) — one-shot radial knockdown shockwave that springs back.<br />
  - Exclusion zones — addZoneCircle / addZoneBox(center, half, yaw, soft) → handle, removeZone: persistent analytic CUT footprints (e.g. building placements) where grass is removed with a soft edge, plus a per-patch "fully enclosed → skip" early-out.<br />
- Wind — analytic flow field with traveling gust fronts + a slow sway.<br />
<br />
Opt-in by material<br />
<br />
Grass density is a per-material attribute (Material::grass_density, 0..1) with an editor "Grass Density" slider. It defaults to 0, so GPU grass is opt-in: enabling the system on an existing world shows nothing until you raise density on the grass materials. Author it toward 1 on grass/dirt, leave it 0 on rock/sand/snow/paths. (This deliberately avoids retro-fitting grass onto every existing material.)<br />
<br />
The demo — Tutorial_14_GpuGrass<br />
<br />
A character standing in a GPU-grass field on real terrain. Controls:<br />
<br />
- WSAD — walk (leaves a trample trail)<br />
- B — blast knockdown<br />
- G — toggle a building CUT zone at the character<br />
- F — force full density everywhere (handy on a world whose materials aren't authored yet)<br />
- Mouse — orbit, Wheel — zoom, Esc — quit<br />
<br />
Note: because density is opt-in, the demo shows no grass until you either set a terrain material's Grass Density &gt; 0 in the editor or press F — there's an on-screen hint for this.<br />
<br />
Implementation notes / caveats<br />
<br />
- Vulkan-only, gated on VkMeshShaderSupported.<br />
- Shaders (grass.task/.mesh/.frag + grass_common.hlsli) are HLSL compiled to SPIR-V by the vendored DXC at engine build time — a parallel path, not via EERegen, so no shader tooling needed at build.<br />
- Drawn from RendererClass::opaque() (RT_DEFERRED) via VkDrawGrass; its own self-contained UBO + descriptor set (not a ShaderVK); reverse-Z + negative-height viewport matched to the deferred config.<br />
- Grass is tagged PSM_TRANSLUCENT and excluded from SSAO (tblades otherwise make screen-space AO shimmer at distance).<br />
<br />
In this repo:<br />
<a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a>]]></description>
			<content:encoded><![CDATA[<span style="font-size: x-large;">Best seen in video not pictures:<br />
<br />
<br />
<a href="https://streamable.com/hu3bzs" target="_blank">https://streamable.com/hu3bzs</a></span><br />
<br />
<br />
<br />
<br />
<a href="https://postimg.cc/FkfNNN4X" target="_blank"><img src="https://i.postimg.cc/tJkTd4vJ/Screenshot-From-2026-06-19-07-09-33.png" border="0" alt="[Image: Screenshot-From-2026-06-19-07-09-33.png]" /></a><br />
<br />
Grass density can be set per terrain material in Titan Editor:<br />
<br />
<a href="https://postimg.cc/zHL8881P" target="_blank"><img src="https://i.postimg.cc/3N1Rjxf7/Screenshot-From-2026-06-19-07-25-00.png" border="0" alt="[Image: Screenshot-From-2026-06-19-07-25-00.png]" /></a><br />
<br />
AI written summary:<br />
<br />
GPU Grass (Ghost-of-Tsushima-style) for Titan Engine — feature + tutorial<br />
<br />
I've added a GPU-driven grass system to my Esenthel/Titan fork and wanted to share it. It generates grass entirely on the GPU each frame using Vulkan mesh shaders, writing straight into the deferred G-buffer — so the engine's normal lighting, shadows, SSR, and fog apply for free. It's a parallel path that bypasses the shader/material system (the shader compiler has no mesh/task stage), and it's purely additive — the legacy CPU GrassObj foliage still works untouched.<br />
<br />
What it does<br />
<br />
- Deferred, lit grass on the GPU — VK_EXT_mesh_shader; blades generated per frame, no vertex/index buffers. Vulkan-only (inert on GL/DX, which keep the legacy grass).<br />
- Terrain-following + per-material density — a camera-centered control texture samples terrain height + a density built from terrain slope × the material's grass_density. Blades drape the heightfield, thin on slopes, and grow only where you author them.<br />
- World-anchored + smooth LOD — blades are seeded by absolute world position (no "swim" as the field re-centers), and density fades smoothly with distance instead of popping.<br />
- Interaction:<br />
  - trample(pos, radius, strength) — footstep flatten that springs back; a decoupled, world-anchored field uploaded dirty-region only.<br />
  - blast(pos, radius, strength) — one-shot radial knockdown shockwave that springs back.<br />
  - Exclusion zones — addZoneCircle / addZoneBox(center, half, yaw, soft) → handle, removeZone: persistent analytic CUT footprints (e.g. building placements) where grass is removed with a soft edge, plus a per-patch "fully enclosed → skip" early-out.<br />
- Wind — analytic flow field with traveling gust fronts + a slow sway.<br />
<br />
Opt-in by material<br />
<br />
Grass density is a per-material attribute (Material::grass_density, 0..1) with an editor "Grass Density" slider. It defaults to 0, so GPU grass is opt-in: enabling the system on an existing world shows nothing until you raise density on the grass materials. Author it toward 1 on grass/dirt, leave it 0 on rock/sand/snow/paths. (This deliberately avoids retro-fitting grass onto every existing material.)<br />
<br />
The demo — Tutorial_14_GpuGrass<br />
<br />
A character standing in a GPU-grass field on real terrain. Controls:<br />
<br />
- WSAD — walk (leaves a trample trail)<br />
- B — blast knockdown<br />
- G — toggle a building CUT zone at the character<br />
- F — force full density everywhere (handy on a world whose materials aren't authored yet)<br />
- Mouse — orbit, Wheel — zoom, Esc — quit<br />
<br />
Note: because density is opt-in, the demo shows no grass until you either set a terrain material's Grass Density &gt; 0 in the editor or press F — there's an on-screen hint for this.<br />
<br />
Implementation notes / caveats<br />
<br />
- Vulkan-only, gated on VkMeshShaderSupported.<br />
- Shaders (grass.task/.mesh/.frag + grass_common.hlsli) are HLSL compiled to SPIR-V by the vendored DXC at engine build time — a parallel path, not via EERegen, so no shader tooling needed at build.<br />
- Drawn from RendererClass::opaque() (RT_DEFERRED) via VkDrawGrass; its own self-contained UBO + descriptor set (not a ShaderVK); reverse-Z + negative-height viewport matched to the deferred config.<br />
- Grass is tagged PSM_TRANSLUCENT and excluded from SSAO (tblades otherwise make screen-space AO shimmer at distance).<br />
<br />
In this repo:<br />
<a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Terrain Anti-Tiling: distance dual-scale + stochastic "by-example" no asset pipeline]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11826</link>
			<pubDate>Fri, 19 Jun 2026 03:57:20 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11826</guid>
			<description><![CDATA[<span style="font-size: large;"><span style="font-weight: bold;">Terrain Anti-Tiling: distance dual-scale + stochastic “by-example” (no asset pipeline)</span></span><br />
<br />
Before anti-tile:<br />
<a href="https://postimg.cc/fSLfnkSR" target="_blank"><img src="https://i.postimg.cc/br0Cntzk/Screenshot-From-2026-06-18-23-39-08.png" border="0" alt="[Image: Screenshot-From-2026-06-18-23-39-08.png]" /></a><br />
<br />
After anti-tile:<br />
<a href="https://postimg.cc/w3TVs4fF" target="_blank"><img src="https://i.postimg.cc/44pFqrzC/Screenshot-From-2026-06-18-23-38-58.png" border="0" alt="[Image: Screenshot-From-2026-06-18-23-38-58.png]" /></a><br />
<br />
Works best when looking out over the terrain, looking straight down at terrain it doesn't help as much.<br />
<br />
<br />
<br />
Written by AI:<br />
<br />
Repeating ground textures betray themselves two different ways, and they need two different fixes:<br />
<br />
<ol type="1">
<li><span style="font-weight: bold;">The far-field grid</span> — from a hill or a grazing camera, the regular <span style="font-style: italic;">periodicity</span> of the texture reads as a checkerboard receding to the horizon.</li>
<li><span style="font-weight: bold;">The close/mid feature repeat</span> — that one distinctive grass clump / rock / crack showing up again every few meters right in front of you.<br />
</li></ol>
<br />
Two complementary techniques in the deferred terrain shader (<div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>Engine/Source/Shaders/Deferred.cpp</code></div></div>
) address each. Both run on the <span style="font-weight: bold;">single- and multi-material</span> terrain paths, up to 5 blended materials, and both are <span style="font-weight: bold;">gated to terrain only</span> (<div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>#if HEIGHTMAP &amp;&amp; SET_POS</code></div></div>
), so buildings, props, and characters are completely untouched.<br />
<br />
<span style="font-size: medium;"><span style="font-weight: bold;">1. Distance-based dual-scale: kills the far grid</span></span><br />
<br />
Sample the same ground texture at <span style="font-weight: bold;">two UV scales</span> and blend in the lower-frequency, larger-tile one with camera distance:<br />
<br />
<div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>tex = lerp(near, RTex(Col, uv * TileFarScale), fade(distance));</code></div></div>
<br />
Near the camera you get full detail. Far away, the tight repeat dissolves into a coarser, non-aligned pattern. This is the standard Unreal-landscape-style trick, and it is <span style="font-weight: bold;">mip-safe for free</span>, because the two samples use implicit derivatives, so no <div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>SampleGrad</code></div></div>
 is needed.<br />
<br />
<span style="font-size: medium;"><span style="font-weight: bold;">2. Stochastic “by-example”: kills the near/mid feature repeat</span></span><br />
<br />
This is <span style="font-weight: bold;">Heitz–Neyret’s by-example noise, minus the histogram LUT</span>, which means it needs <span style="font-weight: bold;">no precomputed assets</span> and no per-texture bake.<br />
<ul>
<li><span style="font-weight: bold;">Triangular / “hex” grid</span> — 3 samples, no axis-aligned blend seams.</li>
<li><span style="font-weight: bold;">Per-cell random offset only, no mirror</span> — the mirror in the cheaper grid variants is what causes those directional scratches/scuffs on grass. Dropping it removes them.</li>
<li><span style="font-weight: bold;">Variance-preserving blend</span> — naive averaging washes out contrast, so instead of <div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>sum(w_i * c_i)</code></div></div>
, we blend the deviation from the mean and renormalize by <div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>1 / sqrt(sum(w_i^2))</code></div></div>
. The mean is read from the texture’s <span style="font-weight: bold;">smallest mip</span>, approximately its average color, so we recover contrast with zero precomputation.</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>SampleGrad</code></div></div>
 keeps mipmapping correct.<br />
</li></ul>
<br />
The result: a single tiling texture stops repeating and reads as a natural, non-periodic surface — no scratches, no muddy blend blobs.<br />
<br />
<span style="font-size: medium;"><span style="font-weight: bold;">How to use it</span></span><br />
<br />
Everything is driven by a single <div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>TerrainTile</code></div></div>
 shader cbuffer, set from the public shader-param API:<br />
<br />
<div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>SPSet("TerrainTileEnable", 1.0f);&nbsp;&nbsp;// distance effect<br />
SPSet("TileFarScale", 0.22f);<br />
SPSet("TileFadeStart", 5.0f);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// ...TileFadeRange, TileFarMax<br />
<br />
SPSet("TerrainStochastic", 1.0f);&nbsp;&nbsp;// stochastic de-tiling</code></div></div>
<br />
<span style="font-weight: bold;">It is off by default.</span> The cbuffer is zero-initialized, so nothing changes unless your app sets the params. The editor and all other content render exactly as before.<br />
<br />
The params only exist once the terrain shader has loaded, so set them <span style="font-weight: bold;">each frame before drawing the world</span>. <div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>SPSet</code></div></div>
 safely no-ops until then. There is no engine-wide default yet; you opt in from your render loop.<br />
<br />
<span style="font-size: medium;"><span style="font-weight: bold;">Parameters</span></span><br />
<ul>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>TerrainTileEnable</code></div></div>
 — 0/1, distance dual-scale on/off.</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>TileFarScale</code></div></div>
 — coarse UV multiplier. Smaller means bigger far tiles and a stronger frequency break.</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>TileFadeStart</code></div></div>
 — meters: distance where the coarse blend begins.</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>TileFadeRange</code></div></div>
 — meters: distance over which it ramps to <div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>TileFarMax</code></div></div>
.</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>TileFarMax</code></div></div>
 — max fraction of the coarse sample blended in at far distance.</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>TerrainStochastic</code></div></div>
 — 0/1, stochastic de-tiling on/off.</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>StochMaxDist</code></div></div>
 — performance limit: only de-tile within this distance. <div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>&lt;=0</code></div></div>
 means no limit.</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>StochMinWeight</code></div></div>
 — performance limit: skip materials whose blend weight is below this.  means de-tile all.<br />
</li></ul>
<br />
<span style="font-size: medium;"><span style="font-weight: bold;">Performance limits: opt-in, runtime</span></span><br />
<br />
Multi-material stochastic can hit up to <span style="font-weight: bold;">5 materials × 4 taps = 20 albedo taps</span>, so there are two runtime knobs:<br />
<ul>
<li><span style="font-weight: bold;"><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>StochMaxDist</code></div></div>
</span> — only run the stochastic taps within this distance. The far grid is handled by the distance blend anyway, so far pixels drop straight back to 1 tap.</li>
<li><span style="font-weight: bold;"><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>StochMinWeight</code></div></div>
</span> — skip stochastic for a material whose per-pixel blend weight is below a threshold, so a barely-visible layer does not cost 4 taps.<br />
</li></ul>
<br />
Both default to “no practical limit.”<br />
<br />
<span style="font-size: medium;"><span style="font-weight: bold;">Large-world / floating-origin</span></span><br />
<br />
Terrain UVs are <span style="font-style: italic;">world-continuous</span>, built from the absolute area index, so far out in a big world they grow large.<br />
<br />
A <div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>Frac(sin(...))</code></div></div>
 hash loses precision there and the de-tiling starts aliasing, so the stochastic cell hash is a <span style="font-weight: bold;">PCG2D integer hash</span> on the integer cell ID, exact at any magnitude.<br />
<br />
The pattern stays locked to the ground and is transparent to floating-origin rebasing. UVs deliberately <span style="font-weight: bold;">do not</span> rebase. Shifting them would move the non-integer-aligned stochastic grid and pop the pattern every rebase.<br />
<br />
<span style="font-size: medium;"><span style="font-weight: bold;">Tutorials</span></span><br />
<br />
<span style="font-weight: bold;"><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>Tutorial_14_TerrainTiling</code></div></div>
</span><br />
<div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>Tutorials/Source/14 - Game Basics/36 - Terrain Tiling.cpp</code></div></div>
<br />
The live A/B demo. It loads a textured world and lets you toggle and tune everything in real time:<br />
<ul>
<li><span style="font-weight: bold;">T</span> — distance anti-tiling on/off.</li>
<li><span style="font-weight: bold;">Y</span> — stochastic de-tiling on/off.</li>
<li><span style="font-weight: bold;">1–8</span> — distance params: strength, fade start, fade range, coarse scale.</li>
<li><span style="font-weight: bold;">G / H</span> — stochastic max distance, for performance.</li>
<li><span style="font-weight: bold;">J / K</span> — stochastic min weight, for performance.</li>
<li><span style="font-weight: bold;">9</span> — reset all to defaults.</li>
<li>Hold <span style="font-weight: bold;">RMB</span> — look.</li>
<li><span style="font-weight: bold;">Mouse wheel</span> — zoom.</li>
<li><span style="font-weight: bold;">ESC</span> — quit.<br />
</li></ul>
<br />
The HUD shows every value live, and the <div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>EE_TILE*</code></div></div>
 env vars let you drive it headless for A/B captures:<br />
<br />
<div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>EE_TILE<br />
EE_TILE_STOCH<br />
EE_TILE_CAM_*<br />
EE_TILE_FARMAX<br />
EE_TILE_STOCHDIST</code></div></div>
<br />
Since the effect lives in the shader, it shows up on <span style="font-style: italic;">any</span> terrain. The other world tutorials are good places to see it on different content once an app enables the params:<br />
<ul>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>Tutorial_14_World</code></div></div>
</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>Tutorial_14_WorldWithCharacter</code></div></div>
</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>Tutorial_14_ProceduralWorld</code></div></div>
</li></ul>
<br />
<span style="font-size: medium;"><span style="font-weight: bold;">Caveats / future work</span></span><br />
<ul>
<li>The only thing the <span style="font-weight: bold;">full Heitz histogram LUT</span> would add over this is slightly sharper blend zones. It carries a per-texture asset-bake cost, so it is left out. The variance-preserving approximation looks clean in practice.</li>
<li>One residual issue: the triangular-grid <span style="font-weight: bold;">barycentric weights</span> lose a little float precision at <span style="font-style: italic;">extreme</span> UV. The cell hash is exact, so the de-tiling holds; only the blend transitions get marginally coarser very far out.</li>
<li><span style="font-weight: bold;">Backends:</span> Vulkan and GL shader paks are regenerated. <span style="font-weight: bold;">DX still needs a Windows tree</span> to recompile, only tested on Linux+Vulkan.<br />
</li></ul>
<br />
in this repo: <a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a>]]></description>
			<content:encoded><![CDATA[<span style="font-size: large;"><span style="font-weight: bold;">Terrain Anti-Tiling: distance dual-scale + stochastic “by-example” (no asset pipeline)</span></span><br />
<br />
Before anti-tile:<br />
<a href="https://postimg.cc/fSLfnkSR" target="_blank"><img src="https://i.postimg.cc/br0Cntzk/Screenshot-From-2026-06-18-23-39-08.png" border="0" alt="[Image: Screenshot-From-2026-06-18-23-39-08.png]" /></a><br />
<br />
After anti-tile:<br />
<a href="https://postimg.cc/w3TVs4fF" target="_blank"><img src="https://i.postimg.cc/44pFqrzC/Screenshot-From-2026-06-18-23-38-58.png" border="0" alt="[Image: Screenshot-From-2026-06-18-23-38-58.png]" /></a><br />
<br />
Works best when looking out over the terrain, looking straight down at terrain it doesn't help as much.<br />
<br />
<br />
<br />
Written by AI:<br />
<br />
Repeating ground textures betray themselves two different ways, and they need two different fixes:<br />
<br />
<ol type="1">
<li><span style="font-weight: bold;">The far-field grid</span> — from a hill or a grazing camera, the regular <span style="font-style: italic;">periodicity</span> of the texture reads as a checkerboard receding to the horizon.</li>
<li><span style="font-weight: bold;">The close/mid feature repeat</span> — that one distinctive grass clump / rock / crack showing up again every few meters right in front of you.<br />
</li></ol>
<br />
Two complementary techniques in the deferred terrain shader (<div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>Engine/Source/Shaders/Deferred.cpp</code></div></div>
) address each. Both run on the <span style="font-weight: bold;">single- and multi-material</span> terrain paths, up to 5 blended materials, and both are <span style="font-weight: bold;">gated to terrain only</span> (<div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>#if HEIGHTMAP &amp;&amp; SET_POS</code></div></div>
), so buildings, props, and characters are completely untouched.<br />
<br />
<span style="font-size: medium;"><span style="font-weight: bold;">1. Distance-based dual-scale: kills the far grid</span></span><br />
<br />
Sample the same ground texture at <span style="font-weight: bold;">two UV scales</span> and blend in the lower-frequency, larger-tile one with camera distance:<br />
<br />
<div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>tex = lerp(near, RTex(Col, uv * TileFarScale), fade(distance));</code></div></div>
<br />
Near the camera you get full detail. Far away, the tight repeat dissolves into a coarser, non-aligned pattern. This is the standard Unreal-landscape-style trick, and it is <span style="font-weight: bold;">mip-safe for free</span>, because the two samples use implicit derivatives, so no <div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>SampleGrad</code></div></div>
 is needed.<br />
<br />
<span style="font-size: medium;"><span style="font-weight: bold;">2. Stochastic “by-example”: kills the near/mid feature repeat</span></span><br />
<br />
This is <span style="font-weight: bold;">Heitz–Neyret’s by-example noise, minus the histogram LUT</span>, which means it needs <span style="font-weight: bold;">no precomputed assets</span> and no per-texture bake.<br />
<ul>
<li><span style="font-weight: bold;">Triangular / “hex” grid</span> — 3 samples, no axis-aligned blend seams.</li>
<li><span style="font-weight: bold;">Per-cell random offset only, no mirror</span> — the mirror in the cheaper grid variants is what causes those directional scratches/scuffs on grass. Dropping it removes them.</li>
<li><span style="font-weight: bold;">Variance-preserving blend</span> — naive averaging washes out contrast, so instead of <div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>sum(w_i * c_i)</code></div></div>
, we blend the deviation from the mean and renormalize by <div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>1 / sqrt(sum(w_i^2))</code></div></div>
. The mean is read from the texture’s <span style="font-weight: bold;">smallest mip</span>, approximately its average color, so we recover contrast with zero precomputation.</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>SampleGrad</code></div></div>
 keeps mipmapping correct.<br />
</li></ul>
<br />
The result: a single tiling texture stops repeating and reads as a natural, non-periodic surface — no scratches, no muddy blend blobs.<br />
<br />
<span style="font-size: medium;"><span style="font-weight: bold;">How to use it</span></span><br />
<br />
Everything is driven by a single <div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>TerrainTile</code></div></div>
 shader cbuffer, set from the public shader-param API:<br />
<br />
<div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>SPSet("TerrainTileEnable", 1.0f);&nbsp;&nbsp;// distance effect<br />
SPSet("TileFarScale", 0.22f);<br />
SPSet("TileFadeStart", 5.0f);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// ...TileFadeRange, TileFarMax<br />
<br />
SPSet("TerrainStochastic", 1.0f);&nbsp;&nbsp;// stochastic de-tiling</code></div></div>
<br />
<span style="font-weight: bold;">It is off by default.</span> The cbuffer is zero-initialized, so nothing changes unless your app sets the params. The editor and all other content render exactly as before.<br />
<br />
The params only exist once the terrain shader has loaded, so set them <span style="font-weight: bold;">each frame before drawing the world</span>. <div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>SPSet</code></div></div>
 safely no-ops until then. There is no engine-wide default yet; you opt in from your render loop.<br />
<br />
<span style="font-size: medium;"><span style="font-weight: bold;">Parameters</span></span><br />
<ul>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>TerrainTileEnable</code></div></div>
 — 0/1, distance dual-scale on/off.</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>TileFarScale</code></div></div>
 — coarse UV multiplier. Smaller means bigger far tiles and a stronger frequency break.</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>TileFadeStart</code></div></div>
 — meters: distance where the coarse blend begins.</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>TileFadeRange</code></div></div>
 — meters: distance over which it ramps to <div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>TileFarMax</code></div></div>
.</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>TileFarMax</code></div></div>
 — max fraction of the coarse sample blended in at far distance.</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>TerrainStochastic</code></div></div>
 — 0/1, stochastic de-tiling on/off.</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>StochMaxDist</code></div></div>
 — performance limit: only de-tile within this distance. <div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>&lt;=0</code></div></div>
 means no limit.</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>StochMinWeight</code></div></div>
 — performance limit: skip materials whose blend weight is below this.  means de-tile all.<br />
</li></ul>
<br />
<span style="font-size: medium;"><span style="font-weight: bold;">Performance limits: opt-in, runtime</span></span><br />
<br />
Multi-material stochastic can hit up to <span style="font-weight: bold;">5 materials × 4 taps = 20 albedo taps</span>, so there are two runtime knobs:<br />
<ul>
<li><span style="font-weight: bold;"><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>StochMaxDist</code></div></div>
</span> — only run the stochastic taps within this distance. The far grid is handled by the distance blend anyway, so far pixels drop straight back to 1 tap.</li>
<li><span style="font-weight: bold;"><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>StochMinWeight</code></div></div>
</span> — skip stochastic for a material whose per-pixel blend weight is below a threshold, so a barely-visible layer does not cost 4 taps.<br />
</li></ul>
<br />
Both default to “no practical limit.”<br />
<br />
<span style="font-size: medium;"><span style="font-weight: bold;">Large-world / floating-origin</span></span><br />
<br />
Terrain UVs are <span style="font-style: italic;">world-continuous</span>, built from the absolute area index, so far out in a big world they grow large.<br />
<br />
A <div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>Frac(sin(...))</code></div></div>
 hash loses precision there and the de-tiling starts aliasing, so the stochastic cell hash is a <span style="font-weight: bold;">PCG2D integer hash</span> on the integer cell ID, exact at any magnitude.<br />
<br />
The pattern stays locked to the ground and is transparent to floating-origin rebasing. UVs deliberately <span style="font-weight: bold;">do not</span> rebase. Shifting them would move the non-integer-aligned stochastic grid and pop the pattern every rebase.<br />
<br />
<span style="font-size: medium;"><span style="font-weight: bold;">Tutorials</span></span><br />
<br />
<span style="font-weight: bold;"><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>Tutorial_14_TerrainTiling</code></div></div>
</span><br />
<div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>Tutorials/Source/14 - Game Basics/36 - Terrain Tiling.cpp</code></div></div>
<br />
The live A/B demo. It loads a textured world and lets you toggle and tune everything in real time:<br />
<ul>
<li><span style="font-weight: bold;">T</span> — distance anti-tiling on/off.</li>
<li><span style="font-weight: bold;">Y</span> — stochastic de-tiling on/off.</li>
<li><span style="font-weight: bold;">1–8</span> — distance params: strength, fade start, fade range, coarse scale.</li>
<li><span style="font-weight: bold;">G / H</span> — stochastic max distance, for performance.</li>
<li><span style="font-weight: bold;">J / K</span> — stochastic min weight, for performance.</li>
<li><span style="font-weight: bold;">9</span> — reset all to defaults.</li>
<li>Hold <span style="font-weight: bold;">RMB</span> — look.</li>
<li><span style="font-weight: bold;">Mouse wheel</span> — zoom.</li>
<li><span style="font-weight: bold;">ESC</span> — quit.<br />
</li></ul>
<br />
The HUD shows every value live, and the <div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>EE_TILE*</code></div></div>
 env vars let you drive it headless for A/B captures:<br />
<br />
<div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>EE_TILE<br />
EE_TILE_STOCH<br />
EE_TILE_CAM_*<br />
EE_TILE_FARMAX<br />
EE_TILE_STOCHDIST</code></div></div>
<br />
Since the effect lives in the shader, it shows up on <span style="font-style: italic;">any</span> terrain. The other world tutorials are good places to see it on different content once an app enables the params:<br />
<ul>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>Tutorial_14_World</code></div></div>
</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>Tutorial_14_WorldWithCharacter</code></div></div>
</li>
<li><div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>Tutorial_14_ProceduralWorld</code></div></div>
</li></ul>
<br />
<span style="font-size: medium;"><span style="font-weight: bold;">Caveats / future work</span></span><br />
<ul>
<li>The only thing the <span style="font-weight: bold;">full Heitz histogram LUT</span> would add over this is slightly sharper blend zones. It carries a per-texture asset-bake cost, so it is left out. The variance-preserving approximation looks clean in practice.</li>
<li>One residual issue: the triangular-grid <span style="font-weight: bold;">barycentric weights</span> lose a little float precision at <span style="font-style: italic;">extreme</span> UV. The cell hash is exact, so the de-tiling holds; only the blend transitions get marginally coarser very far out.</li>
<li><span style="font-weight: bold;">Backends:</span> Vulkan and GL shader paks are regenerated. <span style="font-weight: bold;">DX still needs a Windows tree</span> to recompile, only tested on Linux+Vulkan.<br />
</li></ul>
<br />
in this repo: <a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[[Bug] TitanEditor EditMaterial::fixOldReflect makes old materials metallic → dark]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11825</link>
			<pubDate>Fri, 19 Jun 2026 02:33:33 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11825</guid>
			<description><![CDATA[I noticed when I would change any parameter of the "Path" material in the Tutorials in Titan Editor the material would immediately become darker.<br />
<br />
AI solved it, I'm not sure if this is because of some other change I made in the engine or not but here was the solution..<br />
<br />
[Bug] TitanEditor EditMaterial::fixOldReflect makes old materials metallic → renders dark (esp. on terrain)<br />
<br />
Summary<br />
<br />
When an old material (one saved with a single reflect value, before the reflect_min/reflect_max model) is loaded into TitanEditor, EditMaterial::fixOldReflect() can wrongly set reflect_max = 1, turning a dielectric material into a metallic one. On terrain (multi-material rendering) this makes the material render much darker than its texture, because the high reflectivity reads the packed metal channel and metals have ~no diffuse albedo.<br />
<br />
Affected code<br />
<br />
Editor/.../Shared/Elements/Material.cpp — EditMaterial::fixOldReflect(flt reflect):<br />
<br />
void EditMaterial::fixOldReflect(flt reflect)<br />
{<br />
   if(metal_map.is())<br />
   {<br />
      reflect_min=MATERIAL_REFLECT;<br />
      reflect_max=reflect;<br />
   }else<br />
   {<br />
      reflect_min=reflect;<br />
      reflect_max=1;      // &lt;-- bug<br />
   }<br />
}<br />
<br />
Introduced in commit 60929bf7708b2ff18e92032411928947bf4b74ca (2022-02-21), when the reflect_min/reflect_max model was added — so it's long-standing, not platform-specific.<br />
<br />
Root cause<br />
<br />
An old single-reflect material had constant reflectivity (it predates per-texture metalness). The no-metal-map branch sets reflect_max = 1, and on publish Material::reflect(min, max) computes:<br />
<br />
reflect_add = min;  reflect_mul = (1 - min) * max;<br />
<br />
So reflect(0.04, 1.0) → reflect_mul ≈ 0.96. The intent appears to be "harmless when there's no metal map," but the multi-material / terrain shader samples the metal channel of the packed base_2 (Ext) texture regardless of whether a separate metal_map was assigned. With reflect_mul ≈ 0.96, reflectivity becomes 0.04 + 0.96 × base_2.metal, i.e. metallic wherever that channel is bright → the surface renders dark. The engine's own old-material Material::loadData conversion, by contrast, uses reflect_mul = 0 (pure dielectric), so the editor was inconsistent with the engine.<br />
<br />
Observed effects / how it presents (very confusing!)<br />
<br />
- A material's tile preview looks correct/light (single-material path doesn't sample that metal channel), which masks the problem.<br />
- Painted onto terrain it renders dark, and it's per-material (paint the dark material anywhere → dark; the light one → light).<br />
- A raw copy of an old material stays correct (still the old single-reflect format, dielectric). Editing it (tweaking any slider) republishes it through fixOldReflect → it becomes metallic → goes dark and stays dark, even though the color (1,1,1,1) and all visible sliders look normal. The only tell is ReflectivityMax = 1.000 in the material editor.<br />
<br />
Reproduction<br />
<br />
1. Take an old-format material that has packed base textures and no separate metal_map, with low/constant reflectivity.<br />
2. Assign it to terrain — looks fine.<br />
3. Edit any slider (forces a republish through fixOldReflect).<br />
4. It renders noticeably darker on terrain; the tile preview still looks light.<br />
<br />
Fix<br />
<br />
Make the no-metal-map branch dielectric (matching the engine and how a normal dielectric material loads, where reflectMax() == 0):<br />
<br />
   }else<br />
   {<br />
      reflect_min=reflect;<br />
      reflect_max=0;   // dielectric: old single-'reflect' materials have constant reflectivity; 0 =&gt; reflect_mul=0 (matches Material::loadData)<br />
   }]]></description>
			<content:encoded><![CDATA[I noticed when I would change any parameter of the "Path" material in the Tutorials in Titan Editor the material would immediately become darker.<br />
<br />
AI solved it, I'm not sure if this is because of some other change I made in the engine or not but here was the solution..<br />
<br />
[Bug] TitanEditor EditMaterial::fixOldReflect makes old materials metallic → renders dark (esp. on terrain)<br />
<br />
Summary<br />
<br />
When an old material (one saved with a single reflect value, before the reflect_min/reflect_max model) is loaded into TitanEditor, EditMaterial::fixOldReflect() can wrongly set reflect_max = 1, turning a dielectric material into a metallic one. On terrain (multi-material rendering) this makes the material render much darker than its texture, because the high reflectivity reads the packed metal channel and metals have ~no diffuse albedo.<br />
<br />
Affected code<br />
<br />
Editor/.../Shared/Elements/Material.cpp — EditMaterial::fixOldReflect(flt reflect):<br />
<br />
void EditMaterial::fixOldReflect(flt reflect)<br />
{<br />
   if(metal_map.is())<br />
   {<br />
      reflect_min=MATERIAL_REFLECT;<br />
      reflect_max=reflect;<br />
   }else<br />
   {<br />
      reflect_min=reflect;<br />
      reflect_max=1;      // &lt;-- bug<br />
   }<br />
}<br />
<br />
Introduced in commit 60929bf7708b2ff18e92032411928947bf4b74ca (2022-02-21), when the reflect_min/reflect_max model was added — so it's long-standing, not platform-specific.<br />
<br />
Root cause<br />
<br />
An old single-reflect material had constant reflectivity (it predates per-texture metalness). The no-metal-map branch sets reflect_max = 1, and on publish Material::reflect(min, max) computes:<br />
<br />
reflect_add = min;  reflect_mul = (1 - min) * max;<br />
<br />
So reflect(0.04, 1.0) → reflect_mul ≈ 0.96. The intent appears to be "harmless when there's no metal map," but the multi-material / terrain shader samples the metal channel of the packed base_2 (Ext) texture regardless of whether a separate metal_map was assigned. With reflect_mul ≈ 0.96, reflectivity becomes 0.04 + 0.96 × base_2.metal, i.e. metallic wherever that channel is bright → the surface renders dark. The engine's own old-material Material::loadData conversion, by contrast, uses reflect_mul = 0 (pure dielectric), so the editor was inconsistent with the engine.<br />
<br />
Observed effects / how it presents (very confusing!)<br />
<br />
- A material's tile preview looks correct/light (single-material path doesn't sample that metal channel), which masks the problem.<br />
- Painted onto terrain it renders dark, and it's per-material (paint the dark material anywhere → dark; the light one → light).<br />
- A raw copy of an old material stays correct (still the old single-reflect format, dielectric). Editing it (tweaking any slider) republishes it through fixOldReflect → it becomes metallic → goes dark and stays dark, even though the color (1,1,1,1) and all visible sliders look normal. The only tell is ReflectivityMax = 1.000 in the material editor.<br />
<br />
Reproduction<br />
<br />
1. Take an old-format material that has packed base textures and no separate metal_map, with low/constant reflectivity.<br />
2. Assign it to terrain — looks fine.<br />
3. Edit any slider (forces a republish through fixOldReflect).<br />
4. It renders noticeably darker on terrain; the tile preview still looks light.<br />
<br />
Fix<br />
<br />
Make the no-metal-map branch dielectric (matching the engine and how a normal dielectric material loads, where reflectMax() == 0):<br />
<br />
   }else<br />
   {<br />
      reflect_min=reflect;<br />
      reflect_max=0;   // dielectric: old single-'reflect' materials have constant reflectivity; 0 =&gt; reflect_mul=0 (matches Material::loadData)<br />
   }]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Shader Cache, Azgaar Map Import,  Large Worlds Editor, Procedural Terrain, Portals]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11824</link>
			<pubDate>Sat, 13 Jun 2026 03:39:07 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11824</guid>
			<description><![CDATA[<span style="font-style: italic;">Please note I have only been testing Linux Vulkan RADV in my repo, not sure if any of this runs on other platforms/renderers/drivers.</span><br />
<br />
Added a large set of features focused on large-world support, procedural terrain, and streaming.<br />
<br />
Titan Editor can now import and edit blended worlds that combine procedural terrain with hand-authored tiled areas. A 250 km x 250 km procedural world is under 100 MB on disk, compared to over 3 TB for an equivalent fully tiled world at the same resolution.<br />
<br />
The engine now supports 64-bit large worlds through rebasing and tile-relative storage. Object rendering and PhysX simulation remain accurate and stable at world coordinates like 250,000,250,000 , just as they are near 0,0.<br />
<br />
Azgaar (<a href="https://azgaar.github.io/Fantasy-Map-Generator/" target="_blank">https://azgaar.github.io/Fantasy-Map-Generator/</a>) procedural map import now supports biome data, lake generation, river carving, and water mesh generation. Runtime procedural terrain also supports ring-LOD generation so distant terrain can stream and render efficiently.<br />
<br />
Titan Editor fully supports 64-bit worlds by rebasing the editor view and storing all relevant world data relative to tiles.<br />
<br />
Portal support was also added. Portals are placed through waypoints in Titan Editor and allow real-time movement to disconnected areas of the large world without a loading screen. As the player approaches a portal, the engine pre-streams tiled areas and generates procedural areas on the destination side so the transition is ready before crossing.<br />
<br />
<br />
<br />
AI Generated repo review of engine changes:<br />
<br />
<span style="font-size: 14pt;"><span style="font-weight: bold;">1. Linux-native shader toolchain + EERegen</span></span><br />
<ul>
<li>Vendored DXC v1.9.2602 on Linux, with SPIRV-Cross built from source. Vulkan and GL shader paks can now be regenerated natively on Linux without a Windows machine.</li>
<li><span style="font-weight: bold;">EERegen</span>: a single headless tool that performs shader compilation and Engine.pak / Editor.pak rebundling in one process. This replaces the old two-tool chain of shader-compile build + Engine Builder GUI.</li>
<li>Added a content-hash shader variant cache. Warm regen time dropped from roughly <span style="font-weight: bold;">40 minutes to about 30 seconds</span>. Editing one HLSL file now rebuilds only its affected variants.</li>
<li>Fixed a DXC RelaxedPrecision / OpPhi issue that was causing <span style="font-weight: bold;">544 compile failures</span> in Motion Blur and Temporal shaders.<br />
</li></ul>
<br />
<br />
<span style="font-size: 14pt;"><span style="font-weight: bold;">2. Compiler bug fixed: -fshort-wchar removed</span></span><br />
<br />
Clang's loop-idiom recognizer was rewriting the engine's string-length loop into a glibc wcslen call built for 32-bit wchar_t. This caused path truncation and memory over-reads at -O2.<br />
<br />
The -fshort-wchar flag was removed, which fixed the issue.<br />
<br />
<br />
<span style="font-size: 14pt;"><span style="font-weight: bold;">3. Vulkan backend fixes and improvements</span></span><br />
<br />
Fixed <span style="font-weight: bold;">15 tracked Vulkan issues</span>, including:<br />
<ul>
<li>Device loss from cube-texture views bound to 2D descriptor slots.</li>
<li>Crashes from in-flight buffer/image destruction, fixed with a new deferred-destruction system.</li>
<li>Scene corruption under heavy draw load caused by per-draw UBO ring wrapping. This was replaced with a byte-budget ring.</li>
<li>Eye-adaptation black frame caused by cross-stage image slot collisions.</li>
<li>Per-draw descriptor allocation was O(draws). Switched to dynamic UBO offsets.</li>
<li>Terrain “sparkle” caused by the BRDF LUT being deleted on every render-target reset.</li>
<li>Added per-pipeline descriptor sets.</li>
<li>Added a disk-persistent pipeline cache.</li>
<li>Reduced sync-validation errors from <span style="font-weight: bold;">10 to 0</span>.</li>
<li>Added env-gated abort-on-error debugging tools.<br />
</li></ul>
<br />
<br />
<span style="font-size: 14pt;"><span style="font-weight: bold;">4. Upstream Esenthel sync</span></span><br />
<br />
Merged upstream main back in, regenerated affected shader paks, and fixed a .gitattributes issue that was causing bogus whole-file merge conflicts.<br />
<br />
<br />
<span style="font-size: 14pt;"><span style="font-weight: bold;">5. Large World: 64-bit floating origin</span></span><br />
<ul>
<li>Added runtime floating-origin support in Game::World.</li>
<li>Added camera rebase support.</li>
<li>Added tile-relative area storage.</li>
<li>Added water, decal, and navmesh support for large worlds.</li>
<li>Added local-frame pathfinding wrappers.</li>
<li>Added editor support for large worlds, including jitter-free terrain editing and object placement far from origin.</li>
<li>Updated undo/redo so it survives rebasing.</li>
<li>Added world-coordinate display panels.</li>
<li>Added tile-relative game export.</li>
<li>Added an area relocate/convert tool for existing worlds.<br />
</li></ul>
<br />
<br />
<span style="font-size: 14pt;"><span style="font-weight: bold;">6. Procedural terrain + Azgaar world import</span></span><br />
<ul>
<li>Titan Editor supports Import of Azgaar Maps and seamless blending of Procedural+Tiled areas</li>
<li>Added runtime streamed procedural terrain generation.</li>
<li>Generation is multithreaded and prioritizes areas nearest the camera.</li>
<li>Added a far-LOD geo-clipmap backdrop ring so terrain reaches the horizon.</li>
<li>Added climate-driven biomes with per-biome terrain shapes: flat, rolling, rugged, and dunes.</li>
<li>Added three blended ground materials to reduce visible tiling.</li>
<li>Added terrain multi-texturing by slope and altitude.</li>
<li>Added object scatter for trees and rocks, including static collision.</li>
<li>Added sea level and ocean support.</li>
<li>Added rivers carved from Azgaar map data with mitred winding channels.</li>
<li>Added above-sea-level lake basins.</li>
<li>Water and terrain now render at full detail to the horizon.</li>
<li>Added Azgaar Fantasy Map Generator import. Heightmap and biome control maps drive the generated world.</li>
<li>Added an in-game minimap using M, with player position, facing arrow, and click-to-teleport.</li>
<li>Added editor tools: Procedural Terrain dialog, Biome Editor, and Rivers &amp; Lakes dialog.</li>
<li>Procedural config is included with the exported world.</li>
<li>Added per-area navmesh tiles for generated terrain.</li>
<li>Added a tutorial: <span style="font-weight: bold;">“Procedural World”</span>. 250 km x 250 km Azgaar Map Demo.<br />
</li></ul>
<br />
<br />
<span style="font-size: 14pt;"><span style="font-weight: bold;">7. Portals</span></span><br />
<ul>
<li>Added portal authoring in the editor.</li>
<li>Added runtime portal crossing.</li>
<li>Added a secondary-streaming-foci API so the destination side can pre-stream before the player crosses.</li>
<li>Added tutorial <span style="font-weight: bold;">“21 - Portals”</span> with a sample world.</li>
<li>Portal crossing is seamless, with no flash.</li>
<li>The streaming ring on the departed side is preserved during crossing.</li>
<li>Added a streaming-status HUD.<br />
</li></ul>
<br />
<br />
<span style="font-size: 14pt;"><span style="font-weight: bold;">Honorable mentions</span></span><br />
<ul>
<li>RmlUi improvements: icon: texture loading from the engine image cache, plus same-texture draw batching.</li>
<li>Fixed stale input-action state on popped or masked input maps.</li>
<li>Added memory hardening from ASAN runs, including out-of-bounds guards and completion of the global new/delete family.</li>
<li>Made sizeof(Image) renderer-independent.<br />
</li></ul>
<br />
Source is in this fork: <a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a><br />
<br />
<a href="https://postimg.cc/QHx7vK6v" target="_blank"><img src="https://i.postimg.cc/wTDkyhDx/Screenshot-From-2026-06-12-21-24-05.png" border="0" alt="[Image: Screenshot-From-2026-06-12-21-24-05.png]" /></a><br />
<br />
<a href="https://postimg.cc/fJ2dsWpW" target="_blank"><img src="https://i.postimg.cc/XY3kwpg5/Screenshot-From-2026-06-12-21-24-39.png" border="0" alt="[Image: Screenshot-From-2026-06-12-21-24-39.png]" /></a><br />
<br />
<a href="https://postimg.cc/w7fNzxZ9" target="_blank"><img src="https://i.postimg.cc/cLZBQrct/Screenshot-From-2026-06-12-21-25-50.png" border="0" alt="[Image: Screenshot-From-2026-06-12-21-25-50.png]" /></a><br />
<br />
<a href="https://postimg.cc/fJ2dsWpR" target="_blank"><img src="https://i.postimg.cc/9QVYGrBZ/Screenshot-From-2026-06-12-21-27-49.png" border="0" alt="[Image: Screenshot-From-2026-06-12-21-27-49.png]" /></a><br />
<br />
<a href="https://postimg.cc/PP3DHf9r" target="_blank"><img src="https://i.postimg.cc/6pwfdyhR/Screenshot-From-2026-06-12-21-28-00.png" border="0" alt="[Image: Screenshot-From-2026-06-12-21-28-00.png]" /></a><br />
<br />
<a href="https://postimg.cc/sv6WzfqW" target="_blank"><img src="https://i.postimg.cc/13PrpfKv/Screenshot-From-2026-06-12-23-08-05.png" border="0" alt="[Image: Screenshot-From-2026-06-12-23-08-05.png]" /></a><br />
<br />
<a href="https://postimg.cc/1fCFSR2w" target="_blank"><img src="https://i.postimg.cc/YCkz1hxn/Screenshot-From-2026-06-12-23-08-23.png" border="0" alt="[Image: Screenshot-From-2026-06-12-23-08-23.png]" /></a><br />
<br />
<a href="https://postimg.cc/qzbKrBSG" target="_blank"><img src="https://i.postimg.cc/8zDmhjbx/Screenshot-From-2026-06-12-23-09-07.png" border="0" alt="[Image: Screenshot-From-2026-06-12-23-09-07.png]" /></a>]]></description>
			<content:encoded><![CDATA[<span style="font-style: italic;">Please note I have only been testing Linux Vulkan RADV in my repo, not sure if any of this runs on other platforms/renderers/drivers.</span><br />
<br />
Added a large set of features focused on large-world support, procedural terrain, and streaming.<br />
<br />
Titan Editor can now import and edit blended worlds that combine procedural terrain with hand-authored tiled areas. A 250 km x 250 km procedural world is under 100 MB on disk, compared to over 3 TB for an equivalent fully tiled world at the same resolution.<br />
<br />
The engine now supports 64-bit large worlds through rebasing and tile-relative storage. Object rendering and PhysX simulation remain accurate and stable at world coordinates like 250,000,250,000 , just as they are near 0,0.<br />
<br />
Azgaar (<a href="https://azgaar.github.io/Fantasy-Map-Generator/" target="_blank">https://azgaar.github.io/Fantasy-Map-Generator/</a>) procedural map import now supports biome data, lake generation, river carving, and water mesh generation. Runtime procedural terrain also supports ring-LOD generation so distant terrain can stream and render efficiently.<br />
<br />
Titan Editor fully supports 64-bit worlds by rebasing the editor view and storing all relevant world data relative to tiles.<br />
<br />
Portal support was also added. Portals are placed through waypoints in Titan Editor and allow real-time movement to disconnected areas of the large world without a loading screen. As the player approaches a portal, the engine pre-streams tiled areas and generates procedural areas on the destination side so the transition is ready before crossing.<br />
<br />
<br />
<br />
AI Generated repo review of engine changes:<br />
<br />
<span style="font-size: 14pt;"><span style="font-weight: bold;">1. Linux-native shader toolchain + EERegen</span></span><br />
<ul>
<li>Vendored DXC v1.9.2602 on Linux, with SPIRV-Cross built from source. Vulkan and GL shader paks can now be regenerated natively on Linux without a Windows machine.</li>
<li><span style="font-weight: bold;">EERegen</span>: a single headless tool that performs shader compilation and Engine.pak / Editor.pak rebundling in one process. This replaces the old two-tool chain of shader-compile build + Engine Builder GUI.</li>
<li>Added a content-hash shader variant cache. Warm regen time dropped from roughly <span style="font-weight: bold;">40 minutes to about 30 seconds</span>. Editing one HLSL file now rebuilds only its affected variants.</li>
<li>Fixed a DXC RelaxedPrecision / OpPhi issue that was causing <span style="font-weight: bold;">544 compile failures</span> in Motion Blur and Temporal shaders.<br />
</li></ul>
<br />
<br />
<span style="font-size: 14pt;"><span style="font-weight: bold;">2. Compiler bug fixed: -fshort-wchar removed</span></span><br />
<br />
Clang's loop-idiom recognizer was rewriting the engine's string-length loop into a glibc wcslen call built for 32-bit wchar_t. This caused path truncation and memory over-reads at -O2.<br />
<br />
The -fshort-wchar flag was removed, which fixed the issue.<br />
<br />
<br />
<span style="font-size: 14pt;"><span style="font-weight: bold;">3. Vulkan backend fixes and improvements</span></span><br />
<br />
Fixed <span style="font-weight: bold;">15 tracked Vulkan issues</span>, including:<br />
<ul>
<li>Device loss from cube-texture views bound to 2D descriptor slots.</li>
<li>Crashes from in-flight buffer/image destruction, fixed with a new deferred-destruction system.</li>
<li>Scene corruption under heavy draw load caused by per-draw UBO ring wrapping. This was replaced with a byte-budget ring.</li>
<li>Eye-adaptation black frame caused by cross-stage image slot collisions.</li>
<li>Per-draw descriptor allocation was O(draws). Switched to dynamic UBO offsets.</li>
<li>Terrain “sparkle” caused by the BRDF LUT being deleted on every render-target reset.</li>
<li>Added per-pipeline descriptor sets.</li>
<li>Added a disk-persistent pipeline cache.</li>
<li>Reduced sync-validation errors from <span style="font-weight: bold;">10 to 0</span>.</li>
<li>Added env-gated abort-on-error debugging tools.<br />
</li></ul>
<br />
<br />
<span style="font-size: 14pt;"><span style="font-weight: bold;">4. Upstream Esenthel sync</span></span><br />
<br />
Merged upstream main back in, regenerated affected shader paks, and fixed a .gitattributes issue that was causing bogus whole-file merge conflicts.<br />
<br />
<br />
<span style="font-size: 14pt;"><span style="font-weight: bold;">5. Large World: 64-bit floating origin</span></span><br />
<ul>
<li>Added runtime floating-origin support in Game::World.</li>
<li>Added camera rebase support.</li>
<li>Added tile-relative area storage.</li>
<li>Added water, decal, and navmesh support for large worlds.</li>
<li>Added local-frame pathfinding wrappers.</li>
<li>Added editor support for large worlds, including jitter-free terrain editing and object placement far from origin.</li>
<li>Updated undo/redo so it survives rebasing.</li>
<li>Added world-coordinate display panels.</li>
<li>Added tile-relative game export.</li>
<li>Added an area relocate/convert tool for existing worlds.<br />
</li></ul>
<br />
<br />
<span style="font-size: 14pt;"><span style="font-weight: bold;">6. Procedural terrain + Azgaar world import</span></span><br />
<ul>
<li>Titan Editor supports Import of Azgaar Maps and seamless blending of Procedural+Tiled areas</li>
<li>Added runtime streamed procedural terrain generation.</li>
<li>Generation is multithreaded and prioritizes areas nearest the camera.</li>
<li>Added a far-LOD geo-clipmap backdrop ring so terrain reaches the horizon.</li>
<li>Added climate-driven biomes with per-biome terrain shapes: flat, rolling, rugged, and dunes.</li>
<li>Added three blended ground materials to reduce visible tiling.</li>
<li>Added terrain multi-texturing by slope and altitude.</li>
<li>Added object scatter for trees and rocks, including static collision.</li>
<li>Added sea level and ocean support.</li>
<li>Added rivers carved from Azgaar map data with mitred winding channels.</li>
<li>Added above-sea-level lake basins.</li>
<li>Water and terrain now render at full detail to the horizon.</li>
<li>Added Azgaar Fantasy Map Generator import. Heightmap and biome control maps drive the generated world.</li>
<li>Added an in-game minimap using M, with player position, facing arrow, and click-to-teleport.</li>
<li>Added editor tools: Procedural Terrain dialog, Biome Editor, and Rivers &amp; Lakes dialog.</li>
<li>Procedural config is included with the exported world.</li>
<li>Added per-area navmesh tiles for generated terrain.</li>
<li>Added a tutorial: <span style="font-weight: bold;">“Procedural World”</span>. 250 km x 250 km Azgaar Map Demo.<br />
</li></ul>
<br />
<br />
<span style="font-size: 14pt;"><span style="font-weight: bold;">7. Portals</span></span><br />
<ul>
<li>Added portal authoring in the editor.</li>
<li>Added runtime portal crossing.</li>
<li>Added a secondary-streaming-foci API so the destination side can pre-stream before the player crosses.</li>
<li>Added tutorial <span style="font-weight: bold;">“21 - Portals”</span> with a sample world.</li>
<li>Portal crossing is seamless, with no flash.</li>
<li>The streaming ring on the departed side is preserved during crossing.</li>
<li>Added a streaming-status HUD.<br />
</li></ul>
<br />
<br />
<span style="font-size: 14pt;"><span style="font-weight: bold;">Honorable mentions</span></span><br />
<ul>
<li>RmlUi improvements: icon: texture loading from the engine image cache, plus same-texture draw batching.</li>
<li>Fixed stale input-action state on popped or masked input maps.</li>
<li>Added memory hardening from ASAN runs, including out-of-bounds guards and completion of the global new/delete family.</li>
<li>Made sizeof(Image) renderer-independent.<br />
</li></ul>
<br />
Source is in this fork: <a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a><br />
<br />
<a href="https://postimg.cc/QHx7vK6v" target="_blank"><img src="https://i.postimg.cc/wTDkyhDx/Screenshot-From-2026-06-12-21-24-05.png" border="0" alt="[Image: Screenshot-From-2026-06-12-21-24-05.png]" /></a><br />
<br />
<a href="https://postimg.cc/fJ2dsWpW" target="_blank"><img src="https://i.postimg.cc/XY3kwpg5/Screenshot-From-2026-06-12-21-24-39.png" border="0" alt="[Image: Screenshot-From-2026-06-12-21-24-39.png]" /></a><br />
<br />
<a href="https://postimg.cc/w7fNzxZ9" target="_blank"><img src="https://i.postimg.cc/cLZBQrct/Screenshot-From-2026-06-12-21-25-50.png" border="0" alt="[Image: Screenshot-From-2026-06-12-21-25-50.png]" /></a><br />
<br />
<a href="https://postimg.cc/fJ2dsWpR" target="_blank"><img src="https://i.postimg.cc/9QVYGrBZ/Screenshot-From-2026-06-12-21-27-49.png" border="0" alt="[Image: Screenshot-From-2026-06-12-21-27-49.png]" /></a><br />
<br />
<a href="https://postimg.cc/PP3DHf9r" target="_blank"><img src="https://i.postimg.cc/6pwfdyhR/Screenshot-From-2026-06-12-21-28-00.png" border="0" alt="[Image: Screenshot-From-2026-06-12-21-28-00.png]" /></a><br />
<br />
<a href="https://postimg.cc/sv6WzfqW" target="_blank"><img src="https://i.postimg.cc/13PrpfKv/Screenshot-From-2026-06-12-23-08-05.png" border="0" alt="[Image: Screenshot-From-2026-06-12-23-08-05.png]" /></a><br />
<br />
<a href="https://postimg.cc/1fCFSR2w" target="_blank"><img src="https://i.postimg.cc/YCkz1hxn/Screenshot-From-2026-06-12-23-08-23.png" border="0" alt="[Image: Screenshot-From-2026-06-12-23-08-23.png]" /></a><br />
<br />
<a href="https://postimg.cc/qzbKrBSG" target="_blank"><img src="https://i.postimg.cc/8zDmhjbx/Screenshot-From-2026-06-12-23-09-07.png" border="0" alt="[Image: Screenshot-From-2026-06-12-23-09-07.png]" /></a>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[June 2026]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11823</link>
			<pubDate>Tue, 02 Jun 2026 06:01:04 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11823</guid>
			<description><![CDATA[Updated Esenthel Source:<br />
-updated Sound system to operate on VecD positions (64-bit float for high precision huge worlds)]]></description>
			<content:encoded><![CDATA[Updated Esenthel Source:<br />
-updated Sound system to operate on VecD positions (64-bit float for high precision huge worlds)]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[[UI Editor] Feature requests]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11822</link>
			<pubDate>Thu, 28 May 2026 09:41:25 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11822</guid>
			<description><![CDATA[Hi,<br />
<br />
I'd like to propose two additions to the GUI editor window:<br />
<br />
<span style="font-weight: bold;">1. Multi-Selection Editing</span><br />
When multiple UI elements are selected, params that are unique per element (Name, Description, and Text) should be greyed out and non-editable. Params that are common across the selection should remain editable. Where selected elements have differing values for a shared param, display "–" as a placeholder to indicate the values are not uniform amongst the selected elements<br />
<br />
This would significantly reduce friction when working on complex interfaces. Currently, when adjusting shared properties like scale or size requires selecting all elements, applying the change, deselecting, then selecting an individual element to verify if the param is correct, and repeating. Multi-selection editing would eliminate that process.<br />
<br />
<span style="font-weight: bold;">2. Configurable Insert Templates for UI Elements</span><br />
Add a right-click context menu to entries in the standard UI element list. This menu would open a template configuration window where the default parameters for that element type can be set (width, height, text alignment, text size, and skin override).<br />
<br />
Buttons are currently inserted with a default width of 0.270, height of 0.060, text alignment 0, text size 1, and an empty skin override. These defaults probably won't match the design of a custom button, meaning every newly placed button requires manual adjustment (although the skin could be configured in an application element). With configurable templates, a developer could define defaults that match their project's design once, rather than correcting values on each new element placed in different GUI's.<br />
<br />
Thanks for considering these! Would love to hear feedback!]]></description>
			<content:encoded><![CDATA[Hi,<br />
<br />
I'd like to propose two additions to the GUI editor window:<br />
<br />
<span style="font-weight: bold;">1. Multi-Selection Editing</span><br />
When multiple UI elements are selected, params that are unique per element (Name, Description, and Text) should be greyed out and non-editable. Params that are common across the selection should remain editable. Where selected elements have differing values for a shared param, display "–" as a placeholder to indicate the values are not uniform amongst the selected elements<br />
<br />
This would significantly reduce friction when working on complex interfaces. Currently, when adjusting shared properties like scale or size requires selecting all elements, applying the change, deselecting, then selecting an individual element to verify if the param is correct, and repeating. Multi-selection editing would eliminate that process.<br />
<br />
<span style="font-weight: bold;">2. Configurable Insert Templates for UI Elements</span><br />
Add a right-click context menu to entries in the standard UI element list. This menu would open a template configuration window where the default parameters for that element type can be set (width, height, text alignment, text size, and skin override).<br />
<br />
Buttons are currently inserted with a default width of 0.270, height of 0.060, text alignment 0, text size 1, and an empty skin override. These defaults probably won't match the design of a custom button, meaning every newly placed button requires manual adjustment (although the skin could be configured in an application element). With configurable templates, a developer could define defaults that match their project's design once, rather than correcting values on each new element placed in different GUI's.<br />
<br />
Thanks for considering these! Would love to hear feedback!]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[May 2026]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11819</link>
			<pubDate>Tue, 05 May 2026 05:03:20 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11819</guid>
			<description><![CDATA[Updated Esenthel Source:<br />
-fixed physics tutorials not compiling due to updated Box constructor<br />
-added more Edge2 functions<br />
-"Behind" shader now correctly processes "Blend" Materials]]></description>
			<content:encoded><![CDATA[Updated Esenthel Source:<br />
-fixed physics tutorials not compiling due to updated Box constructor<br />
-added more Edge2 functions<br />
-"Behind" shader now correctly processes "Blend" Materials]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Lag compensation]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11816</link>
			<pubDate>Wed, 22 Apr 2026 01:28:27 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11816</guid>
			<description><![CDATA[I had Claude Code improve and bring into the engine the lag compensation I use in my project, it makes FPS and Chivalry/Mount and Blade weapon trail swing games playable and fair even with some clients having 200+ ms pings (overseas players). I use ENET in my case but I kept it transport layer agnostic here.<br />
<br />
<span style="font-weight: bold;">Networking: Lag Compensation + Physics Rollback</span><br />
<span style="font-style: italic;">Transport-agnostic server-authoritative simulation layer (lag-compensation branch)</span><br />
<br />
A commercial-grade networking substrate built from scratch under *Engine/Net/*. Two independent tiers of temporal correction, built using shipped-game references:<br />
<ul>
<li><span style="font-weight: bold;">Tier 1 — selective hitbox rewind</span> (Photon Fusion 2 / Source / CS:GO model). Each server tick, every replicated actor contributes a flat list of hitbox samples into a 256-tick ring buffer (~4.27 s @ 60 Hz). Spatial queries take a *(tick, frac)* pair, linearly interpolate the two bracketing snapshots, and test against the interpolated hitboxes. The live gameplay scene is never touched — perfect for hitscan / melee where you only need “where was this limb when the client fired”.</li>
<li><span style="font-weight: bold;">Tier 2 — full PhysX rewind + resimulation</span> (UE5 networked-physics model). Snapshots pose / linear + angular velocity / kinematic / sleeping flags for every tracked *Actor* each tick; on demand restores them and drives *Physics.stepOnce(dt)* forward to catch up to the current tick. A *RollbackGuard* (TLS depth counter) is held active for the resim window so gameplay / animation / audio code that checks *RollbackGuard::active()* can suppress side-effects (event fire, notifies, SFX) during replay.<br />
</li></ul>
<br />
All modules are <span style="font-weight: bold;">unconditionally compiled</span> — no CMake flag, no opt-out, no extra link cost (the dependency surface is zero — pure C++, no third-party libraries). The transport layer is <span style="font-weight: bold;">abstract</span>: the engine ships *LoopbackTransport* (in-process queue) and *FakeLatencyTransport* (wraps any transport with configurable latency / jitter / loss), plus sketches for *ENetTransport* and a multi-hop *ForwardingTransport* in *Engine/H/Net/NetTransport.h* header comments. User projects subclass *INetTransport* and forward to ENet / Steam Sockets / WebRTC / whatever fits their deployment.<br />
<br />
<span style="font-weight: bold;">Module layout:</span><br />
<ul>
<li>*Net/NetTick.h/cpp* — *TickSeq* (*ULong*) + *NetTick{seq, frac}* shared tick + sub-tick type</li>
<li>*Net/NetClock.h/cpp* — NTP-style clock sync; rolling 5-sample median-of-offset + RTT + jitter; monotonic server timeline</li>
<li>*Net/NetTransport.h/cpp* — *INetTransport* abstract + *LoopbackTransport* + *FakeLatencyTransport* + ENet / multi-hop sketches</li>
<li>*Net/NetObject.h/cpp* — *INetReplicated* interface, *NetBase* convenience, *NET_REGISTER_CLASS* factory, *HitboxSample*, *NetWriter* / *NetReader* byte-buffer codecs with opt-in *putUIntPacked* (varint) / *putPosQ16* (quantized position, 6 B vs 12 B) compression helpers</li>
<li>*Net/InputCommand.h/cpp* — *InputCommand* POD + *InputBuffer* (client redundancy — re-sends last N unacked cmds) + *InputQueue* (server per-connection, wrap-safe *UShort* seq compare)</li>
<li>*Net/NetReplicator.h/cpp* — spawn / despawn / snapshot build + apply / input routing / ping-pong / per-connection relevance callback hook</li>
<li>*Net/LagCompensation.h/cpp* — <span style="font-weight: bold;">Tier 1</span> 256-tick hitbox ring; *ray* / *sweep* / *sweepArc* / *overlap* / *fetchInterp* CPU-analytic queries (sphere + capsule + box) with *(tick, frac)* OR *(real_time)* addressing for clustered servers</li>
<li>*Net/RollbackGuard.h/cpp* — thread-local depth counter; *RollbackGuard::active()* callable from gameplay / anim code</li>
<li>*Net/NetRollback.h/cpp* — <span style="font-weight: bold;">Tier 2</span> snapshot / restore *Actor* state, *Physics.stepOnce*-driven resim, 8-tick default cap (user-configurable), *InputReplayFn* hook, *AnimCaptureFn* / *AnimRestoreFn* hooks for bone-sourced hitboxes<br />
</li></ul>
<br />
~3,200 lines of new engine code across 18 files, zero third-party links, zero *#ifdef*s outside platform branches.<br />
<br />
<span style="font-weight: bold;">Quick start (single-process, both sides in one *Update()* — this is how every category-20 tutorial is structured):</span><br />
<br />
<div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>// --- Replicated actor ---<br />
struct Target : NetBase<br />
{<br />
Vec pos;<br />
NetClassID netClassId()C override { return 0x54475431u; /* 'TGT1' */ }<br />
void netSerialize&nbsp;&nbsp;(NetWriter &amp;w, UInt fields)C override { w.putVec(pos); }<br />
void netDeserialize(NetReader &amp;r, UInt fields)&nbsp;&nbsp;override { pos = r.getVec(); }<br />
void netHitboxes(Memc&lt;HitboxSample&gt; &amp;out)C override<br />
{<br />
HitboxSample &amp;s = out.New();<br />
s.shape = HITBOX_SPHERE;<br />
s.group = 0x0001;<br />
s.half_ext.set(0.3f, 0.3f, 0.3f);<br />
s.xform.identity(); s.xform.pos = pos;<br />
}<br />
};<br />
NET_REGISTER_CLASS(Target, 0x54475431u);<br />
<br />
// --- Server ---<br />
NetReplicator SvrRep;&nbsp;&nbsp;NetClock SvrClk;&nbsp;&nbsp;LagComp SvrLag;<br />
SvrClk.initServer(60);<br />
SvrRep.serverInit(&amp;transport, &amp;SvrClk);<br />
<br />
each_tick:<br />
SvrRep.serverDrainInputs();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // apply client inputs via INetReplicated::netApplyInput<br />
// ...gameplay simulation...<br />
SvrLag.recordTick(SvrRep, tick, Time.curTime());<br />
if(tick % 3 == 0) SvrRep.sendSnapshots(tick);&nbsp;&nbsp; // 20 Hz @ 60 tick<br />
<br />
on_client_fire(from, to, client_tick):<br />
LagHit h;<br />
if(SvrLag.ray(client_tick, 0.0f, from, to, h))<br />
apply_damage(h.owner, h.owner_idx);<br />
<br />
// --- Client ---<br />
NetReplicator CliRep;&nbsp;&nbsp;NetClock CliClk;&nbsp;&nbsp;InputBuffer CliInputs;<br />
CliClk.initClient(60);<br />
CliRep.clientInit(&amp;transport, &amp;CliClk, /*server_conn*/1);<br />
<br />
each_frame:<br />
CliRep.sendPingMaybe();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // periodic NTP sync<br />
CliRep.update(Time.curTime());&nbsp;&nbsp;&nbsp;&nbsp;// drains spawn/despawn/snapshot/pong<br />
Vec input = sample_keyboard();<br />
pawn-&gt;predict(input);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // local prediction on owned pawn<br />
CliInputs.push(input, buttons, CliClk.tick());<br />
CliInputs.sendTo(CliRep);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // redundant — includes last N unacked<br />
CliInputs.ack(CliRep.lastAckedSeq());<br />
<br />
on_fire(from, to):<br />
// Ship the compensation tick with the fire cmd so the server can rewind<br />
// to exactly what this client was seeing.<br />
fire_cmd.compensation_tick = CliRep.lastSnapshotTick();</code></div></div>
<br />
<span style="font-weight: bold;">Tier 2 (physics rewind) — only when you actually need resim; most games won't:</span><br />
<br />
<span style="font-style: italic;">NetRollback SvrRoll;</span><br />
<span style="font-style: italic;">SvrRoll.init(1.0f/60.0f, /*max_resim=*/8);</span><br />
<span style="font-style: italic;">for(Actor *a : dynamic_physics_actors) SvrRoll.track(*a);</span><br />
<br />
<span style="font-style: italic;">on_physics_step_completed(tick):</span><br />
<span style="font-style: italic;">SvrRoll.recordTick(tick);</span><br />
<br />
<span style="font-style: italic;">on_authoritative_correction(target_tick):</span><br />
<span style="font-style: italic;">SvrRoll.rewindAndResimulate(target_tick, currentTick, replayInputs, user);</span><br />
<br />
<span style="font-weight: bold;">Physics engine touch points</span> — two small, surgical additions; everything else is pure add-on:<br />
<ul>
<li>*Physics.stepOnce(Flt dt)* — direct *simulate* + *fetchResults* with no accumulator, no *simulation_step_completed* callback. This is what drives Tier 2 resim. Safe only between frames (after *stopSimulation()* or inside *WorldManager::physics_update()*).</li>
<li>*Time.rollbackDt(Flt dt)* — thread-local override for *Time.d()*. When *&gt; 0*, *Time.d()* returns it instead of the real frame delta. Set automatically by *NetRollback::rewindAndResimulate* per resim tick to the recorded *dt_used*, so animation code (*Motion::updateAuto*, *AnimatedSkeleton::animate*) replays at the exact dt the forward sim used — deterministic bone poses during rewind.<br />
</li></ul>
<br />
<span style="font-weight: bold;">Animation sync hooks for bone-driven hitboxes</span> — if your game's hitboxes are attached to *AnimatedSkeleton* bones (limbs parented to bone slots), both forward-sim animation and rollback-resim animation must reproduce the same per-tick bone poses. Two engine hooks pair them:<br />
<br />
<ol type="1">
<li><span style="font-weight: bold;">Forward sim</span> — set *Time.rollbackDt(icf.timeDeltaPhysics)* before animating each player tick, so *Motion::updateAuto* uses the physics dt instead of the variable frame dt. Restore with *Time.rollbackDt(0)*.</li>
<li><span style="font-weight: bold;">Rollback resim</span> — pass *NetRollback::setAnimCallbacks(cap, rest, user)*. *AnimCaptureFn* serialises every tracked *Motion* (via *Motion::save* or direct field writes) into a small opaque blob per tick (~64 B per motion × N motions × 256 ticks — fits easily). *AnimRestoreFn* does the inverse <span style="font-weight: bold;">before</span> the user's *InputReplayFn* fires, so replayed animation code starts from the correct *Motion.time* / *Motion.blend*. Pair this with *if(RollbackGuard::active()) return;* at the top of anim-event consumers (weapon clash SFX, hit sparks) so they don't re-fire during replay.<br />
</li></ol>
<br />
<br />
Includes 7 more tutorials.<br />
<a href="https://postimg.cc/hhh408RZ" target="_blank"><img src="https://i.postimg.cc/gcHwm4Sp/Screenshot-from-2026-04-21-21-12-54.png" border="0" alt="[Image: Screenshot-from-2026-04-21-21-12-54.png]" /></a><br />
<br />
in this fork:<br />
<a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a>]]></description>
			<content:encoded><![CDATA[I had Claude Code improve and bring into the engine the lag compensation I use in my project, it makes FPS and Chivalry/Mount and Blade weapon trail swing games playable and fair even with some clients having 200+ ms pings (overseas players). I use ENET in my case but I kept it transport layer agnostic here.<br />
<br />
<span style="font-weight: bold;">Networking: Lag Compensation + Physics Rollback</span><br />
<span style="font-style: italic;">Transport-agnostic server-authoritative simulation layer (lag-compensation branch)</span><br />
<br />
A commercial-grade networking substrate built from scratch under *Engine/Net/*. Two independent tiers of temporal correction, built using shipped-game references:<br />
<ul>
<li><span style="font-weight: bold;">Tier 1 — selective hitbox rewind</span> (Photon Fusion 2 / Source / CS:GO model). Each server tick, every replicated actor contributes a flat list of hitbox samples into a 256-tick ring buffer (~4.27 s @ 60 Hz). Spatial queries take a *(tick, frac)* pair, linearly interpolate the two bracketing snapshots, and test against the interpolated hitboxes. The live gameplay scene is never touched — perfect for hitscan / melee where you only need “where was this limb when the client fired”.</li>
<li><span style="font-weight: bold;">Tier 2 — full PhysX rewind + resimulation</span> (UE5 networked-physics model). Snapshots pose / linear + angular velocity / kinematic / sleeping flags for every tracked *Actor* each tick; on demand restores them and drives *Physics.stepOnce(dt)* forward to catch up to the current tick. A *RollbackGuard* (TLS depth counter) is held active for the resim window so gameplay / animation / audio code that checks *RollbackGuard::active()* can suppress side-effects (event fire, notifies, SFX) during replay.<br />
</li></ul>
<br />
All modules are <span style="font-weight: bold;">unconditionally compiled</span> — no CMake flag, no opt-out, no extra link cost (the dependency surface is zero — pure C++, no third-party libraries). The transport layer is <span style="font-weight: bold;">abstract</span>: the engine ships *LoopbackTransport* (in-process queue) and *FakeLatencyTransport* (wraps any transport with configurable latency / jitter / loss), plus sketches for *ENetTransport* and a multi-hop *ForwardingTransport* in *Engine/H/Net/NetTransport.h* header comments. User projects subclass *INetTransport* and forward to ENet / Steam Sockets / WebRTC / whatever fits their deployment.<br />
<br />
<span style="font-weight: bold;">Module layout:</span><br />
<ul>
<li>*Net/NetTick.h/cpp* — *TickSeq* (*ULong*) + *NetTick{seq, frac}* shared tick + sub-tick type</li>
<li>*Net/NetClock.h/cpp* — NTP-style clock sync; rolling 5-sample median-of-offset + RTT + jitter; monotonic server timeline</li>
<li>*Net/NetTransport.h/cpp* — *INetTransport* abstract + *LoopbackTransport* + *FakeLatencyTransport* + ENet / multi-hop sketches</li>
<li>*Net/NetObject.h/cpp* — *INetReplicated* interface, *NetBase* convenience, *NET_REGISTER_CLASS* factory, *HitboxSample*, *NetWriter* / *NetReader* byte-buffer codecs with opt-in *putUIntPacked* (varint) / *putPosQ16* (quantized position, 6 B vs 12 B) compression helpers</li>
<li>*Net/InputCommand.h/cpp* — *InputCommand* POD + *InputBuffer* (client redundancy — re-sends last N unacked cmds) + *InputQueue* (server per-connection, wrap-safe *UShort* seq compare)</li>
<li>*Net/NetReplicator.h/cpp* — spawn / despawn / snapshot build + apply / input routing / ping-pong / per-connection relevance callback hook</li>
<li>*Net/LagCompensation.h/cpp* — <span style="font-weight: bold;">Tier 1</span> 256-tick hitbox ring; *ray* / *sweep* / *sweepArc* / *overlap* / *fetchInterp* CPU-analytic queries (sphere + capsule + box) with *(tick, frac)* OR *(real_time)* addressing for clustered servers</li>
<li>*Net/RollbackGuard.h/cpp* — thread-local depth counter; *RollbackGuard::active()* callable from gameplay / anim code</li>
<li>*Net/NetRollback.h/cpp* — <span style="font-weight: bold;">Tier 2</span> snapshot / restore *Actor* state, *Physics.stepOnce*-driven resim, 8-tick default cap (user-configurable), *InputReplayFn* hook, *AnimCaptureFn* / *AnimRestoreFn* hooks for bone-sourced hitboxes<br />
</li></ul>
<br />
~3,200 lines of new engine code across 18 files, zero third-party links, zero *#ifdef*s outside platform branches.<br />
<br />
<span style="font-weight: bold;">Quick start (single-process, both sides in one *Update()* — this is how every category-20 tutorial is structured):</span><br />
<br />
<div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>// --- Replicated actor ---<br />
struct Target : NetBase<br />
{<br />
Vec pos;<br />
NetClassID netClassId()C override { return 0x54475431u; /* 'TGT1' */ }<br />
void netSerialize&nbsp;&nbsp;(NetWriter &amp;w, UInt fields)C override { w.putVec(pos); }<br />
void netDeserialize(NetReader &amp;r, UInt fields)&nbsp;&nbsp;override { pos = r.getVec(); }<br />
void netHitboxes(Memc&lt;HitboxSample&gt; &amp;out)C override<br />
{<br />
HitboxSample &amp;s = out.New();<br />
s.shape = HITBOX_SPHERE;<br />
s.group = 0x0001;<br />
s.half_ext.set(0.3f, 0.3f, 0.3f);<br />
s.xform.identity(); s.xform.pos = pos;<br />
}<br />
};<br />
NET_REGISTER_CLASS(Target, 0x54475431u);<br />
<br />
// --- Server ---<br />
NetReplicator SvrRep;&nbsp;&nbsp;NetClock SvrClk;&nbsp;&nbsp;LagComp SvrLag;<br />
SvrClk.initServer(60);<br />
SvrRep.serverInit(&amp;transport, &amp;SvrClk);<br />
<br />
each_tick:<br />
SvrRep.serverDrainInputs();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // apply client inputs via INetReplicated::netApplyInput<br />
// ...gameplay simulation...<br />
SvrLag.recordTick(SvrRep, tick, Time.curTime());<br />
if(tick % 3 == 0) SvrRep.sendSnapshots(tick);&nbsp;&nbsp; // 20 Hz @ 60 tick<br />
<br />
on_client_fire(from, to, client_tick):<br />
LagHit h;<br />
if(SvrLag.ray(client_tick, 0.0f, from, to, h))<br />
apply_damage(h.owner, h.owner_idx);<br />
<br />
// --- Client ---<br />
NetReplicator CliRep;&nbsp;&nbsp;NetClock CliClk;&nbsp;&nbsp;InputBuffer CliInputs;<br />
CliClk.initClient(60);<br />
CliRep.clientInit(&amp;transport, &amp;CliClk, /*server_conn*/1);<br />
<br />
each_frame:<br />
CliRep.sendPingMaybe();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // periodic NTP sync<br />
CliRep.update(Time.curTime());&nbsp;&nbsp;&nbsp;&nbsp;// drains spawn/despawn/snapshot/pong<br />
Vec input = sample_keyboard();<br />
pawn-&gt;predict(input);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // local prediction on owned pawn<br />
CliInputs.push(input, buttons, CliClk.tick());<br />
CliInputs.sendTo(CliRep);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // redundant — includes last N unacked<br />
CliInputs.ack(CliRep.lastAckedSeq());<br />
<br />
on_fire(from, to):<br />
// Ship the compensation tick with the fire cmd so the server can rewind<br />
// to exactly what this client was seeing.<br />
fire_cmd.compensation_tick = CliRep.lastSnapshotTick();</code></div></div>
<br />
<span style="font-weight: bold;">Tier 2 (physics rewind) — only when you actually need resim; most games won't:</span><br />
<br />
<span style="font-style: italic;">NetRollback SvrRoll;</span><br />
<span style="font-style: italic;">SvrRoll.init(1.0f/60.0f, /*max_resim=*/8);</span><br />
<span style="font-style: italic;">for(Actor *a : dynamic_physics_actors) SvrRoll.track(*a);</span><br />
<br />
<span style="font-style: italic;">on_physics_step_completed(tick):</span><br />
<span style="font-style: italic;">SvrRoll.recordTick(tick);</span><br />
<br />
<span style="font-style: italic;">on_authoritative_correction(target_tick):</span><br />
<span style="font-style: italic;">SvrRoll.rewindAndResimulate(target_tick, currentTick, replayInputs, user);</span><br />
<br />
<span style="font-weight: bold;">Physics engine touch points</span> — two small, surgical additions; everything else is pure add-on:<br />
<ul>
<li>*Physics.stepOnce(Flt dt)* — direct *simulate* + *fetchResults* with no accumulator, no *simulation_step_completed* callback. This is what drives Tier 2 resim. Safe only between frames (after *stopSimulation()* or inside *WorldManager::physics_update()*).</li>
<li>*Time.rollbackDt(Flt dt)* — thread-local override for *Time.d()*. When *&gt; 0*, *Time.d()* returns it instead of the real frame delta. Set automatically by *NetRollback::rewindAndResimulate* per resim tick to the recorded *dt_used*, so animation code (*Motion::updateAuto*, *AnimatedSkeleton::animate*) replays at the exact dt the forward sim used — deterministic bone poses during rewind.<br />
</li></ul>
<br />
<span style="font-weight: bold;">Animation sync hooks for bone-driven hitboxes</span> — if your game's hitboxes are attached to *AnimatedSkeleton* bones (limbs parented to bone slots), both forward-sim animation and rollback-resim animation must reproduce the same per-tick bone poses. Two engine hooks pair them:<br />
<br />
<ol type="1">
<li><span style="font-weight: bold;">Forward sim</span> — set *Time.rollbackDt(icf.timeDeltaPhysics)* before animating each player tick, so *Motion::updateAuto* uses the physics dt instead of the variable frame dt. Restore with *Time.rollbackDt(0)*.</li>
<li><span style="font-weight: bold;">Rollback resim</span> — pass *NetRollback::setAnimCallbacks(cap, rest, user)*. *AnimCaptureFn* serialises every tracked *Motion* (via *Motion::save* or direct field writes) into a small opaque blob per tick (~64 B per motion × N motions × 256 ticks — fits easily). *AnimRestoreFn* does the inverse <span style="font-weight: bold;">before</span> the user's *InputReplayFn* fires, so replayed animation code starts from the correct *Motion.time* / *Motion.blend*. Pair this with *if(RollbackGuard::active()) return;* at the top of anim-event consumers (weapon clash SFX, hit sparks) so they don't re-fire during replay.<br />
</li></ol>
<br />
<br />
Includes 7 more tutorials.<br />
<a href="https://postimg.cc/hhh408RZ" target="_blank"><img src="https://i.postimg.cc/gcHwm4Sp/Screenshot-from-2026-04-21-21-12-54.png" border="0" alt="[Image: Screenshot-from-2026-04-21-21-12-54.png]" /></a><br />
<br />
in this fork:<br />
<a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[RmlUi]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11815</link>
			<pubDate>Mon, 20 Apr 2026 13:58:58 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11815</guid>
			<description><![CDATA[Claude Code was able to one-shot RmlUi integration into Esenthel.<br />
<br />
I'm not a web-dev person, but with Vibe Designing coming soon<br />
( e.g. <a href="https://claude.ai/design" target="_blank">https://claude.ai/design</a> ) , I think there is value in having a UI<br />
system defined by a declarative language like HTML/CSS.<br />
<br />
This also makes it much easier for users to mod or skin the UI if they<br />
want. But really, it's about making it easier for the AI to do it.<br />
Claude Code really seems to struggle with building Esenthel GUIs<br />
programmatically that look good and have the correct font size, etc.,<br />
but with HTML/CSS this problem doesn't exist.<br />
<br />
I haven't profiled this, the implementation has not been optimized,<br />
and I'm sure there is a performance penalty. That said, RmlUi is<br />
highly regarded and is designed for games; it is not a full WebKit.<br />
<br />
An integration of <span style="font-weight: bold;"><a href="https://github.com/mikke89/RmlUi" target="_blank">RmlUi</a> v6.2</span> (MIT) as<br />
a retained-mode HTML/CSS UI library that <span style="font-weight: bold;">coexists</span> with Esenthel's built-in<br />
<span style="font-style: italic;">Gui</span> system without touching it. Both run in the same frame — the native<br />
widget set is still available for lightweight 3D-integrated controls, while<br />
RmlUi handles HTML/CSS/animation-heavy UI (menus, HUDs, settings screens,<br />
chat windows). A single RmlUi render backend lives inside the Engine static<br />
library and drives RmlUi's vertex stream through the engine's existing 2D<br />
batcher, so every renderer backend (DX11, DX12, Vulkan, OpenGL) gets RmlUi<br />
for free with zero backend-specific code.<br />
<br />
Game HUD tutorial<br />
<a href="https://pixeldrain.com/api/file/nWMwv7tq" target="_blank"><img src="https://pixeldrain.com/api/file/nWMwv7tq" width="800" height="339" border="0" alt="[Image: nWMwv7tq]" /></a><br />
<br />
<br />
Using Esenthel GUI elements and RmlUi elements side-by-side tutorial<br />
<a href="https://pixeldrain.com/api/file/3cB1Nk65" target="_blank"><img src="https://pixeldrain.com/api/file/3cB1Nk65" width="800" height="339" border="0" alt="[Image: 3cB1Nk65]" /></a><br />
<br />
<br />
<span style="font-weight: bold;">Quick start:</span><br />
<br />
<div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>#include "Gui/RmlUi.h"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// EE::RmlUi::* facade<br />
<br />
// In a single .cpp where you author Rml event listeners, DOM manipulation,<br />
// etc. — wrap the RmlUi includes so Esenthel macros and X11 defines don't<br />
// collide with RmlUi's template parameters and enum names:<br />
#include "../../ThirdPartyLibs/begin.h"<br />
#undef T1&nbsp;&nbsp;#undef T2&nbsp;&nbsp;#undef T3&nbsp;&nbsp;#undef T4&nbsp;&nbsp;#undef T5<br />
#undef T6&nbsp;&nbsp;#undef T7&nbsp;&nbsp;#undef T8&nbsp;&nbsp;#undef T9&nbsp;&nbsp;#undef T10&nbsp;&nbsp;#undef T11<br />
#undef Always&nbsp;&nbsp;#undef NotUseful<br />
#include &lt;RmlUi/Core.h&gt;<br />
#include "../../ThirdPartyLibs/end.h"<br />
<br />
static Rml::Context&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *Ctx=null;<br />
static Rml::ElementDocument *Doc=null;<br />
<br />
Bool Init()<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;EE::RmlUi::Init();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // install render + system interfaces<br />
&nbsp;&nbsp;&nbsp;&nbsp;EE::RmlUi::LoadFontFace("Data/RmlUi/LatoLatin-Regular.ttf");<br />
&nbsp;&nbsp;&nbsp;&nbsp;Ctx=EE::RmlUi::CreateContext();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// sized to D.resW()/D.resH()<br />
&nbsp;&nbsp;&nbsp;&nbsp;Doc=Ctx-&gt;LoadDocument("Data/RmlUi/demo.rml");<br />
&nbsp;&nbsp;&nbsp;&nbsp;if(Doc)Doc-&gt;Show();<br />
&nbsp;&nbsp;&nbsp;&nbsp;return true;<br />
}<br />
void Shut()&nbsp;&nbsp; { EE::RmlUi::Shut(); }<br />
Bool Update() {<br />
&nbsp;&nbsp;&nbsp;&nbsp;if(Kb.bp(KB_ESC))return false;<br />
&nbsp;&nbsp;&nbsp;&nbsp;Gui.update();<br />
&nbsp;&nbsp;&nbsp;&nbsp;EE::RmlUi::PumpInput(Ctx);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Ms/Kb -&gt; context<br />
&nbsp;&nbsp;&nbsp;&nbsp;EE::RmlUi::Update&nbsp;&nbsp; (Ctx);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // events, animations, dirty geometry<br />
&nbsp;&nbsp;&nbsp;&nbsp;return true;<br />
}<br />
void Draw()&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp;Renderer(RenderScene);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // optional 3D world<br />
&nbsp;&nbsp;&nbsp;&nbsp;Gui.draw();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Esenthel Gui above 3D<br />
&nbsp;&nbsp;&nbsp;&nbsp;EE::RmlUi::Render(Ctx);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// RmlUi on top of everything<br />
}</code></div></div>
<br />
<span style="font-weight: bold;">Shipped features:</span><br />
<ul>
<li><span style="font-weight: bold;">Single-TU backend</span> — <span style="font-style: italic;">Engine/Source/Gui/RmlUi.cpp</span> is the only engine<br />
source file that includes RmlUi headers. It subclasses<br />
<span style="font-style: italic;">Rml::RenderInterface</span> and <span style="font-style: italic;">Rml::SystemInterface</span>, owns the 1×1 white<br />
fallback texture, and exports the <span style="font-style: italic;">EE::RmlUi::</span> facade used by app code.<br />
Macro pollution is neutralised locally: Esenthel's template-shortcut<br />
macros <span style="font-style: italic;">T1..T11</span> (from <span style="font-style: italic;">Engine/H/_/defines.h</span>, they collide with<br />
<span style="font-style: italic;">robin_hood.h</span>'s template parameters) and X11's <span style="font-style: italic;">Always</span> / <span style="font-style: italic;">NotUseful</span><br />
(not stripped by <span style="font-style: italic;">Engine/H/_/headers.h</span>) are undefined inside the<br />
<span style="font-style: italic;">ThirdPartyLibs/begin.h</span> / <span style="font-style: italic;">end.h</span> bracket just for this one TU.</li>
<li><span style="font-weight: bold;">API-agnostic rendering</span> — <span style="font-style: italic;">RenderGeometry</span> feeds RmlUi's pixel-space<br />
vertex stream into the engine's existing 2D path. Per-vertex<br />
conversion goes through <span style="font-style: italic;">D.pixelToScreen(Vec2)</span> (canonical helper; no<br />
hand-rolled Y-flip). <span style="font-style: italic;">D.alpha(ALPHA_MERGE)</span> handles RmlUi's<br />
premultiplied RGBA (which it emits since v5). Scissor goes through<br />
<span style="font-style: italic;">D.clip(&amp;engine_rect)</span> / <span style="font-style: italic;">D.pixelToScreen(RectI)</span>. Works identically on<br />
DX11, DX12, Vulkan, OpenGL.</li>
<li><span style="font-weight: bold;">Untextured-path fallback</span> — RmlUi calls <span style="font-style: italic;">RenderGeometry</span> with<br />
<span style="font-style: italic;">texture=0</span> for solid-colour geometry. The backend binds a pre-built<br />
1×1 opaque-white <span style="font-style: italic;">Image</span> so the textured shader path effectively<br />
degenerates to plain vertex colour. This avoids needing a separate<br />
colour-only code path.</li>
<li><span style="font-weight: bold;">FreeType sharing</span> — RmlUi's default FreeType font engine is pointed<br />
at Esenthel's prebuilt <span style="font-style: italic;">EE_FreeType</span> static lib via<br />
<span style="font-style: italic;">FREETYPE_INCLUDE_DIRS</span> / <span style="font-style: italic;">FREETYPE_LIBRARY</span> cache vars in<br />
<span style="font-style: italic;">ThirdPartyLibs/RmlUi/CMakeLists.txt</span>, so one copy of FreeType ships in<br />
the final link. <span style="font-style: italic;">EE_RMLUI_USE_BUNDLED_FREETYPE=ON</span> is an escape hatch<br />
if the prebuilt's config flags ever diverge from what RmlUi expects.</li>
<li><span style="font-weight: bold;">Texture upload</span> — <span style="font-style: italic;">LoadTexture</span> routes to <span style="font-style: italic;">Image.Import</span>, accepting<br />
every format Esenthel's asset pipeline supports (BMP/PNG/JPG/JXL/WEBP/<br />
AVIF/HEIF/TGA/TIF/DDS/PSD/ICO/HDR). <span style="font-style: italic;">GenerateTexture</span> uploads raw<br />
RGBA8 via a soft image, lock/write, row-copy, unlock, and GPU upload<br />
(used for the font atlas and procedural decorator textures).</li>
<li><span style="font-weight: bold;">Dual-mode input pump</span> — <span style="font-style: italic;">EE::RmlUi::PumpInput(ctx)</span> polls<br />
mouse position, mouse buttons, and wheel every frame, walks all<br />
256 <span style="font-style: italic;">KB_KEY</span> values firing key down/up events on edge changes, and<br />
pumps text characters out of <span style="font-style: italic;">Kb.k.c</span>. When the focused RmlUi element<br />
is an input field or textarea the key queue is drained so fast typing<br />
never drops characters; otherwise the head of the queue is peeked<br />
non-destructively so the built-in Gui still sees the same keys on the<br />
same frame.</li>
<li><span style="font-weight: bold;">Opt-in via CMake</span> — <span style="font-style: italic;">option(EE_RMLUI "... " ON)</span> in the root<br />
<span style="font-style: italic;">CMakeLists.txt</span>, default ON on Linux x64 + Windows x64, fatal on iOS<br />
/ Android (deferred until those platforms are tested). With<br />
<span style="font-style: italic;">EE_RMLUI=OFF</span> every integration TU is empty, the<br />
<span style="font-style: italic;">rmlui_core</span> / <span style="font-style: italic;">rmlui_debugger</span> targets are not added to<br />
the build graph, and the tutorial targets are skipped in<br />
<span style="font-style: italic;">Tutorials/CMakeLists.txt</span>.</li>
<li><span style="font-weight: bold;">Built from source</span> — RmlUi v6.2 is vendored at<br />
<span style="font-style: italic;">ThirdPartyLibs/RmlUi/lib/</span> and compiled via subdirectory inclusion so<br />
<span style="font-style: italic;">rmlui_core</span> + <span style="font-style: italic;">rmlui_debugger</span> become Engine interface link deps.<br />
No prebuilt library like the other third-parties, because upstream<br />
ships clean CMake and the library is small. ABI-visible flags are<br />
forced onto both targets so they match Engine's ABI.</li>
<li><span style="font-weight: bold;">Debugger overlay</span> — <span style="font-style: italic;">rmlui_debugger</span> is linked automatically when<br />
<span style="font-style: italic;">EE_RMLUI=ON</span>. Call <span style="font-style: italic;">Rml::Debugger::Initialise(ctx)</span> once, then<br />
<span style="font-style: italic;">Rml::Debugger::SetVisible(true)</span> (or bind to a key) to pop up the live<br />
inspector — Event Log, Element Info, Outlines, Data Models tabs.</li>
<li><span style="font-weight: bold;">Thin facade</span> — <span style="font-style: italic;">Engine/H/Gui/RmlUi.h</span> exposes only the<br />
<span style="font-style: italic;">EE::RmlUi::</span> lifecycle and helper functions. It forward-declares<br />
<span style="font-style: italic;">Rml::Context</span>, <span style="font-style: italic;">ElementDocument</span>, <span style="font-style: italic;">RenderInterface</span>, and<br />
<span style="font-style: italic;">SystemInterface</span>, so app translation units that just drive lifecycle<br />
do not pay to parse all of <span style="font-style: italic;">&amp;lt;RmlUi/Core.h&amp;gt;</span>. App translation units<br />
that want event listeners, DOM mutation, or typed element access can<br />
include <span style="font-style: italic;">&amp;lt;RmlUi/Core.h&amp;gt;</span> themselves.<br />
</li></ul>
<br />
<span style="font-weight: bold;">Non-invasive integration</span> — zero changes to <span style="font-style: italic;">Engine/Source/Gui/Gui.cpp</span>,<br />
<span style="font-style: italic;">Gui.draw</span>, <span style="font-style: italic;">Gui.update</span>, no new state in <span style="font-style: italic;">DisplayClass</span>, no new<br />
alpha mode, no new shaders. The pre-existing <span style="font-style: italic;">VI</span> + <span style="font-style: italic;">D</span> 2D path is the<br />
only rendering surface used; the existing 2D shaders are the only ones<br />
touched. The tutorial harness (<span style="font-style: italic;">Tutorials/TutorialAuto.{h,cpp}</span>,<br />
<span style="font-style: italic;">Tutorials/stdafx.h</span>) is unchanged. <span style="font-style: italic;">EE_RMLUI=OFF</span> builds are<br />
pixel-identical to pre-integration builds (verified against<br />
<span style="font-style: italic;">Tutorial_05_Bars</span>, <span style="font-style: italic;">Tutorial_04_BatchedDrawing</span>,<br />
<span style="font-style: italic;">Tutorial_12_RenderToTexture</span>).<br />
<br />
<span style="font-weight: bold;">RCSS gotcha</span> — RmlUi's transition/animation parser recognises tween<br />
names as defined in <span style="font-style: italic;">Source/Core/PropertyParserAnimation.cpp</span>. Unlike<br />
standard CSS, it does <span style="font-weight: bold;">not</span> accept bare <span style="font-style: italic;">linear</span> or the CSS keyword<br />
<span style="font-style: italic;">ease</span>; use <span style="font-style: italic;">linear-in-out</span> and <span style="font-style: italic;">cubic-in-out</span> (or any of the<br />
supported <span style="font-style: italic;">{back,bounce,circular,cubic,elastic,exponential,linear,quadratic,quartic,quintic&#8203;,sine}-{in,out,in-out}</span><br />
variants). Multi-transitions are comma-separated.<br />
<br />
<span style="font-weight: bold;">Deferred work:</span><br />
<ul>
<li>Custom <span style="font-style: italic;">Rml::FontEngineInterface</span> on top of Esenthel's <span style="font-style: italic;">Font</span> class, so<br />
<span style="font-style: italic;">.pak</span>-packaged fonts could feed RmlUi. Upstream's default FreeType<br />
engine is sufficient for now; TTF files ship alongside the tutorials.</li>
<li><span style="font-style: italic;">Rml::FileInterface</span> on <span style="font-style: italic;">EE::File</span> — would let RmlUi resolve<br />
<span style="font-style: italic;">.rml</span> / <span style="font-style: italic;">.rcss</span> / texture paths through engine <span style="font-style: italic;">.pak</span> archives. The<br />
stdio <span style="font-style: italic;">fopen</span> default is used currently; tutorials ship plain files<br />
under <span style="font-style: italic;">Tutorials/Data/RmlUi/</span>.</li>
<li>Data Binding (MVC layer via <span style="font-style: italic;">Rml::DataModelConstructor</span>) — supported by<br />
the linked <span style="font-style: italic;">rmlui_core</span> but not yet demonstrated by a tutorial.</li>
<li>Lua bindings (<span style="font-style: italic;">rmlui_lua</span>) — disabled in the wrapper (<span style="font-style: italic;">RMLUI_LUA_BINDINGS=OFF</span>);<br />
enabling would require wiring an Esenthel Lua runtime into the engine<br />
first.</li>
<li>Windows smoke sweep via <span style="font-style: italic;">run_smoke_test.ps1</span>. Linux is covered<br />
(all five tutorials + three regression targets pass).<br />
</li></ul>
<br />
Available in this repo:<br />
<a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a>]]></description>
			<content:encoded><![CDATA[Claude Code was able to one-shot RmlUi integration into Esenthel.<br />
<br />
I'm not a web-dev person, but with Vibe Designing coming soon<br />
( e.g. <a href="https://claude.ai/design" target="_blank">https://claude.ai/design</a> ) , I think there is value in having a UI<br />
system defined by a declarative language like HTML/CSS.<br />
<br />
This also makes it much easier for users to mod or skin the UI if they<br />
want. But really, it's about making it easier for the AI to do it.<br />
Claude Code really seems to struggle with building Esenthel GUIs<br />
programmatically that look good and have the correct font size, etc.,<br />
but with HTML/CSS this problem doesn't exist.<br />
<br />
I haven't profiled this, the implementation has not been optimized,<br />
and I'm sure there is a performance penalty. That said, RmlUi is<br />
highly regarded and is designed for games; it is not a full WebKit.<br />
<br />
An integration of <span style="font-weight: bold;"><a href="https://github.com/mikke89/RmlUi" target="_blank">RmlUi</a> v6.2</span> (MIT) as<br />
a retained-mode HTML/CSS UI library that <span style="font-weight: bold;">coexists</span> with Esenthel's built-in<br />
<span style="font-style: italic;">Gui</span> system without touching it. Both run in the same frame — the native<br />
widget set is still available for lightweight 3D-integrated controls, while<br />
RmlUi handles HTML/CSS/animation-heavy UI (menus, HUDs, settings screens,<br />
chat windows). A single RmlUi render backend lives inside the Engine static<br />
library and drives RmlUi's vertex stream through the engine's existing 2D<br />
batcher, so every renderer backend (DX11, DX12, Vulkan, OpenGL) gets RmlUi<br />
for free with zero backend-specific code.<br />
<br />
Game HUD tutorial<br />
<a href="https://pixeldrain.com/api/file/nWMwv7tq" target="_blank"><img src="https://pixeldrain.com/api/file/nWMwv7tq" width="800" height="339" border="0" alt="[Image: nWMwv7tq]" /></a><br />
<br />
<br />
Using Esenthel GUI elements and RmlUi elements side-by-side tutorial<br />
<a href="https://pixeldrain.com/api/file/3cB1Nk65" target="_blank"><img src="https://pixeldrain.com/api/file/3cB1Nk65" width="800" height="339" border="0" alt="[Image: 3cB1Nk65]" /></a><br />
<br />
<br />
<span style="font-weight: bold;">Quick start:</span><br />
<br />
<div class="codeblock">
<div class="title">Code:<br />
</div><div class="body" dir="ltr"><code>#include "Gui/RmlUi.h"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// EE::RmlUi::* facade<br />
<br />
// In a single .cpp where you author Rml event listeners, DOM manipulation,<br />
// etc. — wrap the RmlUi includes so Esenthel macros and X11 defines don't<br />
// collide with RmlUi's template parameters and enum names:<br />
#include "../../ThirdPartyLibs/begin.h"<br />
#undef T1&nbsp;&nbsp;#undef T2&nbsp;&nbsp;#undef T3&nbsp;&nbsp;#undef T4&nbsp;&nbsp;#undef T5<br />
#undef T6&nbsp;&nbsp;#undef T7&nbsp;&nbsp;#undef T8&nbsp;&nbsp;#undef T9&nbsp;&nbsp;#undef T10&nbsp;&nbsp;#undef T11<br />
#undef Always&nbsp;&nbsp;#undef NotUseful<br />
#include &lt;RmlUi/Core.h&gt;<br />
#include "../../ThirdPartyLibs/end.h"<br />
<br />
static Rml::Context&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *Ctx=null;<br />
static Rml::ElementDocument *Doc=null;<br />
<br />
Bool Init()<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;EE::RmlUi::Init();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // install render + system interfaces<br />
&nbsp;&nbsp;&nbsp;&nbsp;EE::RmlUi::LoadFontFace("Data/RmlUi/LatoLatin-Regular.ttf");<br />
&nbsp;&nbsp;&nbsp;&nbsp;Ctx=EE::RmlUi::CreateContext();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// sized to D.resW()/D.resH()<br />
&nbsp;&nbsp;&nbsp;&nbsp;Doc=Ctx-&gt;LoadDocument("Data/RmlUi/demo.rml");<br />
&nbsp;&nbsp;&nbsp;&nbsp;if(Doc)Doc-&gt;Show();<br />
&nbsp;&nbsp;&nbsp;&nbsp;return true;<br />
}<br />
void Shut()&nbsp;&nbsp; { EE::RmlUi::Shut(); }<br />
Bool Update() {<br />
&nbsp;&nbsp;&nbsp;&nbsp;if(Kb.bp(KB_ESC))return false;<br />
&nbsp;&nbsp;&nbsp;&nbsp;Gui.update();<br />
&nbsp;&nbsp;&nbsp;&nbsp;EE::RmlUi::PumpInput(Ctx);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Ms/Kb -&gt; context<br />
&nbsp;&nbsp;&nbsp;&nbsp;EE::RmlUi::Update&nbsp;&nbsp; (Ctx);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // events, animations, dirty geometry<br />
&nbsp;&nbsp;&nbsp;&nbsp;return true;<br />
}<br />
void Draw()&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp;&nbsp;Renderer(RenderScene);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // optional 3D world<br />
&nbsp;&nbsp;&nbsp;&nbsp;Gui.draw();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Esenthel Gui above 3D<br />
&nbsp;&nbsp;&nbsp;&nbsp;EE::RmlUi::Render(Ctx);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// RmlUi on top of everything<br />
}</code></div></div>
<br />
<span style="font-weight: bold;">Shipped features:</span><br />
<ul>
<li><span style="font-weight: bold;">Single-TU backend</span> — <span style="font-style: italic;">Engine/Source/Gui/RmlUi.cpp</span> is the only engine<br />
source file that includes RmlUi headers. It subclasses<br />
<span style="font-style: italic;">Rml::RenderInterface</span> and <span style="font-style: italic;">Rml::SystemInterface</span>, owns the 1×1 white<br />
fallback texture, and exports the <span style="font-style: italic;">EE::RmlUi::</span> facade used by app code.<br />
Macro pollution is neutralised locally: Esenthel's template-shortcut<br />
macros <span style="font-style: italic;">T1..T11</span> (from <span style="font-style: italic;">Engine/H/_/defines.h</span>, they collide with<br />
<span style="font-style: italic;">robin_hood.h</span>'s template parameters) and X11's <span style="font-style: italic;">Always</span> / <span style="font-style: italic;">NotUseful</span><br />
(not stripped by <span style="font-style: italic;">Engine/H/_/headers.h</span>) are undefined inside the<br />
<span style="font-style: italic;">ThirdPartyLibs/begin.h</span> / <span style="font-style: italic;">end.h</span> bracket just for this one TU.</li>
<li><span style="font-weight: bold;">API-agnostic rendering</span> — <span style="font-style: italic;">RenderGeometry</span> feeds RmlUi's pixel-space<br />
vertex stream into the engine's existing 2D path. Per-vertex<br />
conversion goes through <span style="font-style: italic;">D.pixelToScreen(Vec2)</span> (canonical helper; no<br />
hand-rolled Y-flip). <span style="font-style: italic;">D.alpha(ALPHA_MERGE)</span> handles RmlUi's<br />
premultiplied RGBA (which it emits since v5). Scissor goes through<br />
<span style="font-style: italic;">D.clip(&amp;engine_rect)</span> / <span style="font-style: italic;">D.pixelToScreen(RectI)</span>. Works identically on<br />
DX11, DX12, Vulkan, OpenGL.</li>
<li><span style="font-weight: bold;">Untextured-path fallback</span> — RmlUi calls <span style="font-style: italic;">RenderGeometry</span> with<br />
<span style="font-style: italic;">texture=0</span> for solid-colour geometry. The backend binds a pre-built<br />
1×1 opaque-white <span style="font-style: italic;">Image</span> so the textured shader path effectively<br />
degenerates to plain vertex colour. This avoids needing a separate<br />
colour-only code path.</li>
<li><span style="font-weight: bold;">FreeType sharing</span> — RmlUi's default FreeType font engine is pointed<br />
at Esenthel's prebuilt <span style="font-style: italic;">EE_FreeType</span> static lib via<br />
<span style="font-style: italic;">FREETYPE_INCLUDE_DIRS</span> / <span style="font-style: italic;">FREETYPE_LIBRARY</span> cache vars in<br />
<span style="font-style: italic;">ThirdPartyLibs/RmlUi/CMakeLists.txt</span>, so one copy of FreeType ships in<br />
the final link. <span style="font-style: italic;">EE_RMLUI_USE_BUNDLED_FREETYPE=ON</span> is an escape hatch<br />
if the prebuilt's config flags ever diverge from what RmlUi expects.</li>
<li><span style="font-weight: bold;">Texture upload</span> — <span style="font-style: italic;">LoadTexture</span> routes to <span style="font-style: italic;">Image.Import</span>, accepting<br />
every format Esenthel's asset pipeline supports (BMP/PNG/JPG/JXL/WEBP/<br />
AVIF/HEIF/TGA/TIF/DDS/PSD/ICO/HDR). <span style="font-style: italic;">GenerateTexture</span> uploads raw<br />
RGBA8 via a soft image, lock/write, row-copy, unlock, and GPU upload<br />
(used for the font atlas and procedural decorator textures).</li>
<li><span style="font-weight: bold;">Dual-mode input pump</span> — <span style="font-style: italic;">EE::RmlUi::PumpInput(ctx)</span> polls<br />
mouse position, mouse buttons, and wheel every frame, walks all<br />
256 <span style="font-style: italic;">KB_KEY</span> values firing key down/up events on edge changes, and<br />
pumps text characters out of <span style="font-style: italic;">Kb.k.c</span>. When the focused RmlUi element<br />
is an input field or textarea the key queue is drained so fast typing<br />
never drops characters; otherwise the head of the queue is peeked<br />
non-destructively so the built-in Gui still sees the same keys on the<br />
same frame.</li>
<li><span style="font-weight: bold;">Opt-in via CMake</span> — <span style="font-style: italic;">option(EE_RMLUI "... " ON)</span> in the root<br />
<span style="font-style: italic;">CMakeLists.txt</span>, default ON on Linux x64 + Windows x64, fatal on iOS<br />
/ Android (deferred until those platforms are tested). With<br />
<span style="font-style: italic;">EE_RMLUI=OFF</span> every integration TU is empty, the<br />
<span style="font-style: italic;">rmlui_core</span> / <span style="font-style: italic;">rmlui_debugger</span> targets are not added to<br />
the build graph, and the tutorial targets are skipped in<br />
<span style="font-style: italic;">Tutorials/CMakeLists.txt</span>.</li>
<li><span style="font-weight: bold;">Built from source</span> — RmlUi v6.2 is vendored at<br />
<span style="font-style: italic;">ThirdPartyLibs/RmlUi/lib/</span> and compiled via subdirectory inclusion so<br />
<span style="font-style: italic;">rmlui_core</span> + <span style="font-style: italic;">rmlui_debugger</span> become Engine interface link deps.<br />
No prebuilt library like the other third-parties, because upstream<br />
ships clean CMake and the library is small. ABI-visible flags are<br />
forced onto both targets so they match Engine's ABI.</li>
<li><span style="font-weight: bold;">Debugger overlay</span> — <span style="font-style: italic;">rmlui_debugger</span> is linked automatically when<br />
<span style="font-style: italic;">EE_RMLUI=ON</span>. Call <span style="font-style: italic;">Rml::Debugger::Initialise(ctx)</span> once, then<br />
<span style="font-style: italic;">Rml::Debugger::SetVisible(true)</span> (or bind to a key) to pop up the live<br />
inspector — Event Log, Element Info, Outlines, Data Models tabs.</li>
<li><span style="font-weight: bold;">Thin facade</span> — <span style="font-style: italic;">Engine/H/Gui/RmlUi.h</span> exposes only the<br />
<span style="font-style: italic;">EE::RmlUi::</span> lifecycle and helper functions. It forward-declares<br />
<span style="font-style: italic;">Rml::Context</span>, <span style="font-style: italic;">ElementDocument</span>, <span style="font-style: italic;">RenderInterface</span>, and<br />
<span style="font-style: italic;">SystemInterface</span>, so app translation units that just drive lifecycle<br />
do not pay to parse all of <span style="font-style: italic;">&amp;lt;RmlUi/Core.h&amp;gt;</span>. App translation units<br />
that want event listeners, DOM mutation, or typed element access can<br />
include <span style="font-style: italic;">&amp;lt;RmlUi/Core.h&amp;gt;</span> themselves.<br />
</li></ul>
<br />
<span style="font-weight: bold;">Non-invasive integration</span> — zero changes to <span style="font-style: italic;">Engine/Source/Gui/Gui.cpp</span>,<br />
<span style="font-style: italic;">Gui.draw</span>, <span style="font-style: italic;">Gui.update</span>, no new state in <span style="font-style: italic;">DisplayClass</span>, no new<br />
alpha mode, no new shaders. The pre-existing <span style="font-style: italic;">VI</span> + <span style="font-style: italic;">D</span> 2D path is the<br />
only rendering surface used; the existing 2D shaders are the only ones<br />
touched. The tutorial harness (<span style="font-style: italic;">Tutorials/TutorialAuto.{h,cpp}</span>,<br />
<span style="font-style: italic;">Tutorials/stdafx.h</span>) is unchanged. <span style="font-style: italic;">EE_RMLUI=OFF</span> builds are<br />
pixel-identical to pre-integration builds (verified against<br />
<span style="font-style: italic;">Tutorial_05_Bars</span>, <span style="font-style: italic;">Tutorial_04_BatchedDrawing</span>,<br />
<span style="font-style: italic;">Tutorial_12_RenderToTexture</span>).<br />
<br />
<span style="font-weight: bold;">RCSS gotcha</span> — RmlUi's transition/animation parser recognises tween<br />
names as defined in <span style="font-style: italic;">Source/Core/PropertyParserAnimation.cpp</span>. Unlike<br />
standard CSS, it does <span style="font-weight: bold;">not</span> accept bare <span style="font-style: italic;">linear</span> or the CSS keyword<br />
<span style="font-style: italic;">ease</span>; use <span style="font-style: italic;">linear-in-out</span> and <span style="font-style: italic;">cubic-in-out</span> (or any of the<br />
supported <span style="font-style: italic;">{back,bounce,circular,cubic,elastic,exponential,linear,quadratic,quartic,quintic&#8203;,sine}-{in,out,in-out}</span><br />
variants). Multi-transitions are comma-separated.<br />
<br />
<span style="font-weight: bold;">Deferred work:</span><br />
<ul>
<li>Custom <span style="font-style: italic;">Rml::FontEngineInterface</span> on top of Esenthel's <span style="font-style: italic;">Font</span> class, so<br />
<span style="font-style: italic;">.pak</span>-packaged fonts could feed RmlUi. Upstream's default FreeType<br />
engine is sufficient for now; TTF files ship alongside the tutorials.</li>
<li><span style="font-style: italic;">Rml::FileInterface</span> on <span style="font-style: italic;">EE::File</span> — would let RmlUi resolve<br />
<span style="font-style: italic;">.rml</span> / <span style="font-style: italic;">.rcss</span> / texture paths through engine <span style="font-style: italic;">.pak</span> archives. The<br />
stdio <span style="font-style: italic;">fopen</span> default is used currently; tutorials ship plain files<br />
under <span style="font-style: italic;">Tutorials/Data/RmlUi/</span>.</li>
<li>Data Binding (MVC layer via <span style="font-style: italic;">Rml::DataModelConstructor</span>) — supported by<br />
the linked <span style="font-style: italic;">rmlui_core</span> but not yet demonstrated by a tutorial.</li>
<li>Lua bindings (<span style="font-style: italic;">rmlui_lua</span>) — disabled in the wrapper (<span style="font-style: italic;">RMLUI_LUA_BINDINGS=OFF</span>);<br />
enabling would require wiring an Esenthel Lua runtime into the engine<br />
first.</li>
<li>Windows smoke sweep via <span style="font-style: italic;">run_smoke_test.ps1</span>. Linux is covered<br />
(all five tutorials + three regression targets pass).<br />
</li></ul>
<br />
Available in this repo:<br />
<a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Improved Destruction]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11814</link>
			<pubDate>Mon, 20 Apr 2026 02:21:50 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11814</guid>
			<description><![CDATA[<span style="font-weight: bold;">Fracture Destruction System</span> (improve-destruction-v3 branch)<br />
<br />
A production-grade destructible-object system located at Engine/Source/Physics/Fracture*.cpp plus Engine/Source/Game/Objects/Fractured.cpp.<br />
<br />
The existing DestructMesh / Game::Destructible implementation from Tutorial 23 is left untouched. The new system ships alongside it under new type names.<br />
<br />
<span style="font-weight: bold;">Core idea:</span><br />
<br />
A <span style="font-weight: bold;">FractureMesh asset</span> pre-bakes a mesh into chunks, a cluster hierarchy, and a connection graph that records which chunks share faces, with per-edge breaking strain.<br />
<br />
A <span style="font-weight: bold;">Game::Fractured runtime object</span> owns the per-instance state. At runtime, it exists as either a single intact-cluster Actor (cluster proxy) or an array of per-chunk Actors connected by breakable PhysX joints.<br />
<br />
Damage accumulates per node, or on the cluster as a whole. Once the threshold is crossed, the object transitions from <span style="font-weight: bold;">INTACT</span> to <span style="font-weight: bold;">SHATTERED</span> by destroying the proxy and spawning per-chunk actors.<br />
<br />
Mass is force-set from bake-time chunk volume so the convex-hull proxy’s mass overestimate does not cause a visible pop at the moment of breakup.<br />
<br />
Strain then propagates along the connection graph, so a hit on one chunk weakens its neighbours. This reproduces “spider cracks” style breakage without requiring a full spatial-field solver.<br />
<br />
<a href="https://postimg.cc/34G95frD" target="_blank"><img src="https://i.postimg.cc/j5g1fBGv/Screenshot-from-2026-04-19-22-14-24.png" border="0" alt="[Image: Screenshot-from-2026-04-19-22-14-24.png]" /></a><br />
<br />
<a href="https://postimg.cc/SnZMZL4n" target="_blank"><img src="https://i.postimg.cc/Jh85zKd5/Screenshot-from-2026-04-19-21-51-13.png" border="0" alt="[Image: Screenshot-from-2026-04-19-21-51-13.png]" /></a><br />
<br />
<span style="font-weight: bold;">Shipped features:</span><br />
<ul>
[]<span style="font-weight: bold;">Five bake patterns</span> (FRACTURE_PATTERN_) —<br />
PLANAR_SLICES uses random plane cuts and mirrors the classic DestructMesh algorithm.<br />
UNIFORM_VORONOI and CLUSTERED_VORONOI use iterative SplitMeshSolid with perpendicular-bisector planes; the older CSG-based spike was dropped because it was numerically unstable.<br />
RADIAL produces true pie-slice wedges radiating from an impact_point through the wall’s thickness axis.<br />
BRICK uses a dedicated axis-aligned 6-plane-per-cell path (BakeBrickChunks) with <span style="font-weight: bold;">shared-boundary jitter</span>: adjacent bricks read the same boundary entry for their shared seam, so jitter never produces gaps between neighbours.</li>
<li><span style="font-weight: bold;">Cluster proxy</span> (FractureSettings::build_cluster_proxy) — the bake computes a single convex hull over all chunks and stores it on the root Cluster, plus the sum of per-chunk volumes as mass_sum. At spawn, the Fractured object creates one kinematic Actor using this proxy instead of N per-chunk actors. 500 intact crates cost 500 bodies, not 10,000.</li>
<li><span style="font-weight: bold;">Localised release cascade</span> — when damage crosses cluster_break_threshold, Fractured::releaseRootCluster(impact_pos, velocity_delta) destroys the proxy and spawns per-chunk actors, inheriting the proxy’s linear and angular velocity and force-setting each chunk’s mass to the bake-time volume estimate. A <span style="font-weight: bold;">distance-falloff</span> kick is then applied only to chunks within 1.5 m of the impact (mass-scaled, velocity-capped at 4 m/s), so a hit near the bottom of a wall does not catapult distant chunks and make anchored sections rubber-band through joints.</li>
<li><span style="font-weight: bold;">Deferred release</span> — applyPointDamage is called from inside PhysX’s onContact callback; destroying and creating actors there corrupts the scene and crashes the next PxRigidActor::is&lt;&gt;() cast. Instead, threshold crossing sets a pending flag plus the impact position and velocity, and the next update() tick flushes the release outside any callback.</li>
<li><span style="font-weight: bold;">Connection-graph strain propagation</span> — Fractured::propagateStrain walks live_edges each frame when damage_dirty. Damage above the weakest-link elastic limit (0.9 * break_threshold) bleeds across edges; smaller hits dissipate via damage_decay_tau without creeping. Broken joints are removed by BFS; any connected component that does not reach an anchor becomes dynamic.</li>
<li><span style="font-weight: bold;">Bake-time anchors</span> — FractureSettings::anchor_volume marks chunks whose centroid is inside the box with FRAC_FLAG_ANCHORED_AT_BAKE. They spawn kinematic, or the cluster proxy spawns kinematic if any member is anchored. Runtime Fracture::setAnchor(obj, chunk_index, anchored) can flip the anchor state live.</li>
<li><span style="font-weight: bold;">Damage API</span> —<br />
Fracture::applyRadialDamage(pos, radius, strength, impulse, type)<br />
applyContactDamage(a, b, contacts, n, type)<br />
breakNode(obj, node_index, dir, type)<br />
setAnchor(...)<br />
<br />
All accept a DAMAGE_TYPE enum (KINETIC, EXPLOSIVE, PIERCING, THERMAL, CUSTOM) scaled by a global damage_type_scale[5] table, so game code can do things like make grenades 1.5x stronger against concrete with a single tweak.</li>
<li><span style="font-weight: bold;">Multi-subscriber BreakEventCallback</span> — Fracture::addBreakEventCallback is void-return and never consumes the event. addBreakEventCallbackB is bool-return; returning true consumes the event and stops the chain. Legacy setBreakEventCallback is preserved as a single-slot wrapper.</li>
<li><span style="font-weight: bold;">Debris pool</span> — Fracture::setVfxPoolCap, setSfxVoicesCap, debrisActiveCount, plus low-energy sleep and debris-pool caps in updateDebrisHeuristics. Chunks at rest for debris_sleep_timeout seconds are put to sleep so PhysX stops simulating them.</li>
<li><span style="font-weight: bold;">Save / Load</span> — FractureMesh::save(path) and load(path) round-trip the whole asset (chunks, cooked PhysX convex hulls, cluster, and connection graph) through a versioned CC4('F','R','A','C') binary format.<br />
FractureMesh::adjustStorage(universal, physx, bullet) matches the PhysBody contract and allows the asset to be reloaded on a different backend. A cache (Cache&lt;FractureMesh&gt; FractureMeshes("Fracture Mesh")) is preserved so UID-addressed baked assets can live inside pak files like any other engine asset.<br />
<br />
[]<span style="font-weight: bold;">Instance sharing</span> — many Game::Fractured instances can point at the same FractureMesh. Geometry and cooked hulls are shared; only runtime state (damage, actor transforms, live joints) is per-instance.<br />
</li></ul>
<br />
<span style="font-weight: bold;">Non-invasive integration:</span><br />
<br />
Zero changes to DestructMesh, Game::Destructible, or Tutorial 23.<br />
<br />
PhysX’s Physics.reportContact is <span style="font-weight: bold;">not</span> hijacked. The host app registers its own contact callback and can optionally call Fracture::applyContactDamage from inside it.<br />
<br />
Contact damage is PhysX-only, since Bullet does not deliver contact events in this engine. Radial damage works on both backends.<br />
<br />
<span style="font-weight: bold;">Deferred work:</span><br />
<ul>
<li>Multi-level hierarchical fracture (more than 2 levels deep, with per-level break thresholds and cluster-tree view-level LOD). The data model already supports it via Cluster::parent and Cluster::children; the runtime currently walks one level.</li>
<li>Runtime on-the-fly fracture (CSG against an unbaked mesh). Pre-bake only is the shipping path.</li>
<li>View-distance simulation LOD (far objects deferred until the player enters a bubble). The cluster proxy is the foundation; bubble activation logic is planned as a later follow-up.</li>
<li>Multi-platform cooked-hull round-trip. Saved files are currently PhysX-specific; cross-platform support needs adjustStorage(universal=true, ...) before save.</li>
<li>Titan Editor UI for anchor-volume painting, per-chunk material picking, and interactive fracture preview. This branch is intentionally code-only.</li>
<li>Auto-dispatch BreakPreset VFX/SFX from a material tag, so Tutorial 31’s 40-line OnBreak puff could collapse to 5 lines. The scaffolding is already in place (multi-subscriber callbacks, damage types), but the preset registry is deferred.<br />
</li></ul>
<br />
in this fork:<br />
<a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a>]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;">Fracture Destruction System</span> (improve-destruction-v3 branch)<br />
<br />
A production-grade destructible-object system located at Engine/Source/Physics/Fracture*.cpp plus Engine/Source/Game/Objects/Fractured.cpp.<br />
<br />
The existing DestructMesh / Game::Destructible implementation from Tutorial 23 is left untouched. The new system ships alongside it under new type names.<br />
<br />
<span style="font-weight: bold;">Core idea:</span><br />
<br />
A <span style="font-weight: bold;">FractureMesh asset</span> pre-bakes a mesh into chunks, a cluster hierarchy, and a connection graph that records which chunks share faces, with per-edge breaking strain.<br />
<br />
A <span style="font-weight: bold;">Game::Fractured runtime object</span> owns the per-instance state. At runtime, it exists as either a single intact-cluster Actor (cluster proxy) or an array of per-chunk Actors connected by breakable PhysX joints.<br />
<br />
Damage accumulates per node, or on the cluster as a whole. Once the threshold is crossed, the object transitions from <span style="font-weight: bold;">INTACT</span> to <span style="font-weight: bold;">SHATTERED</span> by destroying the proxy and spawning per-chunk actors.<br />
<br />
Mass is force-set from bake-time chunk volume so the convex-hull proxy’s mass overestimate does not cause a visible pop at the moment of breakup.<br />
<br />
Strain then propagates along the connection graph, so a hit on one chunk weakens its neighbours. This reproduces “spider cracks” style breakage without requiring a full spatial-field solver.<br />
<br />
<a href="https://postimg.cc/34G95frD" target="_blank"><img src="https://i.postimg.cc/j5g1fBGv/Screenshot-from-2026-04-19-22-14-24.png" border="0" alt="[Image: Screenshot-from-2026-04-19-22-14-24.png]" /></a><br />
<br />
<a href="https://postimg.cc/SnZMZL4n" target="_blank"><img src="https://i.postimg.cc/Jh85zKd5/Screenshot-from-2026-04-19-21-51-13.png" border="0" alt="[Image: Screenshot-from-2026-04-19-21-51-13.png]" /></a><br />
<br />
<span style="font-weight: bold;">Shipped features:</span><br />
<ul>
[]<span style="font-weight: bold;">Five bake patterns</span> (FRACTURE_PATTERN_) —<br />
PLANAR_SLICES uses random plane cuts and mirrors the classic DestructMesh algorithm.<br />
UNIFORM_VORONOI and CLUSTERED_VORONOI use iterative SplitMeshSolid with perpendicular-bisector planes; the older CSG-based spike was dropped because it was numerically unstable.<br />
RADIAL produces true pie-slice wedges radiating from an impact_point through the wall’s thickness axis.<br />
BRICK uses a dedicated axis-aligned 6-plane-per-cell path (BakeBrickChunks) with <span style="font-weight: bold;">shared-boundary jitter</span>: adjacent bricks read the same boundary entry for their shared seam, so jitter never produces gaps between neighbours.</li>
<li><span style="font-weight: bold;">Cluster proxy</span> (FractureSettings::build_cluster_proxy) — the bake computes a single convex hull over all chunks and stores it on the root Cluster, plus the sum of per-chunk volumes as mass_sum. At spawn, the Fractured object creates one kinematic Actor using this proxy instead of N per-chunk actors. 500 intact crates cost 500 bodies, not 10,000.</li>
<li><span style="font-weight: bold;">Localised release cascade</span> — when damage crosses cluster_break_threshold, Fractured::releaseRootCluster(impact_pos, velocity_delta) destroys the proxy and spawns per-chunk actors, inheriting the proxy’s linear and angular velocity and force-setting each chunk’s mass to the bake-time volume estimate. A <span style="font-weight: bold;">distance-falloff</span> kick is then applied only to chunks within 1.5 m of the impact (mass-scaled, velocity-capped at 4 m/s), so a hit near the bottom of a wall does not catapult distant chunks and make anchored sections rubber-band through joints.</li>
<li><span style="font-weight: bold;">Deferred release</span> — applyPointDamage is called from inside PhysX’s onContact callback; destroying and creating actors there corrupts the scene and crashes the next PxRigidActor::is&lt;&gt;() cast. Instead, threshold crossing sets a pending flag plus the impact position and velocity, and the next update() tick flushes the release outside any callback.</li>
<li><span style="font-weight: bold;">Connection-graph strain propagation</span> — Fractured::propagateStrain walks live_edges each frame when damage_dirty. Damage above the weakest-link elastic limit (0.9 * break_threshold) bleeds across edges; smaller hits dissipate via damage_decay_tau without creeping. Broken joints are removed by BFS; any connected component that does not reach an anchor becomes dynamic.</li>
<li><span style="font-weight: bold;">Bake-time anchors</span> — FractureSettings::anchor_volume marks chunks whose centroid is inside the box with FRAC_FLAG_ANCHORED_AT_BAKE. They spawn kinematic, or the cluster proxy spawns kinematic if any member is anchored. Runtime Fracture::setAnchor(obj, chunk_index, anchored) can flip the anchor state live.</li>
<li><span style="font-weight: bold;">Damage API</span> —<br />
Fracture::applyRadialDamage(pos, radius, strength, impulse, type)<br />
applyContactDamage(a, b, contacts, n, type)<br />
breakNode(obj, node_index, dir, type)<br />
setAnchor(...)<br />
<br />
All accept a DAMAGE_TYPE enum (KINETIC, EXPLOSIVE, PIERCING, THERMAL, CUSTOM) scaled by a global damage_type_scale[5] table, so game code can do things like make grenades 1.5x stronger against concrete with a single tweak.</li>
<li><span style="font-weight: bold;">Multi-subscriber BreakEventCallback</span> — Fracture::addBreakEventCallback is void-return and never consumes the event. addBreakEventCallbackB is bool-return; returning true consumes the event and stops the chain. Legacy setBreakEventCallback is preserved as a single-slot wrapper.</li>
<li><span style="font-weight: bold;">Debris pool</span> — Fracture::setVfxPoolCap, setSfxVoicesCap, debrisActiveCount, plus low-energy sleep and debris-pool caps in updateDebrisHeuristics. Chunks at rest for debris_sleep_timeout seconds are put to sleep so PhysX stops simulating them.</li>
<li><span style="font-weight: bold;">Save / Load</span> — FractureMesh::save(path) and load(path) round-trip the whole asset (chunks, cooked PhysX convex hulls, cluster, and connection graph) through a versioned CC4('F','R','A','C') binary format.<br />
FractureMesh::adjustStorage(universal, physx, bullet) matches the PhysBody contract and allows the asset to be reloaded on a different backend. A cache (Cache&lt;FractureMesh&gt; FractureMeshes("Fracture Mesh")) is preserved so UID-addressed baked assets can live inside pak files like any other engine asset.<br />
<br />
[]<span style="font-weight: bold;">Instance sharing</span> — many Game::Fractured instances can point at the same FractureMesh. Geometry and cooked hulls are shared; only runtime state (damage, actor transforms, live joints) is per-instance.<br />
</li></ul>
<br />
<span style="font-weight: bold;">Non-invasive integration:</span><br />
<br />
Zero changes to DestructMesh, Game::Destructible, or Tutorial 23.<br />
<br />
PhysX’s Physics.reportContact is <span style="font-weight: bold;">not</span> hijacked. The host app registers its own contact callback and can optionally call Fracture::applyContactDamage from inside it.<br />
<br />
Contact damage is PhysX-only, since Bullet does not deliver contact events in this engine. Radial damage works on both backends.<br />
<br />
<span style="font-weight: bold;">Deferred work:</span><br />
<ul>
<li>Multi-level hierarchical fracture (more than 2 levels deep, with per-level break thresholds and cluster-tree view-level LOD). The data model already supports it via Cluster::parent and Cluster::children; the runtime currently walks one level.</li>
<li>Runtime on-the-fly fracture (CSG against an unbaked mesh). Pre-bake only is the shipping path.</li>
<li>View-distance simulation LOD (far objects deferred until the player enters a bubble). The cluster proxy is the foundation; bubble activation logic is planned as a later follow-up.</li>
<li>Multi-platform cooked-hull round-trip. Saved files are currently PhysX-specific; cross-platform support needs adjustStorage(universal=true, ...) before save.</li>
<li>Titan Editor UI for anchor-volume painting, per-chunk material picking, and interactive fracture preview. This branch is intentionally code-only.</li>
<li>Auto-dispatch BreakPreset VFX/SFX from a material tag, so Tutorial 31’s 40-line OnBreak puff could collapse to 5 lines. The scaffolding is already in place (multi-subscriber callbacks, damage types), but the preset registry is deferred.<br />
</li></ul>
<br />
in this fork:<br />
<a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Cloth Physics]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11813</link>
			<pubDate>Sat, 18 Apr 2026 12:56:10 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11813</guid>
			<description><![CDATA[Cloth Physics (XPBD)<br />
A first-party CPU cloth solver built into the engine at Engine/Source/Physics/Cloth.{h,cpp} + Engine/Source/Physics/Cloth Solver.{h,cpp}, sitting behind the existing Cloth / ClothMesh public API. Replaces NVIDIA's deprecated PhysX 3.x cloth (removed in PhysX 4+) with an XPBD (Extended Position-Based Dynamics) implementation — stable, deterministic, tunable, and owned entirely by the engine.<br />
<br />
Core idea — each cloth is a set of mass-weighted particles connected by constraints. Per simulate(dt) the solver substeps (default 8) Jakobsen-style: semi-implicit-Euler predict → XPBD Gauss-Seidel constraint solve (Δλ = (-C - α̃λ)/(w_i+w_j+α̃), α̃ = compliance/dt²) → collision projection against author-placed balls/capsules → derive velocity from position delta. Pinned particles (inv_mass == 0) follow skeleton bones each sub-step via smooth prev↔cur lerp so fast character motion doesn't yank the cape.<br />
<br />
<a href="https://postimg.cc/RW665C3M" target="_blank"><img src="https://i.postimg.cc/GmzkDHds/Screenshot-from-2026-04-18-08-13-11.png" border="0" alt="[Image: Screenshot-from-2026-04-18-08-13-11.png]" /></a><br />
<br />
<a href="https://postimg.cc/1fYnjVzg" target="_blank"><img src="https://i.postimg.cc/x19vqKDP/Screenshot-from-2026-04-18-08-15-29.png" border="0" alt="[Image: Screenshot-from-2026-04-18-08-15-29.png]" /></a><br />
<br />
In this repo: <a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a><br />
See README.md for more info.]]></description>
			<content:encoded><![CDATA[Cloth Physics (XPBD)<br />
A first-party CPU cloth solver built into the engine at Engine/Source/Physics/Cloth.{h,cpp} + Engine/Source/Physics/Cloth Solver.{h,cpp}, sitting behind the existing Cloth / ClothMesh public API. Replaces NVIDIA's deprecated PhysX 3.x cloth (removed in PhysX 4+) with an XPBD (Extended Position-Based Dynamics) implementation — stable, deterministic, tunable, and owned entirely by the engine.<br />
<br />
Core idea — each cloth is a set of mass-weighted particles connected by constraints. Per simulate(dt) the solver substeps (default 8) Jakobsen-style: semi-implicit-Euler predict → XPBD Gauss-Seidel constraint solve (Δλ = (-C - α̃λ)/(w_i+w_j+α̃), α̃ = compliance/dt²) → collision projection against author-placed balls/capsules → derive velocity from position delta. Pinned particles (inv_mass == 0) follow skeleton bones each sub-step via smooth prev↔cur lerp so fast character motion doesn't yank the cape.<br />
<br />
<a href="https://postimg.cc/RW665C3M" target="_blank"><img src="https://i.postimg.cc/GmzkDHds/Screenshot-from-2026-04-18-08-13-11.png" border="0" alt="[Image: Screenshot-from-2026-04-18-08-13-11.png]" /></a><br />
<br />
<a href="https://postimg.cc/1fYnjVzg" target="_blank"><img src="https://i.postimg.cc/x19vqKDP/Screenshot-from-2026-04-18-08-15-29.png" border="0" alt="[Image: Screenshot-from-2026-04-18-08-15-29.png]" /></a><br />
<br />
In this repo: <a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a><br />
See README.md for more info.]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Behavior Trees, Animation Graph, IK]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11812</link>
			<pubDate>Sat, 18 Apr 2026 00:06:09 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11812</guid>
			<description><![CDATA[Behavior Trees (Bt*, animation-state-machine branch)<br />
A data-oriented behavior tree framework for AI decision-making, built into the engine at Engine/H/Game/AI/BehaviorTree.h + Engine/Source/Game/AI/BehaviorTree.cpp. Designed for attachment to Game::Chr subclasses: build a tree of BtNode objects once in Chr::create(), call bt.tick(Time.d()) from Chr::update() each frame.<br />
<br />
<a href="https://postimg.cc/ThH741M7" target="_blank"><img src="https://i.postimg.cc/TYTzh5k6/Screenshot-from-2026-04-17-19-48-04.png" border="0" alt="[Image: Screenshot-from-2026-04-17-19-48-04.png]" /></a><br />
<br />
<br />
Animation Graph (Ag*) and IK (animation-state-machine branch)<br />
A production-grade composable pose-graph animation system, built into the engine at Engine/H/Animation/AnimGraph.h + Engine/Source/Animation/AnimGraph.cpp. Sits on top of the existing AnimatedSkeleton::animate() primitives without modifying them, stylistically parallels the BehaviorTree framework on the same branch (Bt* prefix, namespace Game{}, non-virtual update() wrapper + protected doUpdate() dispatch, tree-walking virtuals for viz).<br />
<br />
<a href="https://postimg.cc/JthYxJgf" target="_blank"><img src="https://i.postimg.cc/QNgLXJqd/Screenshot-from-2026-04-17-19-49-22.png" border="0" alt="[Image: Screenshot-from-2026-04-17-19-49-22.png]" /></a><br />
<br />
IK On:<br />
<a href="https://postimg.cc/4n32Ptqk" target="_blank"><img src="https://i.postimg.cc/xjGr03PC/Screenshot-from-2026-04-17-16-02-10.png" border="0" alt="[Image: Screenshot-from-2026-04-17-16-02-10.png]" /></a><br />
<br />
IK Off:<br />
<a href="https://postimg.cc/XXv1LFmM" target="_blank"><img src="https://i.postimg.cc/y6hqVyTN/Screenshot-from-2026-04-17-16-02-24.png" border="0" alt="[Image: Screenshot-from-2026-04-17-16-02-24.png]" /></a><br />
<br />
In this repo: <a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a><br />
See the README.md for more info.]]></description>
			<content:encoded><![CDATA[Behavior Trees (Bt*, animation-state-machine branch)<br />
A data-oriented behavior tree framework for AI decision-making, built into the engine at Engine/H/Game/AI/BehaviorTree.h + Engine/Source/Game/AI/BehaviorTree.cpp. Designed for attachment to Game::Chr subclasses: build a tree of BtNode objects once in Chr::create(), call bt.tick(Time.d()) from Chr::update() each frame.<br />
<br />
<a href="https://postimg.cc/ThH741M7" target="_blank"><img src="https://i.postimg.cc/TYTzh5k6/Screenshot-from-2026-04-17-19-48-04.png" border="0" alt="[Image: Screenshot-from-2026-04-17-19-48-04.png]" /></a><br />
<br />
<br />
Animation Graph (Ag*) and IK (animation-state-machine branch)<br />
A production-grade composable pose-graph animation system, built into the engine at Engine/H/Animation/AnimGraph.h + Engine/Source/Animation/AnimGraph.cpp. Sits on top of the existing AnimatedSkeleton::animate() primitives without modifying them, stylistically parallels the BehaviorTree framework on the same branch (Bt* prefix, namespace Game{}, non-virtual update() wrapper + protected doUpdate() dispatch, tree-walking virtuals for viz).<br />
<br />
<a href="https://postimg.cc/JthYxJgf" target="_blank"><img src="https://i.postimg.cc/QNgLXJqd/Screenshot-from-2026-04-17-19-49-22.png" border="0" alt="[Image: Screenshot-from-2026-04-17-19-49-22.png]" /></a><br />
<br />
IK On:<br />
<a href="https://postimg.cc/4n32Ptqk" target="_blank"><img src="https://i.postimg.cc/xjGr03PC/Screenshot-from-2026-04-17-16-02-10.png" border="0" alt="[Image: Screenshot-from-2026-04-17-16-02-10.png]" /></a><br />
<br />
IK Off:<br />
<a href="https://postimg.cc/XXv1LFmM" target="_blank"><img src="https://i.postimg.cc/y6hqVyTN/Screenshot-from-2026-04-17-16-02-24.png" border="0" alt="[Image: Screenshot-from-2026-04-17-16-02-24.png]" /></a><br />
<br />
In this repo: <a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a><br />
See the README.md for more info.]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Frame profiler and Input Action System]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11811</link>
			<pubDate>Fri, 17 Apr 2026 01:51:30 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11811</guid>
			<description><![CDATA[In-Engine Frame Profiler<br />
A lightweight CPU scope profiler with an on-screen stats overlay, built into the engine at Engine/H/Misc/Profiler.h + Engine/Source/Misc/Profiler.cpp. Works on all backends (DX11, DX12, Vulkan, GL). Zero overhead when disabled (~1 ns per PROFILE_SCOPE site -- one predictable branch on a cached bool).<br />
<br />
Tutorial_04_FrameProfiler (Tutorials/Source/04 - Graphics/Frame Profiler.cpp<br />
<br />
<a href="https://postimg.cc/216yymw6" target="_blank"><img src="https://i.postimg.cc/y8hZf83F/Screenshot-from-2026-04-15-12-49-24.png" border="0" alt="[Image: Screenshot-from-2026-04-15-12-49-24.png]" /></a><br />
<br />
<a href="https://postimg.cc/RWFhh9R0" target="_blank"><img src="https://i.postimg.cc/rwGrhwR5/Screenshot-from-2026-04-15-12-49-34.png" border="0" alt="[Image: Screenshot-from-2026-04-15-12-49-34.png]" /></a><br />
<br />
<br />
Input Action System (rebindable, savable, stacked)<br />
An Unreal/Unity-style action abstraction over Esenthel's raw polling API, built into the engine at Engine/H/Input/Input Action.h + Engine/Source/Input/Input Action.cpp. 100% additive -- no changes to Kb/Ms/Joypads/Touches. Works on all backends and platforms.<br />
<br />
Tutorial_05_InputRebinding (Tutorials/Source/05 - Gui/20 - Input Rebinding.cpp)<br />
<br />
<a href="https://postimg.cc/xXC88YRf" target="_blank"><img src="https://i.postimg.cc/K87M983M/Screenshot-from-2026-04-16-21-41-02.png" border="0" alt="[Image: Screenshot-from-2026-04-16-21-41-02.png]" /></a><br />
<br />
In this repo: <a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a>]]></description>
			<content:encoded><![CDATA[In-Engine Frame Profiler<br />
A lightweight CPU scope profiler with an on-screen stats overlay, built into the engine at Engine/H/Misc/Profiler.h + Engine/Source/Misc/Profiler.cpp. Works on all backends (DX11, DX12, Vulkan, GL). Zero overhead when disabled (~1 ns per PROFILE_SCOPE site -- one predictable branch on a cached bool).<br />
<br />
Tutorial_04_FrameProfiler (Tutorials/Source/04 - Graphics/Frame Profiler.cpp<br />
<br />
<a href="https://postimg.cc/216yymw6" target="_blank"><img src="https://i.postimg.cc/y8hZf83F/Screenshot-from-2026-04-15-12-49-24.png" border="0" alt="[Image: Screenshot-from-2026-04-15-12-49-24.png]" /></a><br />
<br />
<a href="https://postimg.cc/RWFhh9R0" target="_blank"><img src="https://i.postimg.cc/rwGrhwR5/Screenshot-from-2026-04-15-12-49-34.png" border="0" alt="[Image: Screenshot-from-2026-04-15-12-49-34.png]" /></a><br />
<br />
<br />
Input Action System (rebindable, savable, stacked)<br />
An Unreal/Unity-style action abstraction over Esenthel's raw polling API, built into the engine at Engine/H/Input/Input Action.h + Engine/Source/Input/Input Action.cpp. 100% additive -- no changes to Kb/Ms/Joypads/Touches. Works on all backends and platforms.<br />
<br />
Tutorial_05_InputRebinding (Tutorials/Source/05 - Gui/20 - Input Rebinding.cpp)<br />
<br />
<a href="https://postimg.cc/xXC88YRf" target="_blank"><img src="https://i.postimg.cc/K87M983M/Screenshot-from-2026-04-16-21-41-02.png" border="0" alt="[Image: Screenshot-from-2026-04-16-21-41-02.png]" /></a><br />
<br />
In this repo: <a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[DirectX 12 Ultimate rendering backend]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11810</link>
			<pubDate>Tue, 14 Apr 2026 12:12:36 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11810</guid>
			<description><![CDATA[In this repo: <a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a><br />
<br />
DX12 seems working nicely on Windows 11 nVidia A2000 GPU.<br />
<br />
Lines added (by Claude Code) 11,566<br />
<br />
<a href="https://postimages.org/" target="_blank"><img src="https://i.postimg.cc/jSgX3ZwX/dx12meshshaders.png" border="0" alt="[Image: dx12meshshaders.png]" /></a><br />
<br />
See README.md for info on the added DX12 specific tutorials.]]></description>
			<content:encoded><![CDATA[In this repo: <a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a><br />
<br />
DX12 seems working nicely on Windows 11 nVidia A2000 GPU.<br />
<br />
Lines added (by Claude Code) 11,566<br />
<br />
<a href="https://postimages.org/" target="_blank"><img src="https://i.postimg.cc/jSgX3ZwX/dx12meshshaders.png" border="0" alt="[Image: dx12meshshaders.png]" /></a><br />
<br />
See README.md for info on the added DX12 specific tutorials.]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Vulkan rendering backend with hardware ray tracing]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11809</link>
			<pubDate>Thu, 09 Apr 2026 22:18:40 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11809</guid>
			<description><![CDATA[In this repo: <a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a><br />
<br />
Claude Code did ~97% of the work. Added some Vulkan specific tutorials:<br />
<br />
<br />
<a href="https://postimg.cc/jwvsQt8K" target="_blank"><img src="https://i.postimg.cc/PJnPHqYL/raytracing.png" border="0" alt="[Image: raytracing.png]" /></a><br />
<br />
Tutorial_17_RayTracing — Hardware ray tracing<br />
The marquee Vulkan-vs-OpenGL feature. Renders a small scene (ground plane + 3 colored boxes) entirely via vkCmdTraceRaysKHR running on the GPU's RT cores — no rasterization at all. Includes:<br />
<br />
BLAS (bottom-level acceleration structure) built from raw vertex/index buffers<br />
TLAS (top-level) with one instance of the BLAS<br />
4-shader raytracing pipeline: raygen + primary miss + shadow miss + closest hit<br />
Shader binding table built per Vulkan spec alignment rules<br />
Hardware ray-traced primary rays + hardware ray-traced shadow rays from a directional light, with Lambert shading using flat geometric normals computed in the closest-hit shader via SSBO lookups into the vertex/index buffers<br />
OpenGL has no hardware RT path at all — the legacy GL RT extensions are NV-only and dead. The OpenGL build of this tutorial compiles cleanly but only renders the "Ray tracing: N/A on OpenGL" HUD line.<br />
<br />
GPU requirements: any GPU exposing VK_KHR_ray_tracing_pipeline, VK_KHR_acceleration_structure, and VK_KHR_deferred_host_operations. Verified working on AMD Radeon RX 6650 XT (RDNA2) under Mesa RADV; should work on any RTX 20-series+ NVIDIA, RDNA2+ AMD, or Arc Alchemist+ Intel GPU.<br />
<br />
<br />
<br />
<a href="https://postimg.cc/G8zcJdMb" target="_blank"><img src="https://i.postimg.cc/3NQdhwm0/async-compute.png" border="0" alt="[Image: async-compute.png]" /></a><br />
<br />
Tutorial_17_AsyncCompute — Particle sim on a dedicated compute queue<br />
Demonstrates Vulkan's async-compute feature: a 4096-particle gravity simulation runs on a separate compute queue (when the GPU exposes a queue family with VK_QUEUE_COMPUTE_BIT but NOT VK_QUEUE_GRAPHICS_BIT) while the engine's frame rendering happens in parallel on the graphics queue. OpenGL has no equivalent — it is strictly single-queue.<br />
<br />
The HUD reports whether the GPU has a dedicated compute family. The compute shader is hand-written GLSL (particle_update.comp), pre-compiled once to SPIR-V via glslangValidator and checked in as a C header (particle_update_spv.h) so the build needs no shader tooling installed.<br />
<br />
<br />
<br />
<br />
<a href="https://postimg.cc/yg71ZkT1" target="_blank"><img src="https://i.postimg.cc/1tqfbFnq/gputimer.png" border="0" alt="[Image: gputimer.png]" /></a><br />
<br />
Tutorial_17_Benchmark — Renderer throughput benchmark<br />
Renders 50 animated characters in a 10×5 grid with vsync disabled, an atmospheric sky, the FPS counter, and the active API name. On Vulkan it also shows a per-phase GPU time breakdown (Prepare / GBuffer / Light / Sky / Blend / Post) measured via real vkCmdWriteTimestamp query pools. Useful for measuring relative renderer throughput between OpenGL and Vulkan builds on the same hardware.<br />
<br />
The GPU timer API (Engine/H/Graphics/GpuTimer.h: GpuTimerEnable, GpuTimerMs, GPU_TIMER_PHASE) is exposed engine-wide so any tutorial can read per-phase GPU times. On DX11/GL it compiles to stubs that return 0.<br />
<br />
<br />
See README.md for more info.]]></description>
			<content:encoded><![CDATA[In this repo: <a href="https://github.com/DrewGilpin/EsenthelEngine" target="_blank">https://github.com/DrewGilpin/EsenthelEngine</a><br />
<br />
Claude Code did ~97% of the work. Added some Vulkan specific tutorials:<br />
<br />
<br />
<a href="https://postimg.cc/jwvsQt8K" target="_blank"><img src="https://i.postimg.cc/PJnPHqYL/raytracing.png" border="0" alt="[Image: raytracing.png]" /></a><br />
<br />
Tutorial_17_RayTracing — Hardware ray tracing<br />
The marquee Vulkan-vs-OpenGL feature. Renders a small scene (ground plane + 3 colored boxes) entirely via vkCmdTraceRaysKHR running on the GPU's RT cores — no rasterization at all. Includes:<br />
<br />
BLAS (bottom-level acceleration structure) built from raw vertex/index buffers<br />
TLAS (top-level) with one instance of the BLAS<br />
4-shader raytracing pipeline: raygen + primary miss + shadow miss + closest hit<br />
Shader binding table built per Vulkan spec alignment rules<br />
Hardware ray-traced primary rays + hardware ray-traced shadow rays from a directional light, with Lambert shading using flat geometric normals computed in the closest-hit shader via SSBO lookups into the vertex/index buffers<br />
OpenGL has no hardware RT path at all — the legacy GL RT extensions are NV-only and dead. The OpenGL build of this tutorial compiles cleanly but only renders the "Ray tracing: N/A on OpenGL" HUD line.<br />
<br />
GPU requirements: any GPU exposing VK_KHR_ray_tracing_pipeline, VK_KHR_acceleration_structure, and VK_KHR_deferred_host_operations. Verified working on AMD Radeon RX 6650 XT (RDNA2) under Mesa RADV; should work on any RTX 20-series+ NVIDIA, RDNA2+ AMD, or Arc Alchemist+ Intel GPU.<br />
<br />
<br />
<br />
<a href="https://postimg.cc/G8zcJdMb" target="_blank"><img src="https://i.postimg.cc/3NQdhwm0/async-compute.png" border="0" alt="[Image: async-compute.png]" /></a><br />
<br />
Tutorial_17_AsyncCompute — Particle sim on a dedicated compute queue<br />
Demonstrates Vulkan's async-compute feature: a 4096-particle gravity simulation runs on a separate compute queue (when the GPU exposes a queue family with VK_QUEUE_COMPUTE_BIT but NOT VK_QUEUE_GRAPHICS_BIT) while the engine's frame rendering happens in parallel on the graphics queue. OpenGL has no equivalent — it is strictly single-queue.<br />
<br />
The HUD reports whether the GPU has a dedicated compute family. The compute shader is hand-written GLSL (particle_update.comp), pre-compiled once to SPIR-V via glslangValidator and checked in as a C header (particle_update_spv.h) so the build needs no shader tooling installed.<br />
<br />
<br />
<br />
<br />
<a href="https://postimg.cc/yg71ZkT1" target="_blank"><img src="https://i.postimg.cc/1tqfbFnq/gputimer.png" border="0" alt="[Image: gputimer.png]" /></a><br />
<br />
Tutorial_17_Benchmark — Renderer throughput benchmark<br />
Renders 50 animated characters in a 10×5 grid with vsync disabled, an atmospheric sky, the FPS counter, and the active API name. On Vulkan it also shows a per-phase GPU time breakdown (Prepare / GBuffer / Light / Sky / Blend / Post) measured via real vkCmdWriteTimestamp query pools. Useful for measuring relative renderer throughput between OpenGL and Vulkan builds on the same hardware.<br />
<br />
The GPU timer API (Engine/H/Graphics/GpuTimer.h: GpuTimerEnable, GpuTimerMs, GPU_TIMER_PHASE) is exposed engine-wide so any tutorial can read per-phase GPU times. On DX11/GL it compiles to stubs that return 0.<br />
<br />
<br />
See README.md for more info.]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Wheel controller/collider]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11808</link>
			<pubDate>Mon, 06 Apr 2026 03:16:14 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11808</guid>
			<description><![CDATA[What kind of wheel controller/collider does Titan Engine use? Thanks.]]></description>
			<content:encoded><![CDATA[What kind of wheel controller/collider does Titan Engine use? Thanks.]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[April 2026]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11806</link>
			<pubDate>Fri, 03 Apr 2026 04:20:49 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11806</guid>
			<description><![CDATA[Updated Esenthel Source:<br />
-reduced stack memory usage for World Editor heightmap building (to fix crashes on Linux)<br />
-replaced ThreadMayUseGPUData / ThreadFinishedUsingGPUData with a helper class GPUDataUse for auto lock and release]]></description>
			<content:encoded><![CDATA[Updated Esenthel Source:<br />
-reduced stack memory usage for World Editor heightmap building (to fix crashes on Linux)<br />
-replaced ThreadMayUseGPUData / ThreadFinishedUsingGPUData with a helper class GPUDataUse for auto lock and release]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[March 2026]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11805</link>
			<pubDate>Sat, 07 Mar 2026 06:37:33 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11805</guid>
			<description><![CDATA[Updated Esenthel Source:<br />
-tweaked vignette to automatically apply perceptual gamma adjustment based on vignette color brightness (now dark colors are stronger)<br />
-'Input/Inputs' now supports all button states<br />
-added translation languages: chinese traditional (CT), turkish (TR), czech (CS), ukrainian (UA), hungarian (HU), indonesian (IND)<br />
-improved performance / reduced memory usage of translation functions<br />
-improved outline shader to draw outlines on the outside (and not on inside which made the objects seem thinner)<br />
-fixed issues when using multi-sampling (motion vector shaders - TAA+MotionBlur, SSR, outline)<br />
-increased default 'SoundMaxConcurrent' to 32<br />
-renamed SP-&gt;ES, JP-&gt;JA, PO-&gt;PT<br />
-improved 'LanguageSpecific' function<br />
-'OSLanguage' now supports detection of Chinese Traditional (CT)]]></description>
			<content:encoded><![CDATA[Updated Esenthel Source:<br />
-tweaked vignette to automatically apply perceptual gamma adjustment based on vignette color brightness (now dark colors are stronger)<br />
-'Input/Inputs' now supports all button states<br />
-added translation languages: chinese traditional (CT), turkish (TR), czech (CS), ukrainian (UA), hungarian (HU), indonesian (IND)<br />
-improved performance / reduced memory usage of translation functions<br />
-improved outline shader to draw outlines on the outside (and not on inside which made the objects seem thinner)<br />
-fixed issues when using multi-sampling (motion vector shaders - TAA+MotionBlur, SSR, outline)<br />
-increased default 'SoundMaxConcurrent' to 32<br />
-renamed SP-&gt;ES, JP-&gt;JA, PO-&gt;PT<br />
-improved 'LanguageSpecific' function<br />
-'OSLanguage' now supports detection of Chinese Traditional (CT)]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[New animations]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11803</link>
			<pubDate>Sat, 21 Feb 2026 13:21:05 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11803</guid>
			<description><![CDATA[Hi Guys, Some stuff ive been working<br />
<br />
Back 2 the future<br />
<br />
<a href="https://www.youtube.com/watch?v=s3nwI2pvhKM" target="_blank">https://www.youtube.com/watch?v=s3nwI2pvhKM</a><br />
<br />
Johnny Bravo<br />
<br />
<a href="https://www.youtube.com/watch?v=U5WlK9YZrYw" target="_blank">https://www.youtube.com/watch?v=U5WlK9YZrYw</a><br />
<br />
Will Smith Rap<br />
<br />
<a href="https://www.youtube.com/watch?v=c-WEwwbNS6w" target="_blank">https://www.youtube.com/watch?v=c-WEwwbNS6w</a><br />
<br />
<br />
Superman<br />
<br />
<a href="https://www.youtube.com/watch?v=pHv1r0SlwwE" target="_blank">https://www.youtube.com/watch?v=pHv1r0SlwwE</a><br />
<br />
<br />
Batman<br />
<br />
<a href="https://www.youtube.com/watch?v=WImSZBtwQ9E" target="_blank">https://www.youtube.com/watch?v=WImSZBtwQ9E</a>]]></description>
			<content:encoded><![CDATA[Hi Guys, Some stuff ive been working<br />
<br />
Back 2 the future<br />
<br />
<a href="https://www.youtube.com/watch?v=s3nwI2pvhKM" target="_blank">https://www.youtube.com/watch?v=s3nwI2pvhKM</a><br />
<br />
Johnny Bravo<br />
<br />
<a href="https://www.youtube.com/watch?v=U5WlK9YZrYw" target="_blank">https://www.youtube.com/watch?v=U5WlK9YZrYw</a><br />
<br />
Will Smith Rap<br />
<br />
<a href="https://www.youtube.com/watch?v=c-WEwwbNS6w" target="_blank">https://www.youtube.com/watch?v=c-WEwwbNS6w</a><br />
<br />
<br />
Superman<br />
<br />
<a href="https://www.youtube.com/watch?v=pHv1r0SlwwE" target="_blank">https://www.youtube.com/watch?v=pHv1r0SlwwE</a><br />
<br />
<br />
Batman<br />
<br />
<a href="https://www.youtube.com/watch?v=WImSZBtwQ9E" target="_blank">https://www.youtube.com/watch?v=WImSZBtwQ9E</a>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[February 2026]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11802</link>
			<pubDate>Wed, 11 Feb 2026 06:13:29 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11802</guid>
			<description><![CDATA[Updated Esenthel Source:<br />
-Gui Skin Editor can now edit TextBox Panel<br />
-Gui Window title bar now additionally includes StrEx allowing to include images and text color<br />
-added a new kind of light 'LightPointEx' which is more customizable]]></description>
			<content:encoded><![CDATA[Updated Esenthel Source:<br />
-Gui Skin Editor can now edit TextBox Panel<br />
-Gui Window title bar now additionally includes StrEx allowing to include images and text color<br />
-added a new kind of light 'LightPointEx' which is more customizable]]></content:encoded>
		</item>
	</channel>
</rss>