EMFTeam / EMF

Extended Mechanics & Flavor
34 stars 18 forks source link

Open questions (exploratory) & performance design considerations for realm_size-scaled levies #21

Closed zijistark closed 10 years ago

zijistark commented 10 years ago

A performance goal of NerfLevies is to only ever evaluate the anti-snowball levy-law-modifier adjustment event (i.e., the nerf-levies event) for a playable ruler when the realm_size for that ruler almost certainly changed (false positives OK, false negatives unacceptable). This is only a goal; it's neither clear whether it's yet possible to prevent all false negatives (uncaught realm_size changes) nor is meeting this goal required for NerfLevies to function.

This issue tracks current experimental work with patch 2.1.5.6's modding extensions to determine if all realm_size change cases can now be covered efficiently. These cases are:

It is presumed that the on_war_victory hook will be used to satisfy the first requirement, but is it even necessary? Could false positives (e.g., white peace, revolts, etc.) even be eliminated from the triggering system (and the dependency upon on_war_victory)?

Finally, the nerf-levies event itself is not expected to be extremely cheap to execute. It will likely be quite bloated with many nested if statements bearing down on the actual value of realm_size or its previous value-range. The complexity of this computation might be much higher if a very smooth diminishing-returns curve is designed for the nerf.

This is entirely due to the inability store numeric triggers like realm_size's values in variables for easy comparison and computation with other variable state. If the usurpation of titles on_action is called very frequently (e.g., say 40x in one CB completion for a Crusade or similar) and this forces the nerf-levies event to be reevaluated from scratch every time, this might also prove rather undesirable for performance.

In general, if the trigger sources are too trigger-happy, it may actually be more efficient to just reevaluate the nerf-levies event on all of the >=DUKE sub-realms on pulses and perhaps only use some smaller subset of the realm_size triggers purely for user experience (reduced mean update speed to reflect large changes) or no triggers at all.

[ ANSWER: Simply take all on_new_holder_usurpation, on_new_holder, etc. trigger sources and, from their hidden-immediate handler events, simply schedule a dynamic levy law recalculation for the following game-day. Completely rolls all batch micro-events into a single operation and is a best practice in any case for working with these types of on_actions. ]

Once this exploratory work is done, the design of the trigger system for the nerf-levies event's enforcement (hopefully precisely when necessary, but as can be seen, batch operation with a delay might prove preferable) of levy-law modifiers upon all playable rulers at all times can be determined. NOTE: "All playable rulers" may be adjusted to simply be duke-tier or higher rulers for simplicity and to further cut overhead.

The nerf-levies event structure will then need to be designed for optimal efficiency at removing previous hidden character modifiers that scale down levy efficiency with realm_size and at applying the correct, new character modifier to scale levies for the character. Some challenges for the event design:

[ ANSWER: Use a variable as memoization state which is set to the actual integer value of the associated dynamic levy law modifier grade. Use a hard-coded, traditional binary search over the probably-64 modifier space checking the variable value range against literals in a traditional binary search. Execution will complete on the exact line of code which removes the correct character modifier. ]

[ ANSWER II: If hidden title laws are used, as is now planned but testing still remains in order to confirm feasibility, no memoization state is strictly necessary (adding a new law removes the old value). However, if a revoke_law set needs to be done for some reason upon pass, then the above approach should be used to efficiently only revoke_law 1, correct previous law setting rather than having 63 sequential revoke_laws. ]

zijistark commented 10 years ago

The more I consider it, the more "batching" mechanisms for the various trigger sources of which I can think in order to employ in cutting-down on all unnecessary recalculation. Levy law recalculations can be effective as soon as 1 day after a game state change occurred which changed a playable ruler's realm_size, with no excess recalculation no matter how trigger-happy on_new_holder and its ilk are in bulk operations like CB completions.

Additionally, however, I'm questioning the need for such near-instantaneous modifier updates (actually preferring a maintenance approach in some cases). On the other hand, I'd like to not complicate the design too much with a bunch of types of mixed maintenance (in the name of minimizing performance impact largely, as a totally straight-up maintenance approach is quite expensive on its own-- neither extreme is particularly thrilling).

Finally, I'm certainly questioning whether it's worth tracking this stuff for counts (presumably they have no snowball malus anyhow, so why?), but it's not entirely clear how to take advantage of the fact that you don't really need counts to be involved in the equation in real performance terms, as the point at which they become dukes or the reverse happens is always a point you still need to track in order to promptly add or remove their levy law modifiers accordingly.

We'll be going with a batch-minimized still-triggered-only approach as best as I can tell. I can only guess at real performance impacts; as long as that's true, one might as well follow the clearest-seeming path. I think I'm just a bit shy of hooking into relatively critical paths like title transfer (but, really, these events would occur far less than a lot of everyday stuff firing). And, as always, I'm delicate-- very, very performance-delicate, and even though this ain't a huge collection of coarse-grained triggered modifiers applied to all rulers, it still will have a cost and impact to implement this feature.

zijistark commented 10 years ago

Note that currently I can't get any of the new title on_actions to work at all (I'm surely doing something wrong-- I hope I'm doing something wrong), so determining the answer to some of the open questions is slowed a bit. [And has me just considering writing a full-maintenance-style trigger approach-- the main event would be roughly the same anyhow.]

zijistark commented 10 years ago

It occurred to me while spouting-off chatter in the Skype group earlier today that one annoying case to deal with for logarithmically-scaled levies is major revolts. Revolt titles will be affected by their leader's character modifier, and even if that is automatically replaced within the CBs for faction wars to reflect the much larger revolt title size, it can't a) cover non-faction major revolts like overthrow_ruler and depose_liege, and b) probably won't actually affect the initial levy raised in the revolt, because actual liege levy counts are calculated in batch (monthly, I think) based upon all modifiers and not instantaneously upon modifier update.

Then, I figured, "hey, what if we just tracked the log-scale levy malus modifier directly in the primary title of rulers with a hidden law?" Turns out, this might solve the problem and more than a few other deficiencies caused by using character modifiers rather than laws directly.

Some text that I wrote from that point, added to a bit:

If that works, I could consider actually not using character modifiers at all to modify the levy laws. I could maintain that modifier state in the primary titles of each ruler. [Naturally, would need to apply the correct modifier again shortly when primary titles are switched, but only a minor annoyance.]

Then, when a major revolt happens of any kind, the 2.1 revolt title will automatically copy the primary title laws and set its levies appropriately before the hard-code recalculates the total liege levy before the initial levy-raise, making logarithmically-scaled levies fair and not fatal for major revolts. [The laws should ideally still be updated in faction CBs to account for the realm_sizes that are about to emerge from the split (at least for the liege, who should not be hurt by a huge-realm malus while raising basically only his demesne and a few vassals (or none, in AF1) in an AF revolt.]

Would also show-up properly in the levies-from-each-ruler breakdown and the % available due to laws, etc.

And I think would be much faster to reflect changes in the actual liege levy available (instant on a law change, I think, as opposed to updated in batch monthly). This was one infuriating thing about the law modifiers in CK2+. They took an extra long amount of time to seem to be reflected in the Military View's actual "Troops Available From Vassals" display. I think the fact that that the CK2+ system was based upon character modifiers may have prevented instant update due to direct levy law change.

Now...

Question is, will potential = { always = no } laws that are in effect (by event) still apply their modifiers? Almost certainly, but not verified. That's critical to this alternate approach.

Also, will the engine barf for some reason if there are, say, 60 law slider settings in one law group (that never gets displayed)? That would be critical too.

zijistark commented 10 years ago

Note that if the engine does barf when, say, 60 laws are available in one [hidden] group, it's not actually critical to the approach. One could simply have, say, 60/5 =12 hidden law groups, each with 6 slider settings: the default setting being 'off' (no modifier contribution). The other 5 covering grades of modifiers within that group and all the groups composing the total curve.

zijistark commented 10 years ago

Note that the levy "nerf" will actually be additive. It will increase a ruler's primary title's levy_law obligations rather than nerf them: it simply will add far fewer levies to larger realm_size realms, which equates to a "nerf" in balance terms.

This avoids cases where certain law combinations could actually result in no levies or significant distortion of the logarithmically-scaled component of the levy curve.

It also ensures that if a primary title is switched to a new one (which does not yet have laws attached to it, or simply another of the same tier held in the character's demesne), the default will be to remove all of the additive bonus until a maintenance event updates the new primary title with the appropriate dynamic levy law, preventing an exploit where one could simply switch primary titles right before a war-- before any maintenance event noticed the change in primary and had a chance to update laws-- and bypass the levy nerf w/ large realm_size.

zijistark commented 10 years ago

Almost all questions have been answered from the initial post/article, greatly reducing its size and making it more concrete. Still need to get to testing whether hidden laws will work as expected and, sigh, a quick 'nother whack at testing _on_new_holderinheritance with a more sophisticated test than previously used to confirm that it works. That's all the open questions or design considerations that are still in the air.

zijistark commented 10 years ago

In taking a look at what the law definitions are going to look like for all 64 dynlevy laws, I'd prefer to export the effect of law-passing (revoking whatever the previous dynlevy law was efficiently) to a supplementary event. I'm unsure if the effect will get fired for add_lawing a potential = { always = no } law, just as I'm still not sure whether the law's modifiers will take effect, so I must test this.

Not only do I not know for certain whether the law supplement event will fire in this configuration, but it also raises an interesting question: Will the FROM scope of that supplement event contain the title?