Latest topics
» Saturday game perhaps?
by Charmead Today at 1:50 am

» Stock scenarios with KS Mod AAR
by risorgimento59 Yesterday at 7:43 pm

» Impromptu Games
by Mongo Yesterday at 6:07 pm

» Gettysburg meets Kriegsspiel
by Charmead Yesterday at 12:40 am

» AARs - post here all after battle comments and replay files
by Mr. Digby Yesterday at 12:39 am

» SOW Scenario Generator
by Mr. Digby Yesterday at 12:38 am

» Sandbox campaign
by Uncle Billy Thu Feb 14, 2019 10:46 pm

» Game Crash?
by Mr. Digby Tue Feb 12, 2019 7:31 pm

» Set Up for SOWWL NAPOLEON GAMES For Kriegspiel style
by Tactical Wargamer Tue Feb 12, 2019 2:52 pm

» The reason I was smelling sulfur yesterday.
by Mongo Mon Feb 11, 2019 3:21 pm

» I MADE IT!
by Charmead Sun Feb 10, 2019 12:25 am

» Stand-alone OOBs
by Mr. Digby Mon Jan 28, 2019 7:36 pm

Statistics
We have 1032 registered users
The newest registered user is Mongo

Our users have posted a total of 25650 messages in 2024 subjects
Log in

I forgot my password


Stock scenarios with KS Mod AAR

Go down

Stock scenarios with KS Mod AAR

Post  Mr. Haelscheir on Thu Jan 31, 2019 10:29 am

Greetings fellows,

It has been a long while. Now, the gameplay I am about to discuss dates back to some eight months ago during and after a winter study term at my university, likely circa Didz' discussion on "Deciphering the AI system"; I had long intended on making this post, even "rehearsing" by reviewing all of my screenshots and renumbering them so they are ordered correctly on Google Drive, but had otherwise never gotten to it. It comprises my play-through of the stock Quatre Bras, Ligny and Waterloo scenarios while using the KS Mod (I am unsure if this type of post would belong in the existing AAR thread which seems to be more for the multiplayer games). You may find a plethora of screenshots and the corresponding replays here. Some replays are divided into parts due to the game's tendency to crash after two hours or so, requiring me to save the game state and replay and then resume. I unfortunately forgot to save the replay when I did my first backup of the Ligny battle. Each screenshot is numbered and named with a description of the situation. A bare mini-map was used except for the Battle of Ligny. All screenshots were taken with a 1:1 sprite ratio and highest settings on an Intel i7-4790 and NVIDIA GTX 960 gaming PC.

As for mods, as we know from my post on "Deciphering the AI system", I made some mods to the Logistics/sfx.csv file which can be seen in the linked directory; changes are labelled with "reverted to stock" and comprise changing the filename or replacing the file with its stock counterpart. This includes the commander walking and cavalry running sounds, and restoring the stock battle sounds which I greatly prefer for their satisfying "pop" which you might expect out of an entire brigade firing at will after the first volley. Those sound effects which seemed to have been taken from the Gettysburg game were also particularly off-putting in this setting. I also got my hands on the glorious Smoke&Flare mod which just substantially adds to the immersion; the soldiers gain a sense of "individuality" as you observe each of them fire a piece with the feedback of a flash. It would have been even nicer if these smoke and hence flash effects during a volley were to be triggered in more of a natural "ripple" rather than in perfect simultaneity. Now, I did have to disable the Supplemental Maps mod to get this to work as the muzzle flash sprites conflict with the Terrain_Effects.dds file, causing the game to crash after a short while. It would be pretty cool if you were to consider integrating this mod into the KS Mod, as it effectively replaces some smoke sprites and durations.

These days, I do very much enjoy and prefer commanding at the corps or army level and riding about as the situation in the battle raging at the other side of the map remains unbeknownst to me. I have been told that the stock scenarios may tend to be very difficult when played in HITS mode, but I have found that with the KS Mod (after resolving the "folder in a folder" issue), I have been able to consistently "win" the stock scenarios, albeit sometimes narrowly (there were times when I felt that the situation was dire, but magically pulled through, unless this was before I had resolved the "folder in a folder" issue), at least to the extent that I have managed to sustain much less casualties and inflict much more on the enemy than I used to. I don't know if this is due to different balancing caused by the mod, though I personally trust the stock OOB for their historical accuracy (though I do remember a topic on this forum discussing this; I don't know if damage and morale stats are attached to the mod or the specific scenario's OOB), whether 'twas something I "did right", or the random number gods blessed me with a number of gloriously successful cavalry charges.

I suggest following the screenshots and their descriptions while reading through the following.

Ligny (The Eagle Triumphs):

While I had played all the French scenarios other than the full Waterloo battle before, using the stock game (amid the "folder in a folder" issue) and Grog's toolbar, this was the first scenario I played using the KS Napoleon Mod 1.27. Here, I still needed to rely on the mini-map, at least using the minimum view radius, though of course, in the past this has given me unrealistic foreknowledge of enemy cavalry charges. At this point, I have grown quite used to using the indispensable command map (pressing 'N') to move myself around by road and move large groups of men. I must comment that for some reason, at least in the KSFrenchScenario2, whenever I try to move my corps commander using the command map, regardless of my subordinates' TC status, it always sends dispatches for my entire army to move with me; this has forced me to manually right click ahead of me or spam the 'W' HITS movement to get around the map, which can be quite inconvenient. I tried once with a bare mini-map and felt it impossible given that I couldn't even ascertain the relative positions of my OOB. I think that at that time, some setting may have prevented the "commander destination crosshairs" from being visible, whereas in my subsequent battles, the appearance of this at the beginning of the battle at least allowed me to familiarize myself with my OOB and their locations. Obviously, a commander cannot go into battle with absolutely no idea of what he has available to him and where they were initially supposed to be.

Otherwise, the battle commenced with a bombardment of Saint Amand at around 3 PM. I believe I had detached Maurice, or effectively the entire reserve artillery, to the ridge west of the village, among others. This effectively continued until Saint Amand was nigh entirely decimated (information "unfortunately" relayed to me by the mini-map), so this may have saved me much casualties. I read in the KS Mod Manual that you guys prefer to "let the artillery commanders find the best position", but unfortunately, the game does not let one order a corps commander to "form a large battery and bombard Saint Amand before attacking", so I felt forced to micromanage courier commands for general artillery brigade wide facing, formation and unlimbering commands, sometimes still having to manually place those individual batteries I detached where I want them. Otherwise, to observe some potentially missed opportunities for extensive bombardment of a clumped position.

By 4 PM, Lefol would be committed towards Saint Amand with the others under Vandamme to eventually follow. Here, I face the limitations of being unable to tell Vandamme to do this for me, or relay to him the desired course of action as opposed to simply selecting him and clicking on the general area on the command map whence he might do something horrid. As always, glorious visuals. Sometimes, I see large groupings of troops in loose formation, like in picture 014 - I presume that these are the "skirmisher battalions" added by the KS Mod? Anyways, my general strategy was to first apply substantial pressure on the left and hope Blucher commits a large amount here before I proceed to advance Gerard upon Ligny. Then, of course, Hulot (or at least his subordinate, Baume), who was probably meant to engage upon Tongrenelle, managed to pull off a premature attack on Ligny at around 4:30 PM while I was busy supervising the left. I was thus left to let Toussaint join in. The picture filenames should explain the rest.

By 5 PM, Lefol and Berthezene would continue to press on the enemy's right while Hulot struggled to take Ligny. Duhesme's Young Guard (IIRC) would be sent up to help on the left. Nigh 6 PM, Vallin and then Berruyer under Maurin's cavalry division would be committed at Saint Amand, in part to counter enemy cavalry. Perhaps some of these commitments were "unprofessional". Having Domon likewise support the attack on the left as Blucher takes my bait. To learn that often, while a "decimated" cavalry brigade's population may dwindle to "negative" numbers, a large number probably survive having been routed and may still operate at Waterloo. Hmm, I must have screwed up my image numbering terribly by image 038 as the narrative is otherwise uninterrupted...  By 6:45 PM, we would take Ligny, surprised to receive no response from Blucher. I would begin to amass the Old Guard for a large push through the center, though I am unsure what the historical, concealed route was.

Around 7 PM, Vandamme and Duhesme are struggling to fend off Blucher's push through Saint Amand La Haye. "Image 89" is really supposed to be "image 39"; 30 minutes lost by that crash. 'Twas here that I figured out how to retain a replay regardless of these crashes. Perhaps I had retconned my intents for Exelman's cavalry corps. And, ANOTHER crash. Now, enemy cavalry to pin us at Ligny. I guess I haven't been too effective at countering these large cavalry counterattacks. Around 7:30 PM, the mini-map would allow me to see a massive blob of enemy artillery I would have loved to run over with cuirassiers. I believe I sent Pecheux and Soult to engage Tongrenelle; they would arrive by 8 PM. Now, I personally don't know what advantage Napoleon would have had in engaging Tongrenelle other than to inflict casualties and if successful, wrap around upon Blucher's center. Perhaps, Hulot may have been enough, or I simply consistently failed to have Pajol's cavalry arrive on time to support them, or either would always end up being waylaid along the way, or sometimes be halted in square in the midst of an enemy artillery battery. Otherwise, by 9 PM, everyone except for those at Tongrenelle would have been committed towards beating through Blucher's center. We shall have routed virtually everyone by 11 PM, though the battle scripts may have been broken after saving and resuming, for night never came.

Looking at the mini-map from the replay (good old signed 16-bit casualty numbers), it appears that we very much cleared most of them out, retaining the grand majority of our brigades, though it appears that at least two of Blucher's along with much cavalry were never committed. Morale wise, they would have most probably retreated, else they may have prevented the defeat from being this decisive. The overall casualties were more than double the historical numbers shown on Wikipedia, though clearly, things were a matter more vigorous in this battle. Otherwise, it may have been due to unrealistic levels of mixing between friendly cavalry and infantry allowing for increased damage output, or the game's failure to properly account for the space battalions occupy so as to limit the number of muskets bearing upon the enemy at any time.

The KS Mod Manual mentioned something about battalions, brigades and divisions typically being committed "one at a time". Brigades, maybe, depending on the frontage that needs to be covered which might not be well represented by Scourge of War: Waterloo's sprite limit. Otherwise, I would like to learn of the sources which better describe in detail how many troops would usually be engaged at a time for a given frontage. As for the stock scenario battalion and brigade spacing, it sometimes appears to be based on the 200 sprite limit, whereby a visualization of battalions in their full scale may demand changes to the scenario design (in an otherwise correctly scaled battlefield) to make the brigades fit. Maybe a KS Mod addition that calculates a battalion's "actual" frontage and prevents battalions from being positioned too close together may permit casualty rates closer to their historical values.

Quatre Bras:

Here, it seems that only the Ligny map got an overhaul, unless this is due to my having disabled the Supplemental Maps Mod to get Smoke&Flare to work. This time, I was using a bare mini-map, only relying on the "commander crosshairs" to give me a rough idea of their last destination (though they could easily end up somewhere completely different). Now I am forced to actually look around me to ascertain the situation. For me, this was a relatively "simple" battle, having Foy take on Gemioncourt while Bachelu took on Lairalle and then the allied left. Now, 'twas by experience that I would know to send in Huber or Wathiez to preemptively protect Gauthier and Jamin west of Gemioncourt should the allied cavalry come along. Of course, there would be the pain of having a commander walk out of an objective's radius at the last second. By 3:45 PM or so, to have Jerome's division proceed northwest to secure Grand Pierrepont and the southern Bossu Woods. Of course, this would be a defeat according to the game, yet we suffered historical casualty rates whereas the allies suffered more than double the historical number. Now, again, this may be due to the aforementioned issues with "battalion frontage", but may also be due to a series of insane cavalry rampages, such as two of Wathiez' lancer squadrons single-handedly clearing out the Bossu Woods. Pire's cavalry division alone killed off more than the historical number. I may have just been extremely lucky, or the enemy panicked, or an unrealistic amount of "combined arms" was permitted by the game with cavalry charging through their friendly units. "They kept on coming, and we kept on routing them."

In past, stock experiences, the replays showed us against an exceedingly more substantial enemy force with some battalions having remained uncommitted, yet here, it seems like we had almost evened them out and forced them to be fully committed, whereby we may have forced a retreat had Bauduin and Soye been able to wrap around their left flank.

Now, for whatever the reason, the original battle script causes nightfall to come ridiculously early. I managed to mod this out so that in a separate play-through, I was able to summon d'Erlon and Desnouettes to completely wipe out Wellington's forces.

Waterloo (La Bataille de Mont-Saint-Jean):

Finally, I'd tackle this massive battle for the first time. Of course, to first familiarize myself with the OOB and where everyone is to acquire the initial awareness Napoleon should have had. I am guessing that the KS Mod tends to make artillery more "conservative" with the threshold at which they start firing. I also recall that the Quatre Bras map was the "sunniest" with a bluer sky compared to Waterloo and Ligny. To proceed with a standard engagement of Hougoumont, except that this time, I started with a bombardment that cleared the woods completely before noon. I would again call up Maurice's artillery division to bolster the Grande Batterie. Instead of taking Hougoumont directly, I first had Jerome in his entirety clear out the left and have some troops sally out of the farm before letting Foy secure the building with minimal resistance; I had supposed that Napoleon would have known about those forces' presence. Without images of the mini-map, it is harder to recall where I was in a particular picture.

Wathiez' lancers to go on yet another insane rampage. Now, as far as I can tell, at a 1:1 sprite ratio and individual squadrons, I can feel that sights such as image 022 depict these cavalry engagements in their true, glorious scale, whereas the "brigade conglomerations" seen in the KS Mod scenario OOBs seem to greatly reduce this sense of scale and breadth. For me, it was most satisfying to be able to see things like Milhaud's corps in its entirety, sprawling over hundreds of meters with squadrons laid out as they likely truly were. In image 024, which depicts two by four columns by division side by side, each "column" would really only depict one half or "section" of a full-strength French infantry company, whereby the arrangement in the image would be of the scale of just two columns by division back to back - dear goodness, a full-strength battalion would be massive. From this, it should be clear that with the way the game currently spaces out units, an unnatural number of muskets are being allowed to bear on the other at a time within a given space.

It would be until 2 PM that I would have Bachelu begin an engagement of the center while my cavalry claimed the ridge. I would begin moving Lobau to guard Frischermont at around the historical time. By around 3:20 PM, Marcognet and Donzelot under d'Erlon would begin the engagement upon the right. Around 3:45 PM, I believe that I figured to have Alexandre support Lobau instead of staying back at Plancenoit or supporting a push through the center; I'd later have Duhesme stay at Plancenoit and later support the repulsion of the Prussians. To have completely broken Wellington's right by 5 PM and striven to envelop his forces from both sides. It seems that by 6 PM, Wellington had withdrawn all of his remaining troops to Mont Saint-Jean behind his massive cavalry reserve. I was running out of cavalry whereby his reserves seemed impenetrable; I had "unprofessionally" exhausted them. I was afraid that we might lose.

As for fending of the Prussians, Lobau and Alexandre didn't budge once. I thus recalled some cavalry from Frischermont to work against the center. At this point, I just went all out and brought forward as much as I could against the village. Victory. On resumption, to see the freak British cavalry squadron appear out of the darkness chasing and being chased by one of my own. 'Twas quite the brawl.

Looking at the replay, Alexandre took a very strange route to Frischermont. Likewise, the Prussians unfortunately spawned on top of one of the cavalry brigades I sent there. It seems that Alexandre was also blocked by a Prussian cavalry contingent while the Prussians at Frischermont began to surround Lobau. Once those cavalry were cleared away, Alexandre was able to meet the Prussians at their left flank and push them back at least off of Lobau's right flank. Duhesme would also be moved from Plancenoit to help in the effort. Throughoutthis whole ordeal, d'Erlon's forces would be wrapping around Wellington's forces from the right. As for the fog of war, here, I was completely unaware that Wellington had actually bunched up his troops around Merbe Braine, whereby I not only completely removed any chance of his joining with Blucher, but also acquired a position that would prevent him from retreating, though I am unsure as to what extent either side would have been fit to continue fighting. If anything, both sides were completely caught up in the chaos and confusion.

Comments on the KS Mod:

Indeed, the KS Mod probably wasn't meant to be used with the stock scenarios, but per my desire to have a good experience with the large battles of the Waterloo Campaign specifically, these have been my experiences and observations. Of course, it would be great to see the Smoke&Flare mod be made compatible with the KS Mod. Likewise, I'd imagine that even in multiplayer, if you were to order a human to "form a grand battery", they might still have to do some manual cannon placement themselves. In playing through the KSFrenchScenario2, I still miss the visual and operational scale made possible by the separation of cavalry into their squadrons. Finally, the provision of a means to enforce even more "realistic" spacing between battalions to reflect their massive frontage may help reduce the disproportionate casualty rates and crowding. As for the want of receiving more information from my immediate subordinates, it appears that this is beginning to be addressed more and more, though I still find it annoying when a bunch of brigadiers deep in the chain of command start spamming me with messages which don't matter if I don't even know who and where they are; it would be nice if I only ever heard from my immediate subordinates or those whom I ask.


Last edited by Mr. Haelscheir on Thu Jan 31, 2019 10:32 am; edited 1 time in total (Reason for editing : small addition to the last paragraph)
Mr. Haelscheir
Mr. Haelscheir

Posts : 9
Join date : 2017-09-04
Location : Canada

Back to top Go down

Re: Stock scenarios with KS Mod AAR

Post  Mr. Digby on Thu Jan 31, 2019 11:38 am

Thanks for the post, really useful stuff.

even in multiplayer, if you were to order a human to "form a grand battery", they might still have to do some manual cannon placement themselves.
A human player almost always orders a battery to deploy and of course multiple batteries takes some placement - but so it should - grand batteries were rarely formed during a battle for this reason and usually were set up before battle commenced. There is no need for a human player to consider placing individual guns in the same way that brigade and division commanders wouldn't have an interest in such things, nor even the ability to do so since that is the battery commanders job.

I still miss the visual and operational scale made possible by the separation of cavalry into their squadrons.
We changed from squadrons to regiments after an enormous amount of play testing, and I mean literally years. There are numerous reasons why on a gameplay level as well as a historical level regiments work better and are more appropriate than squadrons and I believe we've gone into this in the documents.

Finally, the provision of a means to enforce even more "realistic" spacing between battalions to reflect their massive frontage may help reduce the disproportionate casualty rates and crowding.
It would reduce crowding, yes, but not casualty rates. The density of sprites is ignored by the game engine when calculating casualty rates. While it would be better if units were spaced apart more, bear in mind several issues: 1) brigades and divisions would take longer to deploy and battles would lengthen. 2) Maps would be smaller as divisions would encounter each other over greater distances. 3) The KS Mod slows troop movements down somewhat from the stock game so a small change such as battalion spacing actually affects losses as well as a brigade taking longer to deploy will suffer more losses if it is under fire. 4) Players naturally want to win so will always try to pack their battalions into the smallest possible space to gain the maximum firepower. Well spaced-apart formations would still crowd together when a player commands them which would give the human a signifcant advantage over an AI opponent who would obey the formation limitations more.

So you end up with the same situation of a crowded battlefield in the end but several downsides to time and losses while you get there.

As you can appreciate, a small adjustment in one area can have wide reaching effects in other areas.

As for the want of receiving more information from my immediate subordinates, it appears that this is beginning to be addressed more and more, though I still find it annoying when a bunch of brigadiers deep in the chain of command start spamming me with messages which don't matter if I don't even know who and where they are; it would be nice if I only ever heard from my immediate subordinates or those whom I ask.
Thats a feature of the stock game and was designed to inform the human player of the situation rather than the historical virtual commander. I agree its annoying, especially when the message contains no clue as to the sender's location, but the game has limited ability to describe where a sender is and adding that detail for every single map would be the work of a lifetime! In MP KS play we have actually removed many of these and if you command a division you no longer get these reports from your subordinate brigades which we find makes the game more relaxing. Having to dismiss messages you don't have time to read while you are fighting your troops is something we all agreed was worse than having no information.

_________________
The other Martin - Charles Reille, le dernier Maréchal de France.

"Any hussar who has not got himself killed by the age of 30 is a jackass." - Antoine Charles Louis Lasalle, commander of Napoleon's light cavalry, killed in battle at Wagram 6 July 1809, aged 34.

"I had forgotten there was an objective." - Generallieutenant Mikhail Borozdin I
Mr. Digby
Mr. Digby

Posts : 5095
Join date : 2012-02-14
Age : 59
Location : UK Midlands

Back to top Go down

Re: Stock scenarios with KS Mod AAR

Post  Uncle Billy on Thu Jan 31, 2019 6:00 pm

I'll second that, very nice AAR/critique. I'll add a couple of comments to what Digby has written.

I have been able to consistently "win" the stock scenarios, albeit sometimes narrowly (there were times when I felt that the situation was dire, but magically pulled through, unless this was before I had resolved the "folder in a folder" issue), at least to the extent that I have managed to sustain much less casualties and inflict much more on the enemy than I used to.
Having never played a stock scenario, I don't know how badly the mod unbalances them. I'm sure it does to some extent, but maybe overall, the changes tend to cancel each other out and not skew the results too badly.

In general, casualties tend to be higher than what history records, because the AI tends to fight past a point where units are no longer combat effective and would have withdrawn. Although the KS mod makes some attempt to force the AI to withdraw brigades once they reach that state, it is probably still too high a threshold and they should withdraw even earlier. Another limit is that brigade and division players will never see their brigade(s) withdraw. They can be fought until everyone routes, again unrealistic.

whenever I try to move my corps commander using the command map, regardless of my subordinates' TC status, it always sends dispatches for my entire army to move with me; this has forced me to manually right click ahead of me or spam the 'W' HITS movement to get around the map, which can be quite inconvenient.
Moving the corps commander via the command map will move the entire corps in the stock game as well as in the KS mod. That is a game engine mechanic and can't be modded. The only units that won't move are those that are TC'd.

As for the want of receiving more information from my immediate subordinates, it appears that this is beginning to be addressed more and more, though I still find it annoying when a bunch of brigadiers deep in the chain of command start spamming me with messages which don't matter if I don't even know who and where they are; it would be nice if I only ever heard from my immediate subordinates or those whom I ask.
Messages from AI subordinates no longer pop up in the SP game as of KS version 1.29. If you are a corps commander, you will receive periodic updates from your division commanders. They will tell you where they are, the casualties they've suffered and the size of the enemy they are confronting. In general, you'll see quite a few differences between the 'old' version of the mod, 1.27 and 1.29. You'll have to update your game to version 1.03 before you can use it. Try one or two of the stock scenarios with the latest version of the mod and let us know if the results differ significantly.


_________________
I can make this march and I will make Georgia howl.
Uncle Billy
Uncle Billy

Posts : 3323
Join date : 2012-02-27
Location : western Colorado

Back to top Go down

Re: Stock scenarios with KS Mod AAR

Post  Mr. Haelscheir on Fri Feb 01, 2019 2:01 am

Thank you for your responses.

Moving the corps commander via the command map will move the entire corps in the stock game as well as in the KS mod. That is a game engine mechanic and can't be modded. The only units that won't move are those that are TC'd.

I meant the movement of my avatar, who would technically be a "corps commander" in the Quatre Bras scenario; when moving my avatar via the command map in the stock scenario, both in the default menu and when ported as a user scenario, my avatar doesn't send out any dispatches to move everyone where I am going, otherwise, this is indeed the expected behaviour for my immediate subordinates. In KSFrenchScenario2, moving my avatar moves my entire army as I would a mere subordinate. Otherwise, I guess this might not be a problem for your HITS gameplay if playing 10 meters from the saddle instead of 2 when you can just right click on the ground (still difficult to get across the map).

The density of sprites is ignored by the game engine when calculating casualty rates.

I agree that the sprite ratio wouldn't change casualty rates, whereby of course, numerically it would be calculated from the unit's "center" as a data structure entity, but if less battalions (points and unit instances to process) regardless of the sprite ratio were allowed to engage at a time by merit of their not fitting, then there should be less units at any one time performing damage calculations upon the other based on their strength and population stats. Perhaps what I am getting at is that if the stock maps are in fact correctly scaled and bear actual distances, then potentially, our existing play styles are causing an unrealistic, mind physically impossible number of men to be engaging at the same time. That physical limits, if made to be roughly necessitated in the game, would coax us as players, along with the AI, to send in our battalions and brigades one at a time (as described in the mod manual) because they otherwise literally would not fit.

1) Brigades and divisions would take longer to deploy and battles would lengthen.

Perhaps not necessarily, for as with bearing a slower loading gun amid some fixed shooting window, one would simply be forced to shoot less bullets. Maybe I could read more into the historic pace of actual battles, but maybe our expectations for deployment speeds may still yet be mistaken. Perhaps our facing the logistic realities of these massive deployments may force us to play within more "realistic" limitations as to the number of things and movements that can be made within the span of a single battle, likewise limiting the amount of damage that can be practically dealt.

2) Maps would be smaller as divisions would encounter each other over greater distances.

Did you mean "more crowded" (occupying more space and frontage instead of bunching up) for the same map size? I'd still find that these spacing changes (if programmatically possible) would likewise limit the number of forces that can be engaged at a time and deter one's urge to amass one's troops into one area.

3) The KS Mod slows troop movements down somewhat from the stock game so a small change such as battalion spacing actually affects losses as well as a brigade taking longer to deploy will suffer more losses if it is under fire.

That may be the case, but perhaps, the point is that it may no longer be the case that a brigade has all of its battalions engaged at once, so only one or two battalions would ever be firing and under fire at any moment out of spatial necessity. Or in the case of two brigades, since only one brigade could actually "fit" in the desired frontage, then the other's remaining behind would counteract any additional losses due to "increased deployment times", or again, due to spacing constraints, there would be as many battalions to shoot at or be shot by as the brigade deploys.

Take d'Erlon's corps at Waterloo, for example. Here, we see d'Erlon's "brick" which probably spans the actual distance it would have in real life. Yet with the 200 sprite limit (which may have influenced the scenario designers' choice of unit spacing), the brigades of a division are placed side by side and thus together present a frontage of two battalions, wherefore as suggested on Wikipedia within footnote 65, it is more likely that they were placed one brigade after the other, being in all 8 to 9 battalions deep instead of 4 or 5, with the frontage of one battalion taking the place of the two seen in the stock Waterloo scenario (apparently, only Quiot's brigades were actually side by side). Due to the stock game's treatment of "battalion spacing", three of d'Erlon's "columns" end up perceiving the equivalent frontage of two battalions instead of the more likely and realistic one, from which one might expect the casualty rate of this scenario to have been "doubled" by the game. The described spacing changes would alter the scenario to have each pair of brigades forming one large line of battalions and now have extra space between them where the missing sprites would have fit. As we know, this formation was supposedly chosen because the brigades wouldn't have to spend any time deploying before firing.

4) Players naturally want to win so will always try to pack their battalions into the smallest possible space to gain the maximum firepower. Well spaced-apart formations would still crowd together when a player commands them which would give the human a significant advantage over an AI opponent who would obey the formation limitations more.

This may be akin to the "natural tendencies for us to play like a farb" which the KS Mod is already trying to combat. Again, if it were programmatically possible to prevent the player from packing their brigades together or implement overrides to limit the number and spacing of battalions presenting their arms (be it forcing them to retreat or move to or target the side instead), then players may learn to understand that you can only fit so many troops along a frontage for the same reason that we wouldn't want to condense our entire army of a hundred battalions into one dense point and sweep through the field like a black hole or cloud of lead; people experienced with Total War would understand this practical constraint despite their tendency to botch formations.

We changed from squadrons to regiments after an enormous amount of play testing, and I mean literally years...

I guess it is fair to sacrifice the aesthetic aspects in favour of OOBs that promote less out of control ravaging of squadrons all over the place or "fitting into every nook and cranny" (my interpretation of what was referenced in the mod manual). Still, I'd imagine that this act is condensing four or more autonomous "points" (from a programming and data structures perspective) into one, so a cavalry brigade's force becomes much more concentrated and localized than what their actual physical and historical volumetric presence would suggest, unless actual play experience from your games would counter this supposition. I read on napolun, the main site I tend to consult when not in reach of books, that "Napoleon said that '[the] squadron will be to the cavalry what the battalion is for infantry.'" This also goes into detail about squadron formations and their physical arrangement which is what I desire to be seen and fully simulated in my gameplay.

Perhaps to defend against the "lone wolf" squadrons which may be the vice of the stock cavalry OOBs perceived in your community, my approach has just been to keep my cavalry divisions far back on the Hold stance yet sufficiently close to respond, committing individual brigades or divisions when needed. I find that the AI manages to hold some back in reserve or to rest intermittently, not constantly pressing its entirety against the enemy unless in a desperate situation. And given that the squadron probably really was the most cohesive tactical and mobile cavalry unit around this time like the battalion was for infantry, it shouldn't be unreasonable for individual squadrons or pairs to occasionally "get lost", whereas the "regimental OOB" seems to completely do away with this. While the mass cohesion of entire regiments charging together may be desirable, maybe it's just that as it was for the historical commanders whereby in reality, coordinating a mass of 1,000 troopers really is as difficult as the stock game makes it to be. I'd personally feel the conglomeration of squadrons into mere regiments to be as detrimental as a conglomeration of multiple battalions into a single "brigade" unit (though Ultimate General: Gettysburg seems to be fine with this simplification).

If these changes are what allow your multiplayer games to go more smoothly, so be it, but I shall continue unto my pursuit of full immersion.

I may try some experimentation of my own if you were to give me a pointer to the game attributes that might be used to control this. Also, is there some centralized documentation on the specific coding changes that were made as part of the KS Mod, or a link to a repository I might look at? I might experiment with making a branch of the mod to experiment with this and the resulting effects on play.


Last edited by Mr. Haelscheir on Fri Feb 01, 2019 2:04 am; edited 1 time in total (Reason for editing : another image)
Mr. Haelscheir
Mr. Haelscheir

Posts : 9
Join date : 2017-09-04
Location : Canada

Back to top Go down

Re: Stock scenarios with KS Mod AAR

Post  Martin on Fri Feb 01, 2019 3:19 pm

I agree with you (and Napoleon) re squadrons Mr. Haelscheir.  To me, their agglomeration in regiments seems unhistorical, and also makes the battlefield appear unrealistic.  

As I understand it, the switch was made to prevent cavalry brigade commanders taking individual squadrons on speculative rides behind enemy lines.  But you can still do that with the regiments, and they have more staying-power.

IMHO a superior solution would be to prevent players taking the role of cavalry brigade commander.  Limit them to either a division, or an infantry brigade. Of course that may not stop division commanders riding off at the head of a squadron, but it would be at the huge cost of not commanding their division.  I suspect their team-mates would have something to say about that after the game.

Overall the KS mod seems much superior to the stock game, but I think this is one of the few areas where it is not.

Martin (J)


Last edited by Martin on Fri Feb 01, 2019 3:52 pm; edited 1 time in total

Martin

Posts : 2269
Join date : 2008-12-20
Location : London

Back to top Go down

Re: Stock scenarios with KS Mod AAR

Post  Uncle Billy on Fri Feb 01, 2019 3:30 pm

You've made a number of interesting suggestions, even touching on some points we here have discussed. The largest problem in implementing almost everything you've asked about is our lack of access to that particular piece of the game code. The code we have is only a piece of the AI code. There is another piece of that which controls where the corps, divisions and brigades place their sub-units and how they fight them. For the most part, that is beyond our ability to change.

For example, the KS mod makes some attempt to prevent battalions from overlapping. However, a battalion will only access the modded code every 10 sec. or so. Outside and hidden from us is another layer of code where the brigade cmdr. directly orders his battalions to certain locations without any seeming regard for overlap. It becomes a constant tug of war. Another example: Until recently we had no access at all to the x,y, values of a location of any unit. We still have no way of setting a numeric value for a unit to move to except via a roundabout way. Yet another example: Ever wonder why troops march through lakes, rivers and woods, which slows them down tremendously, when marching around the obstacle would be much faster and more sensible? No A* algorithm is in use. Or if it is, it is implemented so poorly as to be nearly worthless. It would be easy to implement one but again, there is no way to access the path array of any unit and give it the new course.

If you want to get a sense of what code we do have available to us, I suggest you download the SDK from the NSD site. The folders you want to look in are named SOWAIInf and SOWMod. The KS mod has rewritten large sections of that code. If you can program in C++ and are really interested in helping us improve the mod, you'd be most welcome to participate.

_________________
I can make this march and I will make Georgia howl.
Uncle Billy
Uncle Billy

Posts : 3323
Join date : 2012-02-27
Location : western Colorado

Back to top Go down

Re: Stock scenarios with KS Mod AAR

Post  risorgimento59 on Fri Feb 01, 2019 5:05 pm

Hi all.
Nice points raised by Mr. Haelscheir, as usual. Smile

Uncle Billy wrote:No A* algorithm is in use. ... It would be easy to implement one but again, there is no way to access the path array of any unit and give it the new course.

PRTerrain (heightmap, dimensions, etc.), grayscale map, collision grid/objects/polys, CHexMap, unit destpos and list of path nodes, etc.
I've got all their addresses more or less exactly marked in the v1.03 executable, Kevin.

I integrated imgui for debugging purposes lately...

Coded a multithreaded/SIMD-accelerated brute-force algorithm for AABB calculation and frustum culling...
SOWWL AABB/Frustum Culling algo:

Code:
static NorbWar::Unit *s_unitsCache[UINT16_MAX]; // precached pointers for linear iteration
 static std::size_t s_unitSpritesCountAccum[UINT16_MAX]; // in sprites

 auto unitsCacheWriteIt = std::begin(s_unitsCache);
 std::size_t unitsCacheSize{ 0U };
 auto spritesCountAccumIt = std::begin(s_unitSpritesCountAccum);
 std::size_t spritesCountAccum{ 0U };
 for (auto unitListIt = NorbWar::s_unitList_singleton.units.first; unitListIt != NorbWar::s_unitList_singleton.units.last; ++unitListIt) {
 const auto unit = *unitListIt;
 const bool unitVisibleGlobal = (unit->_unk_3b1_ & 0b100) == UINT8_C(0) /* DUPE */ && !unit->hidden && !unit->disabled;
 if (unitVisibleGlobal) {
 bool unitVisibleLocal = false;
 if (NorbWar::s_cfgDebugAlphaOmega) {
 if (unit->enemyUnitVisibleToLocalPlayer) {
 unitVisibleLocal = true;
 }
 }
 else {
 if (NorbWar::s_localPlayerOOBLink.side == 0 || unit->OOBLink.side == NorbWar::s_localPlayerOOBLink.side) {
 unitVisibleLocal = true;
 }
 else if (unit->enemyUnitVisibleToLocalPlayer) {
 unitVisibleLocal = true;
 }
 }
 if (unitVisibleLocal) {
 *unitsCacheWriteIt++ = unit;
 ++unitsCacheSize;
 spritesCountAccum += unit->numCurrentSprites;
 *spritesCountAccumIt++ = spritesCountAccum;
 }
 }
 }

 // unit frustum visibility culling parallel loop
 tbb::parallel_for(
 MyRange(std::size_t{ 0U }, unitsCacheSize, std::size_t{ 5000U }, s_unitSpritesCountAccum), // iterate over gameplay global/local visibility culling output list!
 [&unitsCache = s_unitsCache, &frustumPlanes = m_frustumPlanes](const MyRange &r) -> void {
 
 // ...
 __declspec(align(16)) float unitSpritesLocXBuf[16384];
 __declspec(align(16)) float unitSpritesLocYBuf[16384];
 __declspec(align(16)) msvcrt80::float_opt_64_t unitSpritesLocTempBuf[400]; // 200 * 2
 __declspec(align(16)) std::size_t unitSpritesLocBufOffset[256];

 std::size_t spritesBufOffset{ 0U };
 auto spritesBufLocXWriteIt = &unitSpritesLocXBuf[0];
 auto spritesBufLocYWriteIt = &unitSpritesLocYBuf[0];
 auto spritesBufLocOffsetWriteIt = &unitSpritesLocBufOffset[0];

 auto unitIdxIt = unitIndicesBegIt + r.m_lowerUnitIdx;

 for (auto unitIdx = r.m_lowerUnitIdx; unitIdx != r.m_upperUnitIdx; ++unitIdx)
 {
 const auto unitsCacheIdx = *unitIdxIt++;
 const auto unit = unitsCache[unitsCacheIdx];
 const auto unitNumOfSprites = unit->numCurrentSprites;
 for (std::size_t spriteIdx{ 0U }; spriteIdx != unitNumOfSprites; ++spriteIdx) {
 unitSpritesLocTempBuf[spriteIdx] = unit->sprites[spriteIdx].currPos.loc.x; // it1?
 unitSpritesLocTempBuf[spriteIdx + 200] = unit->sprites[spriteIdx].currPos.loc.y; // it2?
 }
 spritesBufLocXWriteIt = NorbWar::Utils::MSVCRT80::sse_unpack_float_opt_64_stream_to_ps(
 &unitSpritesLocTempBuf[0], unitNumOfSprites, spritesBufLocXWriteIt);
 spritesBufLocYWriteIt = NorbWar::Utils::MSVCRT80::sse_unpack_float_opt_64_stream_to_ps(
 &unitSpritesLocTempBuf[200], unitNumOfSprites, spritesBufLocYWriteIt);
 spritesBufOffset += unitNumOfSprites;
 auto spritesBufPadSize = (4U - (spritesBufOffset & 3U)) & 3U; // in sprites
 spritesBufOffset += spritesBufPadSize;
 while (spritesBufPadSize--) { // always less than 4! never span all the 4 lanes...
 /*
 X86 MIN/MAXPS semantics:
 If any value is NaN, return 2nd operand
 If both values are +/-0.0, return 2nd operand
 return a < b ? a : b (vice versa for Max)

 Note that if only one value is a NaN for this instruction, the source operand value (either NaN or valid floating-point value) is written to the result. This behavior allows compilers to use the MINPS instruction for common C conditional constructs. If instead of this behavior, it is required that the NaN source operand be returned, the minimum functional can be emulated using a sequence of instructions: a comparison followed by AND, ANDN and OR.
 */
 *spritesBufLocXWriteIt++ = *spritesBufLocYWriteIt++ = std::numeric_limits<float>::quiet_NaN(); // how is this handled by SIMD min/max?
 }
 *spritesBufLocOffsetWriteIt++ = spritesBufOffset;
 }


 // ...
 __declspec(align(16)) float unitAABB[1024]; // 4*minX-4*minY-4*maxX-4*maxY x 256/4 units

 __declspec(align(16)) float unitAABB4MinXYTemp[8];
 __declspec(align(16)) float unitAABB4MaxXYTemp[8];

 std::size_t spritesBufLowerOffset{ 0U };
 std::size_t unitAABB4BlockOffset{ 0U };
 auto unitAABBMinXWriteIt = &unitAABB[0];
 auto unitAABBMinYWriteIt = &unitAABB[4];
 auto unitAABBMaxXWriteIt = &unitAABB[8];
 auto unitAABBMaxYWriteIt = &unitAABB[12];

 for (auto spritesBufLocOffsetIt = &unitSpritesLocBufOffset[0]; spritesBufLocOffsetIt != spritesBufLocOffsetWriteIt; ++spritesBufLocOffsetIt)
 {
 spritesBufOffset = *spritesBufLocOffsetIt;
 auto spritesBufLocXReadIt = reinterpret_cast<const __m128 *>(&unitSpritesLocXBuf[spritesBufLowerOffset]);
 auto spritesBufLocYReadIt = reinterpret_cast<const __m128 *>(&unitSpritesLocYBuf[spritesBufLowerOffset]);
 spritesBufLowerOffset = spritesBufOffset;

 __m128 unitAABB4MinX, unitAABB4MinY, unitAABB4MaxX, unitAABB4MaxY; // init them to some value!!!
 unitAABB4MinX = unitAABB4MinY = _mm_set1_ps(std::numeric_limits<std::float_t>::max()); // to constants?
 unitAABB4MaxX = unitAABB4MaxY = _mm_set1_ps(std::numeric_limits<std::float_t>::lowest()); // to constants?
 while (spritesBufOffset) {
 {
 __m128 locX = _mm_load_ps(reinterpret_cast<const float *>(spritesBufLocXReadIt++));
 unitAABB4MinX = _mm_min_ps(unitAABB4MinX, locX);
 unitAABB4MaxX = _mm_max_ps(unitAABB4MaxX, locX);
 }
 {
 __m128 locY = _mm_load_ps(reinterpret_cast<const float *>(spritesBufLocYReadIt++));
 unitAABB4MinY = _mm_min_ps(unitAABB4MinY, locY);
 unitAABB4MaxY = _mm_max_ps(unitAABB4MaxY, locY);
 }
 spritesBufOffset -= 4U;
 }
 _MM_TRANSPOSE4_PS(unitAABB4MinX, unitAABB4MinY, unitAABB4MaxX, unitAABB4MaxY);
 {
 __m128 minXY;
 minXY = _mm_min_ps(unitAABB4MinX, unitAABB4MinY);
 minXY = _mm_min_ps(minXY, unitAABB4MaxX);
 minXY = _mm_min_ps(minXY, unitAABB4MaxY);
 _mm_store_ps(&unitAABB4MinXYTemp[unitAABB4BlockOffset], minXY); // 0-1, 4-5
 }
 {
 __m128 maxXY;
 maxXY = _mm_max_ps(unitAABB4MinX, unitAABB4MinY);
 maxXY = _mm_max_ps(maxXY, unitAABB4MaxX);
 maxXY = _mm_max_ps(maxXY, unitAABB4MaxY);
 _mm_store_ps(&unitAABB4MaxXYTemp[unitAABB4BlockOffset], maxXY); // 2-3, 6-7
 }
 if ((unitAABB4BlockOffset = (unitAABB4BlockOffset + 4U) % 8U) == 0U) {
 *unitAABBMinXWriteIt++ = unitAABB4MinXYTemp[0]; // minX
 *unitAABBMinXWriteIt++ = unitAABB4MinXYTemp[4]; // minX
 *unitAABBMinYWriteIt++ = unitAABB4MinXYTemp[1]; // minY
 *unitAABBMinYWriteIt++ = unitAABB4MinXYTemp[5]; // minY
 *unitAABBMaxXWriteIt++ = unitAABB4MaxXYTemp[2]; // maxX
 *unitAABBMaxXWriteIt++ = unitAABB4MaxXYTemp[6]; // maxX
 *unitAABBMaxXWriteIt++ = unitAABB4MaxXYTemp[3]; // maxY
 *unitAABBMaxXWriteIt++ = unitAABB4MaxXYTemp[7]; // maxX
 }
 }


 // ...
 std::int32_t unitFrustumCullingRes[UINT16_MAX];

 const auto camera = PowerRender::PR.GameEngine.Camera;

 unitAABBCullingSSE(
 frustumPlanes.x,
 frustumPlanes.y,
 frustumPlanes.z,
 frustumPlanes.d,
 r.m_upperUnitIdx - r.m_lowerUnitIdx,
 reinterpret_cast<const __m128 *>(&unitAABB[0]), // offset AABBs by a fixed radius? sprite positions are just dimensionless points. taking spritepack dims into consideration would slow everything down too much, probably...
 camera->Position.y + camera->Direction.y * camera->NearClip,
 reinterpret_cast<__m128i *>(&unitFrustumCullingRes[0]));

 // TODO
 // interlocked memcpy of unitcullingres?
 // or preallocate a memory buffer to which only the task processing the unit can write (bad for cpu caching? not severe... range guarantees pretty much memory contiguous looping)

 },
 tbb::simple_partitioner()
 );
Heightmap bilinear sampling for units, SIMD-accelerated as well...
SOWWL Heightmap BI:

Code:
const auto terrain = PowerRender::PR.GameEngine.terrain;
 const auto hm = reinterpret_cast<const std::uint16_t *>(terrain->HeightMap);

 const __m128 kTerrainInvWorldScale = _mm_set1_ps(1.f / terrain->WorldScale);
 const __m128i kTerrainHeightmapStride = _mm_set1_epi32(terrain->Width); // I MAY GO OFF MEMORY BOUNDS HERE!!! ADD x == img->width - 1 CHECK?
 const __m128 kTerrainHeightScale = _mm_set1_ps(terrain->HeightScale); // I MAY GO OFF MEMORY BOUNDS HERE!!! ADD x == img->width - 1 CHECK?
 const __m128 k1111f = _mm_set1_ps(1.f);

 __m128 px, py;
 __m128i pxi, pyi;
 __m128 pxFloor, pyFloor;
 __m128 pxFrac, pyFrac;
 __m128 v1, v2;
 __m128 w[4];
 __m128i o[2];
 __m128 h[4];

 __declspec(align(16)) std::int32_t terrainHeightmapOffsets[8];
 __m128i *hmOffsetsAsM128BegIt = reinterpret_cast<__m128i *>(terrainHeightmapOffsets);
 __m128i *hmOffsetsAsM128It;
 const std::int32_t *hmOffsetsAsU32BegIt = std::begin(terrainHeightmapOffsets);
 const std::int32_t *hmOffsetsAsU32It;
 //const std::int32_t *hmOffsetsAsU32EndIt = std::end(terrainHeightmapOffsets);

 std::uint16_t terrainHeightmapSamples[4];
 const auto hM128BegIt = std::begin(h);
 const auto hM128EndIt = std::end(h);
 __m128 *hM128It;

 for (const auto &t : boost::adaptors::stride(boost::make_iterator_range(
 boost::make_zip_iterator(boost::make_tuple(std::begin(d.currPosX), std::begin(d.currPosY), std::begin(d.currPosHeight))),
 boost::make_zip_iterator(boost::make_tuple(std::begin(d.currPosX) + d.count, std::begin(d.currPosY) + d.count, std::begin(d.currPosHeight) + d.count))
 ), 4))
 {
 auto &height = boost::get<2>(t);
 if (height == UINT32_MAX) continue;

 // Load world position of sprites
 const auto &x = boost::get<0>(t);
 const auto &y = boost::get<1>(t);
 px = _mm_load_ps(reinterpret_cast<const float *>(&x));
 py = _mm_load_ps(reinterpret_cast<const float *>(&y));
 // World to heightfield pixel space
 px = _mm_mul_ps(px, kTerrainInvWorldScale);
 py = _mm_mul_ps(py, kTerrainInvWorldScale);
 // Truncate pixel space coords to integer
 pxi = _mm_cvtps_epi32(px);
 pyi = _mm_cvtps_epi32(py);
 // Takes floored and fractional part, as floats
 pxFloor = _mm_cvtepi32_ps(pxi);
 pyFloor = _mm_cvtepi32_ps(pyi);
 pxFrac = _mm_sub_ps(px, pxFloor);
 pyFrac = _mm_sub_ps(py, pyFloor);

 // Compute weights
 v1 = _mm_sub_ps(k1111f, pxFrac); // 1 - px
 v2 = _mm_sub_ps(k1111f, pyFrac); // 1 - py
 w[0] = _mm_mul_ps(v1, v2); // (1 - px) * (1 - py)
 w[1] = _mm_mul_ps(pxFrac, v2); // px * (1 - py)
 w[2] = _mm_mul_ps(v1, pyFrac); // (1 - px) * py
 w[3] = _mm_mul_ps(pxFrac, pyFrac); // px * py

 // Offsets in elements of sizeof(std::uint16_t)
 /*o[0] = _mm_mul_epu32(pxi, kTerrainHeightmapStrideInBytes);
 o[1] = _mm_mul_epu32(pyi, k2222); // += pyi * sizeof(std::uint16_t)
 o[0] = _mm_add_epi32(o[0], o[1]);
 o[1] = _mm_add_epi32(o[0], kTerrainHeightmapStrideInBytes);
 _mm_unpacklo_epi32(o[0], o[1]);
 _mm_unpackhi_epi32(o[0], o[1]);
 _mm_store_si128(reinterpret_cast<__m128i *>(&terrainHeightmapOffsets[0]), o[0]);
 _mm_store_si128(reinterpret_cast<__m128i *>(&terrainHeightmapOffsets[4]), o[1]);*/

 // Calculate offsets in elements of sizeof(std::uint16_t)
 o[0] = _mm_mul_epu32(pxi, kTerrainHeightmapStride);
 o[0] = _mm_add_epi32(o[0], pyi);
 o[1] = _mm_add_epi32(o[0], kTerrainHeightmapStride);
 hmOffsetsAsM128It = hmOffsetsAsM128BegIt;
 _mm_store_si128(hmOffsetsAsM128It++, _mm_unpacklo_epi32(o[0], o[1]));
 _mm_store_si128(hmOffsetsAsM128It, _mm_unpackhi_epi32(o[0], o[1]));

 // Heightmap 4 x values sampling
 /*for (hmSamplesAsU32It = hmSamplesAsU32BegIt, hmOffsetsAsU32It = hmOffsetsAsU32BegIt; hmSamplesAsU32It != hmSamplesAsU32EndIt; ++hmSamplesAsU32It, ++hmOffsetsAsU32It) {
 *hmSamplesAsU32It = *reinterpret_cast<const std::uint32_t *>(&hm[*hmOffsetsAsU32It]);
 }*/
 hmOffsetsAsU32It = hmOffsetsAsU32BegIt;
 for (hM128It = hM128BegIt; hM128It != hM128EndIt; ++hM128It) {
 reinterpret_cast<std::uint32_t *>(&terrainHeightmapSamples)[0] = *reinterpret_cast<const std::uint32_t *>(&hm[*hmOffsetsAsU32It++]);
 reinterpret_cast<std::uint32_t *>(&terrainHeightmapSamples)[1] = *reinterpret_cast<const std::uint32_t *>(&hm[*hmOffsetsAsU32It++]);
 *hM128It = _mm_cvtepi32_ps(_mm_set_epi32(terrainHeightmapSamples[0], terrainHeightmapSamples[1], terrainHeightmapSamples[2], terrainHeightmapSamples[3]));
 }

 //h[0] = _mm_cvtpu16_ps(xxx/*_mm_set_pi32(terrainHeightmapSamples[0], terrainHeightmapSamples[1])*/);
 //h[1] = _mm_cvtpu16_ps(_mm_set_pi32(terrainHeightmapSamples[2], terrainHeightmapSamples[3]));
 //h[2] = _mm_cvtpu16_ps(_mm_set_pi32(terrainHeightmapSamples[4], terrainHeightmapSamples[5]));
 //h[3] = _mm_cvtpu16_ps(_mm_set_pi32(terrainHeightmapSamples[6], terrainHeightmapSamples[7]));
 _MM_TRANSPOSE4_PS(h[0], h[1], h[2], h[3]);

 // dot(h,w)
 h[0] = _mm_mul_ps(h[0], w[0]);
 h[1] = _mm_mul_ps(h[1], w[1]);
 h[2] = _mm_mul_ps(h[2], w[2]);
 h[3] = _mm_mul_ps(h[3], w[3]);
 h[0] = _mm_add_ps(h[0], h[1]);
 h[0] = _mm_add_ps(h[0], h[2]);
 h[0] = _mm_add_ps(h[0], h[3]);

 // Scale the height
 h[0] = _mm_mul_ps(h[0], kTerrainHeightScale);

 // h[0] contains heights interpolated... for 4 sprites
 _mm_store_si128(reinterpret_cast<__m128i *>(&height), _mm_cvtps_epi32(h[0]));
 }

I reiterate that if there's any serious intention to get around the obstacles caused by the lack of availability of the source code, the sincere will to cooperate and a consistent modding plan to achieve clear and common goals, I'd be glad to give a hand or just sharing more stuff.
Still loving this amazing game, after all. santa

In all cases, take care, respected Kriegsspielers! pirat

risorgimento59

Posts : 24
Join date : 2015-06-19

Back to top Go down

Re: Stock scenarios with KS Mod AAR

Post  Mr. Haelscheir on Thu Feb 14, 2019 7:52 pm

Clarifications:

I just wanted to clarify what I meant by
self wrote:...is there some centralized documentation on the specific coding changes that were made as part of the KS Mod, or a link to a repository I might look at? I might experiment with making a branch of the mod to experiment with this and the resulting effects on play.

Uncle Billy wrote:If you want to get a sense of what code we do have available to us, I suggest you download the SDK from the NSD site. The folders you want to look in are named SOWAIInf and SOWMod. The KS mod has rewritten large sections of that code. If you can program in C++ and are really interested in helping us improve the mod, you'd be most welcome to participate.

I downloaded and examined the SDK source files quite a while ago (I assume this is the latest though the manual in my current installation says "Version 1.00"?). While this may give me a good idea of where to look, which I have tried in the past, I was wondering if you had some centralized "repository" (e.g. GitHub) from which I can examine or branch the KS Mod's source code to see what concrete improvement techniques have already been used so I can work from there instead of reinventing the wheel. I'd imagine you guys would have needed some form of source control to collaborate in developing and deploying that mod.

On fine-tuning AI:

Uncle Billy wrote:We still have no way of setting a numeric value for a unit to move to except via a roundabout way.

What about XUnit.SetDest()? (I assume CXPos parameters and return values simply add the unit's orientation.) I guess direct usage of it would have the AI preferring to go in a straight line, but I guess an AI can be designed to divide received movement orders into multiple SetDest() calls that appropriately navigate around obstacles. As for that "additional layer" of brigade commander code, are you saying that even with a completely altered SowUnitBrigThink() (I assume SowInfAIFunc() and Think() comprise the mind of an individual battalion), there is some other background code that could override any attempts at regulating battalion spacing? Or is this "brigadier override" only seen as part of "division play" whereby it should be possible to "re-implement" the functionality of brigade formations using SetDest() as a replacement for the stock/vanilla brigade formation management code.

Otherwise, I will continue working on a system such that all information transmitted between AI officers comprises strings of natural language alone. With that AI, I may wish to bypass as many of the stock/hidden routines and properties (e.g. "stances" and "states") for whatever algorithms my new AI "learns". Again, I will need a mechanism for persisting "minds" separate from the CXUnit class which can be looked up by ID. Provided that the data structure is sufficiently lightweight, we will then have the problem of linking OOB battalions and officers to file-persisted mind data structures and "training" each of these to have a certain set of competencies and personality. While xlink.h seems to expose the fundamental interface through which to interact with the game world (though I guess we have to query the terrain bitmaps externally to give the AIs more fine-grained "vision"), with all other functions other than the periodic callbacks and combat mechanics being auxiliary, my aim will be to turn all of the stock "thinking" routines and fields into dead code. I am guessing that CXUnit.Command() is effectively the link to the stock interpreter for the stock order representation whose more complex orders would be replaced by my natural language processing AI.

On clarifying AI communications:

In pursuing this aim, I recently figured out how CXOffs send orders and interpret them. I am curious as to why SendOrdersByCour() is only used at the division level; might I guess that this method isn't the only way to trigger the spawning of a courier? Likewise, would this be the only reliable method to have an AI at any command level send any generated string for interpretation by a subordinate? It seems that the Orders() method is used only at the brigade level, though it is unclear if "setting" the order is for the brigadier itself, or if it dispatches the order to its subordinates. Also, do we have any info on what OffThink() actually does? Also, what is the relationship between TTacOrders and SXComms? Particularly, which would be most appropriate for sending string commands that can be translated into concrete actions?

As for the CSoldCour1Think() code, it seems that couriers can either carry an "order string" to be parsed into a single SXComm and added to what seems to be a "global command queue" via CXUtil.AddComm(), or instead carry a list of SXComm orders to all be added to the queue. In that same code, the courier can construct an SXComm instance and feed this to its Command() function in order to make it do something in the game world. I thus find that this reflexive Command()/ICommand()/"Do()" function is the means through which an AI enacts agency over itself, whereby it can form these "self-commands" from its internal and external state as well as orders from its superiors. As for receiving orders from superiors, one method is direct or synchronous, that being the GetInfCommand() method and such for allowing battalions to get situational "advice" from their brigadiers. As for asynchronous orders that can be received at any time, the means for retrieving it seems to vary with different levels of command; e.g. GetOrders() at the division level acquires TTacOrders from the corps commander I presume, and CXOff.AssignedOrders() and SXComm.Param() at the brigade level I presume interpret orders from the division commander.

The questions are thus the following:

  • At each command level, which methods apply the command to the agent itself, and which directly dispatch a command to a subordinate?

  • Given those methods, which actually generate a courier through which CSoldCour1Think() is executed and orders are posted to the command queue? Since artillery officers and division commanders can also invoke CXUtil.AddComm() in certain situations, it seems as though this method represents the direct "shouting" of orders or handing a subordinate a dispatch directly, whereby SendOrdersByCour() explicitly adds a travelling courier overhead to delegate the task of passing on the order. I would thus suspect that it is complex formation commands sent into CXUnit.Command() at the brigade level and up which generate more complex courier dispatch patterns behind the scenes compared to the granular SendOrdersByCour(). While division commands can talk to their brigadiers individually, it seems that the current implementation leverages the stock implementation of Command() to dispatch all the couriers required to move the battalions into formation. It is my belief that methods such as SendOrdersByCour() and interpreters within a modified Think() method that translate commands into Command() and SetDest() calls etc. can be used to fully re-implement many of the more complex ECommands to account for realistic battalion spacing.

  • Once an order is placed on the command queue directly by an officer or indirectly by a courier, how and when does the target receive those orders? E.g. What populates AssignedOrders(): CXOff.Orders(), AddComm(), or other? When are the GetOrders() methods relevant? In the end, how do I get a string message from A to B?

  • What is the courier code replaced by on difficulty modes that disable couriers at particular levels?

  • Can all battalion and officer behaviour be regarded as originating from the "Think()" functions, or are there some nefarious background tasks that will always interfere with our "fixes"? (I am trying to gauge whether the problem of the AI always overriding desired custom formations and spacing is an avertible "illusion"; it would be fairly easy to mod TC mode out of existence.)

I guess I feel like we've been long faced by the illusion of the immutability of the described algorithm whereby the real option is to simply not use them and rebuild their functionality from fundamental operations. Yes, Command() or ReEvalTactics() lead to delegates to untouchable code, but who says we have to use them in order to emulate a full chain of command? Please tell me if I am misunderstanding some constraint.

P.S. When I realized that the SDK's "AI Function Library.xlsx" file had a sheet for each class - no wonder why I couldn't find some function descriptions...
Mr. Haelscheir
Mr. Haelscheir

Posts : 9
Join date : 2017-09-04
Location : Canada

Back to top Go down

Re: Stock scenarios with KS Mod AAR

Post  Uncle Billy on Thu Feb 14, 2019 10:34 pm

That's quite the laundry list. I'll comment on some of these questions/guesses.

There is no GitHub repository for the KS code. It resides on my computer as I'm the only one that modifies the code. I can send you a dropbox link for the code. However rather than working independently, I'd rather it be a collaborative effort to improve the KS mod. As far as I know, the KS mod is the only one publicly offered that modifies the AI. All others merely tinker with the values in the csv files.

What about XUnit.SetDest()?
It takes a CXVec as an argument. CXVec has private variables x & y. There is no CXVec.SetX() or SetY(). To get around this I have to use the SXComm.BuildCommand() and us the scripting command Amovespec:loc:xvalue-yvalue.

With that AI, I may wish to bypass as many of the stock/hidden routines and properties (e.g. "stances" and "states") for whatever algorithms my new AI "learns".
I have no idea of how to do that. You can turn off the AI completely, but then you will have no way to interface with the game engine. There is no partial shut-off switch.

I am curious as to why SendOrdersByCour() is only used at the division level;
It can be used at any level of command. NSD just chose to use it in the AI code at the division level. All other orders in the AI code are transmitted instantaneously.

Command()/ICommand()
This is primary way the AI code passes its desires to the game engine. As you know it uses the ECommand values to pass on the required action and uses the SXComm Vec, Dir, Param and Val functions to flesh out the command as necessary.

As for asynchronous orders that can be received at any time, the means for retrieving it seems to vary with different levels of command; e.g. GetOrders() at the division level acquires TTacOrders from the corps commander I presume, and CXOff.AssignedOrders() and SXComm.Param() at the brigade level I presume interpret orders from the division commander.
NSD only uses GetOrders() for a brigade commander when the brigade is not moving or fighting but there is an enemy unit in view. It simply orders a formation change and movement towards that enemy if the morale level permits it. I've never done anything different with it.
TTacOrders.GetOrders() returns the officer's stance and values associated with that stance. The KS mod makes some use of it in the officer decision making process. It makes much more use of GetStance as the TableStance is of much greater use.
CXOff.AssignedOrders() simply generates the text string of the strategic orders from the corps cmdr. to the div. cmdr. That text is written in the log file and on the screen for the player. You cannot pass any information to the game engine that way.

Given those methods, which actually generate a courier through which CSoldCour1Think() is executed and orders are posted to the command queue?
CSoldCour1Think() is used at any level whenever a courier is created and sent off to deliver an order. When courier by brigade is used as a game setting, couriers will be sent whenever a player orders a subordinate to move or change formation. That subordinate will also send couriers to his subordinates with the appropriate orders. Player to player messages are also handled by this function as are any KS unit to player information messages.

It is my belief that methods such as SendOrdersByCour() and interpreters within a modified Think() method that translate commands into Command() and SetDest() calls etc. can be used to fully re-implement many of the more complex ECommands to account for realistic battalion spacing.
Only for a very brief moment. As soon as the game engine loops back to the brigade officer, it will supersede your idea proper spacing with its own.

how do I get a string message from A to B?
Use the ECommand, eComCourDoc, with the text being put into the Param() function. However, the only way I've found to extract that text at the receiving end is via CXCourier.ShowScreen(). That pops up the text on the player's screen. I suspect that is not what you are after, however.

Can all battalion and officer behaviour be regarded as originating from the "Think()" functions, or are there some nefarious background tasks that will always interfere with our "fixes"?
The game engine has its own piece of AI code which decides how units move and how they are positioned to fight. It will always override your intentions in this regard. In general, the "Think()" functions provide tactical responses to specific battle situations, (e.g. what to do if a unit is being flanked, what to do if a unit is being charged, what to do if the unit approaches to within a certain distance to the enemy, etc.).

I guess I feel like we've been long faced by the illusion of the immutability of the described algorithm whereby the real option is to simply not use them and rebuild their functionality from fundamental operations. Yes, Command() or ReEvalTactics() lead to delegates to untouchable code, but who says we have to use them in order to emulate a full chain of command? Please tell me if I am misunderstanding some constraint.
At the strategic level the AI decisions can to some extent be bypassed with our own substituted. The KS mod does a bit of that. But that's not where the game engine is weakest. The weak link of the game and something NSD was never able to grasp is what to do when the enemy comes into range. It is here that the game engine fails. The game engine just attacks. The divisions order the brigades move the battalions to engage the enemy. To be fair to NSD, that is a hard problem. It requires a good knowledge of the period's tactical thinking and then the hard work of turning that into code.

_________________
I can make this march and I will make Georgia howl.
Uncle Billy
Uncle Billy

Posts : 3323
Join date : 2012-02-27
Location : western Colorado

Back to top Go down

Re: Stock scenarios with KS Mod AAR

Post  Mr. Haelscheir Yesterday at 2:05 am

Uncle Billy wrote:It takes a CXVec as an argument. CXVec has private variables x & y. There is no CXVec.SetX() or SetY(). To get around this I have to use the SXComm.BuildCommand() and use the scripting command Amovespec:loc:xvalue-yvalue.

How about GetLocAhead() et al.? I don't know what provisions they have for CXVec arithmetic, but wouldn't this and the wheeling commands be fundamental to fine-tuned custom-AI-controlled movement?

Uncle Billy wrote:Command()/ICommand() ... This is the primary way the AI code passes its desires to the game engine. ... Only for a very brief moment. As soon as the game engine loops back to the brigade officer, it will supersede your idea of proper spacing with its own. ... The game engine has its own piece of AI code which decides how units move and how they are positioned to fight. It will always override your intentions in this regard.

For movement, I understand that of course, the modder won't be holding the unit's hand all the way through every inch of its march. I accept that the Command() function will "fire off" some background routines to fulfill the details of the order (I've implemented "military boids" before that run virtual machine code "scripts" to plan their movements and signal other boids), and I am fine with the battalion-level combat mechanics being out of our control (e.g. let the game engine manage how the muskets fire and how casualties are deducted, perhaps influenced by stances and past orders), but you are saying that the game engine at the brigade level and on will nigh always override your battalion level movement commands? Wouldn't this only happen if we previously passed an eComMove command to the Command() method whereby we would expect the brigadier eComMove routine to constantly "keep dressing his troops" to the last assumed formation? Did you grant that we can "bypass" those commands with our own movement algorithms built upon more "fundamental" ECommands (e.g. split up a long-distance eComMove into smaller parts or waypoints that are less prone to issues)? If so, I guess what we have left is the problem you mentioned with the built-in combat mechanics. Would you regard this as one of the only "automatic" AI routines that get triggered independent of routines set off from the Command() function? That is, are there things the "hidden" AI would do even if all units had no orders or stances and all "Think()" functions were empty? E.g. Would a brigadier or division commander suddenly start dishing out dispatches upon an enemy coming near, even if their "Think()" methods were completely empty? If so, are there csv engagement range values we could set to make these triggers extremely rare? To what extent are commands merely "suggestions" such that the "Think()" methods are really just "micromanagement helpers" put in place of humans?

Perhaps, if you could list out all of the main known "unexpected/undesirable" automatic phenomena and whether they can be triggered from "Think()" code, we could gain the large picture with which to circumvent them.

For SXComm.Param(), would there be any reason to set the index to anything other than 1? Also, I take that the "bitstreams" in SXComm are rather "bit fields".

Uncle Billy wrote:Use the ECommand, eComCourDoc, with the text being put into the Param() function. However, the only way I've found to extract that text at the receiving end is via CXCourier.ShowScreen().

Can you tell me what items you can extract when placed into a courier? As for "getting from A to B", whenever a command is "placed into the mailbox" by say a courier via AddComm(), how does the AI code within a "Think()" function's stack query the contents of that order so that it can react accordingly or delegate the received SXComm instance to its own Command() method? Else, is AddComm() little more than a way for one unit to call Command() for another? If so, would it be impossible to implement a separate and orthogonal mailbox/message queue data structure for transmitting data of any form we desire between CXUnits? CXUnits would simply push to and pull from a globally accessible message queue instance (one per commander). And what are the limitations on adding library dependencies such as message queues within the mod, if any?

I am not trying to "shut off" the AI entirely so much as circumvent as much of it as possible with my own implementations. Again, my view for the requirements of a good "military AI playground" are a reasonable array of sensors and effectors. To me, the only important game interface methods within xlink.h are those that allow an officer AI to examine its surroundings (I know that a division commander's subordinates can be queried, and hopefully also a brigadier's), and those that provide the bare fundamentals for interacting with the game world. Thus, for now, I see "mind data structure persistence", inter-CXUnit communication, and "unThinkable" tactical AI reflexes as the main bottlenecks.
Mr. Haelscheir
Mr. Haelscheir

Posts : 9
Join date : 2017-09-04
Location : Canada

Back to top Go down

Re: Stock scenarios with KS Mod AAR

Post  risorgimento59 Yesterday at 10:57 am

A while ago I made experiments to deal with some of those obstacles through a special courier unit type class.
If I remember rightly, in its dedicated AI routines it used combinations of eComAddCourTxt/Orders() as memory storage, -1 TargId to avoid comms being delivered, etc. to outline a sort of "persistency agent" for being attached to officers and support their decision making process.
If i were you, I'd take a look down this route for implementing blackboards, message queues, influence maps and similar stuff.
Possibly it'd serialize "naturally" across savegames too.


Last edited by risorgimento59 on Fri Feb 15, 2019 11:07 am; edited 2 times in total

risorgimento59

Posts : 24
Join date : 2015-06-19

Back to top Go down

Re: Stock scenarios with KS Mod AAR

Post  risorgimento59 Yesterday at 11:05 am

About the "unThinkable" interferences...
They were a major cause of dissuasion for myself too.
http://kriegsspiel.forumotion.net/t1989p25-cannon-fire-v-musket-fire#24325
But I don't remember how far did I went into debugging that "dummy dll", honestly.
Too much water flowed under the bridge since then.

And good luck. Wink

risorgimento59

Posts : 24
Join date : 2015-06-19

Back to top Go down

Re: Stock scenarios with KS Mod AAR

Post  Uncle Billy Yesterday at 3:49 pm

How about GetLocAhead() et al.? I don't know what provisions they have for CXVec arithmetic, but wouldn't this and the wheeling commands be fundamental to fine-tuned custom-AI-controlled movement?
Yes, that does work, however the BuildCommand technique I outlined is easier to use. We have CXVec.GetX() & GetY() available, so locations of other units and positions relative to them are easy to get.

but you are saying that the game engine at the brigade level and on will nigh always override your battalion level movement commands? Wouldn't this only happen if we previously passed an eComMove command to the Command() method whereby we would expect the brigadier eComMove routine to constantly "keep dressing his troops" to the last assumed formation?
The game engine AI, (GEAI), will override our AI commands whenever it feels the need to do so. What constitutes that need/desire is unknown. Certainly during the heat of battle, when the brigade is engaged, the GEAI will issue new commands based on it's assessment of the situation. I don't know if it periodically imposes its will on the units during a road march, for example. However it will do so when the enemy first comes into view.

That is, are there things the "hidden" AI would do even if all units had no orders or stances and all "Think()" functions were empty? E.g. Would a brigadier or division commander suddenly start dishing out dispatches upon an enemy coming near, even if their "Think()" methods were completely empty?
I don't know. My guess is that it would but the units will behave very stupidly.

For SXComm.Param(), would there be any reason to set the index to anything other than 1? Also, I take that the "bitstreams" in SXComm are rather "bit fields".
The index value seems to make no difference. I doubt the entire SXComm is a long bit field, rather it probably fills a command structure. The one bit variable, ChkBit is likely just a tip of the hat to 70s and 80s programming.

Can you tell me what items you can extract when placed into a courier?
The ECommand can be extracted as well as ETacType.

As for "getting from A to B", whenever a command is "placed into the mailbox" by say a courier via AddComm(), how does the AI code within a "Think()" function's stack query the contents of that order so that it can react accordingly or delegate the received SXComm instance to its own Command() method? Else, is AddComm() little more than a way for one unit to call Command() for another?
I'm not sure what the prioritization is. I'm not even convinced there is any necessity for AddComm(). One can stack Command() orders and have them carried out in series. They can be sent to any unit via SXComm.Unit() function.

If so, would it be impossible to implement a separate and orthogonal mailbox/message queue data structure for transmitting data of any form we desire between CXUnits? CXUnits would simply push to and pull from a globally accessible message queue instance (one per commander). And what are the limitations on adding library dependencies such as message queues within the mod, if any?
It is certainly possible. However to get the units to do anything you'd still have to rely on the game engine. The KS mod has a large data structure for implementing and keeping track of various KS events. However at the end of the line, calls still have to be made to Command().

To me, the only important game interface methods within xlink.h are those that allow an officer AI to examine its surroundings (I know that a division commander's subordinates can be queried, and hopefully also a brigadier's), and those that provide the bare fundamentals for interacting with the game world.
Your hopes will go unfulfilled. The units are mostly blind to their surroundings. They only know and react to a very small piece of the battlefield. That piece is mostly limited to the closest enemy unit. The KS mod has been giving the units the necessary vision. It has routines to determine the enemy line, it's center of mass, whether enemy units are in front of its own line or behind it, etc. Things that most people take for granted when assessing the battlefield are completely missing from the stock AI.

_________________
I can make this march and I will make Georgia howl.
Uncle Billy
Uncle Billy

Posts : 3323
Join date : 2012-02-27
Location : western Colorado

Back to top Go down

Re: Stock scenarios with KS Mod AAR

Post  risorgimento59 Yesterday at 7:43 pm

Uncle Billy wrote:    
What about XUnit.SetDest()?

It takes a CXVec as an argument. CXVec has private variables x & y. There is no CXVec.SetX() or SetY().

CVec holds 2 fixed-point numbers (*).
The size in memory of each one being 8 bytes.
28 bits of them representing the decimal part.
So converting from a plain integer should result as straightforward as shifting the source number 28 bits to the left...
Untested utility code snippet:
Code:
void set_cvec(CVec *v, int x, int y) {
 _mm_storeu_si128(reinterpret_cast<__m128i *>(v), _mm_slli_epi64(_mm_set_epi64x(x, y), 28));
}

* Epic 10-years old account of the reasons behind the choice, directly from Norb's pen:
http://www.norbsoftdev.net/articles-mainmenu-59/news-mainmenu-2/latest-news-mainmenu-57/85
study

risorgimento59

Posts : 24
Join date : 2015-06-19

Back to top Go down

Re: Stock scenarios with KS Mod AAR

Post  Sponsored content


Sponsored content


Back to top Go down

Back to top

- Similar topics

 
Permissions in this forum:
You cannot reply to topics in this forum