Rendered at 23:29:09 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
enbugger 11 hours ago [-]
Interesting observation. This is what was lurking in the back of my mind when I was playing Path of Exile. But I usually thought it would be very elegant to implement it in the form of Lisp macros that transform all the computations into fast code that would need to be executed every tick.
I also think something similar could be implemented for an in-game rig of animated characters. In order to cut off the computation of animations that don't affect the result in any way at an earlier stage — for example, when the character switches weapons. This could make animation code more CPU cache friendly as many conditions could be optimized out.
mitander 10 hours ago [-]
Yeah, that’s close to my intuition too.
I’m not generating code for this, at least not currently. It’s more like lowering authored content into fixed-size runtime data that the hot path can consume.
The animation graph example feels like the same general shape.
well_ackshually 17 hours ago [-]
>if skill == cleave and support == wide_sweep:
make cleave radius bigger
if skill == cleave and support == focused_edge:
make cleave smaller but stronger
if skill == cleave and support == twin_cleave and rule == guarded_arc:
quietly move to the woods
>That can work for a demo. It gets rough once the game has lots of skills, supports, items, statuses, and encounter rules.
Path of Exile has deeper mechanics than pretty much every other ARPGs out there, PoB covers the vast majority of them, in a beautiful mess of about 20k lines of calculations in various files.
Making it a "Compiler problem" makes it an unsolvable one in acceptable time.
Second fun fact: that's the one for Path of Exile 2, that has different mechanics. The PoE1 version is the same thing, except even larger.
So yeah, don't bother about making a compiler. Your compiler will figure out micro upgrades and die in three months when the game adds incompatible mechanics. Stay agile and on your feet.
mitander 10 hours ago [-]
Yeah, I think this is the real danger with this kind of approach: mistaking a useful pipeline for a universal mechanic language.
I’m not trying to build a PoE solver or make every future mechanic fit a clean algebra. The compiler analogy is smaller: authored content emits facts, rows keep provenance, caches are rebuilt in a known phase, and combat consumes the resulting runtime data.
The part I want to avoid is common mechanics becoming direct skill/support/item/status branches inside every skill implementation.
But I agree the weird cases need escape hatches. Some rules should probably be explicit hooks, and some unique mechanics may just deserve custom resolution code.
The interesting design question to me is where to draw that line: what belongs in the boring fact pipeline, and what should stay as hand-written mechanic code?
enbugger 11 hours ago [-]
> Making it a "Compiler problem" makes it an unsolvable one in acceptable time.
If I understand correctly, "compilation" is needed only on equipment change. I remember some FPS drop in PoE when changing the gear too.
>in a beautiful mess of about 20k lines of calculations in various files.
And is it actually suitable for realtime? PoB is not a realtime application.
well_ackshually 11 hours ago [-]
Stat recalculations are pretty much immediate, yes. The only thing that takes a while is calculating the nodes with the most potential gains, since you're in effect calculating reachability, combined stat increases, etc, which is a few thousands to millions of calculations depending on how far you look.
FPS drops in PoE would not be caused by damage recalc: this is done entirely server side and on application of the damage/dot tick. That drop you're seeing is just loading the assets and recompiling some shaders, if anything.
But also: it doesn't need to be realtime. Your damage is a simple math formula, applied against another for enemy defense, for each hit instance. There are definitely some builds that stress servers due to the sheer amount of hits/tick, but that's a matter of optimisation. PoE lowers server ticks when it can't follow.
The post is talking about optimizing for build damage/resistance/etc, crafting a build. Which probably doesn't even take into account conversion shenanigans, over time things like wardloop that requires setup, cwdt and a load of other things. This is not an optimizable thing in any reasonable time, and human application of logic is pretty much faster.
mitander 10 hours ago [-]
Small clarification: I don’t mean optimizing buildcraft in the Path of Building sense, as in searching the space of possible gear/tree/support setups and finding the best upgrade.
I mean the engine-side representation problem after the player already has a concrete setup.
The output is not “the optimal build.” The output is things like increased damage, more multipliers, pierce count, area radius delta, conversion rules, status payloads, etc.
I agree that global build optimization quickly becomes a much harder and more human-guided problem. I’m mostly interested in how to keep normal runtime resolution from becoming a giant matrix of skill/support/item/status branches.
AlotOfReading 15 hours ago [-]
There's another way to view this that I think is even more elegant:
You start with facts, which are composed by rules. The current state at any given instant is the fixed point of the derived facts. Provenance is the path through the derivation graph. The whole thing is relational algebra, like SQL or datalog.
A helpful side effect of this perspective is that it can adjust our performance expectations. For example, You can do an in-memory, in-process relational query in microseconds for thousands or millions of rows, way more than any realistic game scene. Incremental updates, etc are nice optimizations, but just let'er rip first and see how fast it goes.
You can even do these kinds of queries fast enough to end up memory bottlenecked if you restrict yourself to queries with fixed graph degree, which is admittedly a mouthful to say (and understand).
mitander 10 hours ago [-]
I agree, “derived facts with provenance” is probably the cleaner way to describe it.
What I have right now is much less formal than Datalog: supports/items/statuses emit rows, rebuild passes fold those rows into skill/entity caches, and combat reads the cached facts.
But the shape I care about is the same: I don’t just want to know “what is the damage number?” I want to know “why is this number here?” and “which source facts need to go away when equipment changes?”
Good point on performance too. The dirty domains are mostly there to make invalidation and phase ownership explicit, not because I’m convinced the first version needs clever incremental updates. Full clear-and-rebuild is probably the right baseline, and then we can add incremental paths only where profiling proves they’re needed.
ZeWaka 17 hours ago [-]
Darn, this title got me excited about Minecraft.
valorzard 16 hours ago [-]
I’m glad I’m not the only one who thought about the classic 1.2.5 mod with the giant quarries that mined out entire chunks of the world, and the pipes you could use to make a Jaffa cake factory
roygbiv2 13 hours ago [-]
Quarries were great but I hated the big hole it left in the ground.
I also think something similar could be implemented for an in-game rig of animated characters. In order to cut off the computation of animations that don't affect the result in any way at an earlier stage — for example, when the character switches weapons. This could make animation code more CPU cache friendly as many conditions could be optimized out.
I’m not generating code for this, at least not currently. It’s more like lowering authored content into fixed-size runtime data that the hot path can consume.
The animation graph example feels like the same general shape.
if skill == cleave and support == focused_edge: make cleave smaller but stronger
if skill == cleave and support == twin_cleave and rule == guarded_arc: quietly move to the woods
>That can work for a demo. It gets rough once the game has lots of skills, supports, items, statuses, and encounter rules.
Laughs in path of building : https://github.com/PathOfBuildingCommunity/PathOfBuilding-Po...
Path of Exile has deeper mechanics than pretty much every other ARPGs out there, PoB covers the vast majority of them, in a beautiful mess of about 20k lines of calculations in various files.
Making it a "Compiler problem" makes it an unsolvable one in acceptable time.
Fun fact: it directly parses mods from their ingame description into a mod database that is basically a stringly typed list of whatever increases also. : https://github.com/PathOfBuildingCommunity/PathOfBuilding-Po...
Second fun fact: that's the one for Path of Exile 2, that has different mechanics. The PoE1 version is the same thing, except even larger.
So yeah, don't bother about making a compiler. Your compiler will figure out micro upgrades and die in three months when the game adds incompatible mechanics. Stay agile and on your feet.
I’m not trying to build a PoE solver or make every future mechanic fit a clean algebra. The compiler analogy is smaller: authored content emits facts, rows keep provenance, caches are rebuilt in a known phase, and combat consumes the resulting runtime data.
The part I want to avoid is common mechanics becoming direct skill/support/item/status branches inside every skill implementation.
But I agree the weird cases need escape hatches. Some rules should probably be explicit hooks, and some unique mechanics may just deserve custom resolution code.
The interesting design question to me is where to draw that line: what belongs in the boring fact pipeline, and what should stay as hand-written mechanic code?
If I understand correctly, "compilation" is needed only on equipment change. I remember some FPS drop in PoE when changing the gear too.
>in a beautiful mess of about 20k lines of calculations in various files.
And is it actually suitable for realtime? PoB is not a realtime application.
FPS drops in PoE would not be caused by damage recalc: this is done entirely server side and on application of the damage/dot tick. That drop you're seeing is just loading the assets and recompiling some shaders, if anything.
But also: it doesn't need to be realtime. Your damage is a simple math formula, applied against another for enemy defense, for each hit instance. There are definitely some builds that stress servers due to the sheer amount of hits/tick, but that's a matter of optimisation. PoE lowers server ticks when it can't follow.
The post is talking about optimizing for build damage/resistance/etc, crafting a build. Which probably doesn't even take into account conversion shenanigans, over time things like wardloop that requires setup, cwdt and a load of other things. This is not an optimizable thing in any reasonable time, and human application of logic is pretty much faster.
I mean the engine-side representation problem after the player already has a concrete setup.
So the “compiler” part is closer to:
equipped skills/supports/items/statuses -> emitted modifier/behavior rows -> dirty derived caches -> runtime facts consumed by combat
The output is not “the optimal build.” The output is things like increased damage, more multipliers, pierce count, area radius delta, conversion rules, status payloads, etc.
I agree that global build optimization quickly becomes a much harder and more human-guided problem. I’m mostly interested in how to keep normal runtime resolution from becoming a giant matrix of skill/support/item/status branches.
You start with facts, which are composed by rules. The current state at any given instant is the fixed point of the derived facts. Provenance is the path through the derivation graph. The whole thing is relational algebra, like SQL or datalog.
A helpful side effect of this perspective is that it can adjust our performance expectations. For example, You can do an in-memory, in-process relational query in microseconds for thousands or millions of rows, way more than any realistic game scene. Incremental updates, etc are nice optimizations, but just let'er rip first and see how fast it goes.
You can even do these kinds of queries fast enough to end up memory bottlenecked if you restrict yourself to queries with fixed graph degree, which is admittedly a mouthful to say (and understand).
What I have right now is much less formal than Datalog: supports/items/statuses emit rows, rebuild passes fold those rows into skill/entity caches, and combat reads the cached facts.
But the shape I care about is the same: I don’t just want to know “what is the damage number?” I want to know “why is this number here?” and “which source facts need to go away when equipment changes?”
Good point on performance too. The dirty domains are mostly there to make invalidation and phase ownership explicit, not because I’m convinced the first version needs clever incremental updates. Full clear-and-rebuild is probably the right baseline, and then we can add incremental paths only where profiling proves they’re needed.