<?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, 31 May 2026 20:44:27 +0000</pubDate>
		<generator>MyBB</generator>
		<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 different 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 different 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>
		<item>
			<title><![CDATA[January 2026]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11801</link>
			<pubDate>Sun, 01 Feb 2026 15:11:44 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11801</guid>
			<description><![CDATA[Updated Esenthel Source:<br />
-IMPORTANT: slightly tweaked bloom formula, please adjust your Editor Settings \ Video Options \ Advanced \ Bloom Scale = 0.6, if you're using custom 'D.bloomScale' in your game codes you might need to adjust it<br />
-Joypad DPAD is now also included in the button functions b, bp, br, bd<br />
-configured engine to use Windows::Gaming::Input first, and if unavailable then fall back to XInput, for improved compatibility when running on Linux<br />
-Gui Objects now additionally support "StrEx descEx"<br />
-added a workaround for old game controllers to have axis centered initially<br />
-other various improvements]]></description>
			<content:encoded><![CDATA[Updated Esenthel Source:<br />
-IMPORTANT: slightly tweaked bloom formula, please adjust your Editor Settings \ Video Options \ Advanced \ Bloom Scale = 0.6, if you're using custom 'D.bloomScale' in your game codes you might need to adjust it<br />
-Joypad DPAD is now also included in the button functions b, bp, br, bd<br />
-configured engine to use Windows::Gaming::Input first, and if unavailable then fall back to XInput, for improved compatibility when running on Linux<br />
-Gui Objects now additionally support "StrEx descEx"<br />
-added a workaround for old game controllers to have axis centered initially<br />
-other various improvements]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Merry Christmas]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11800</link>
			<pubDate>Wed, 24 Dec 2025 11:00:45 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11800</guid>
			<description><![CDATA[Merry Christmas!<br />
<!-- start: postbit_attachments_attachment -->
<br /><img src="images/attachtypes/image.gif" border="0" alt=".jpg" />&nbsp;&nbsp;<a href="attachment.php?aid=3331" target="_blank">676b654d3dfde-merry-christmas-wishes-255207535-16x9.jpg</a> (Size: 110.48 KB / Downloads: 12)
<!-- end: postbit_attachments_attachment -->]]></description>
			<content:encoded><![CDATA[Merry Christmas!<br />
<!-- start: postbit_attachments_attachment -->
<br /><img src="images/attachtypes/image.gif" border="0" alt=".jpg" />&nbsp;&nbsp;<a href="attachment.php?aid=3331" target="_blank">676b654d3dfde-merry-christmas-wishes-255207535-16x9.jpg</a> (Size: 110.48 KB / Downloads: 12)
<!-- end: postbit_attachments_attachment -->]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Sound Qestion]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11799</link>
			<pubDate>Wed, 24 Dec 2025 09:25:07 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11799</guid>
			<description><![CDATA[Hi everybody,<br />
Out of curiosity, what kind of headphones/speakers do you use?<br />
Can you hear quality difference between music/sound files encoded in the engine as 96 vs 128 kbps Opus? (from full quality WAV/FLAC)]]></description>
			<content:encoded><![CDATA[Hi everybody,<br />
Out of curiosity, what kind of headphones/speakers do you use?<br />
Can you hear quality difference between music/sound files encoded in the engine as 96 vs 128 kbps Opus? (from full quality WAV/FLAC)]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Johnny "incel" Bravo]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11798</link>
			<pubDate>Fri, 05 Dec 2025 19:16:58 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11798</guid>
			<description><![CDATA[Just finished this, enjoy<br />
<br />
<a href="https://www.youtube.com/watch?v=U5WlK9YZrYw" target="_blank">https://www.youtube.com/watch?v=U5WlK9YZrYw</a>]]></description>
			<content:encoded><![CDATA[Just finished this, enjoy<br />
<br />
<a href="https://www.youtube.com/watch?v=U5WlK9YZrYw" target="_blank">https://www.youtube.com/watch?v=U5WlK9YZrYw</a>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[December 2025]]></title>
			<link>https://esenthel.com/forum/showthread.php?tid=11797</link>
			<pubDate>Mon, 01 Dec 2025 07:55:10 +0000</pubDate>
			<guid isPermaLink="false">https://esenthel.com/forum/showthread.php?tid=11797</guid>
			<description><![CDATA[Released Esenthel for Windows:<br />
-added support for Visual Studio 2026<br />
-disabled Windows UWP project setting, now you don't need to install it along with VS]]></description>
			<content:encoded><![CDATA[Released Esenthel for Windows:<br />
-added support for Visual Studio 2026<br />
-disabled Windows UWP project setting, now you don't need to install it along with VS]]></content:encoded>
		</item>
	</channel>
</rss>