Improving muscle strain repetitive use mechanics #59864
Description
Is your feature request related to a problem? Please describe.
#59862 adds a mechanic, "strain", which tracks the use and overuse of muscles during strenuous activity as a new physical resource you can exhaust, replacing stamina (now called "breath") in places where we're talking more about contractility than aerobic sustainability. However, "strain" on its own recovers very quickly. It is designed to require a secondary not-quite-as-short-term complementary mechanic that makes it recover a bit less quickly in the short term the more times you exhaust it.
Solution you would like.
"Lactate" is a nickname for "building up strain until muscle failure". I have called it this more because of the colloquial understanding that lactic acid buildup gives you that muscle burn feeling, even though the mechanic itself doesn't just model that. Note that the name should not be mentioned in game and whoever codes this can call it whatever they want, that's just what I'm calling it in this issue for brevity. Please stop debating what it should be called in the comments.
If you let lactate accumulate it slows strain recovery and increases weariness. However this will also increase muscle gains a lot when we have a system for that.
- When
strain
reaches 50%, every time strain increases further, addlactate
equal toL * strain_change / base_strength
, whereL=1
. Notelactate
is a float.- Increase L by 1 at 75%, 90%, and 100%.
- lactate does not increase if your net strain decreased this turn.
- Every turn, if your strain is not increasing, decrease lactate by
std::min( base_strength/current_strain, 1)
or 1 if current_strain = 0. -
- Before reducing lactate, add a number of kcal equal to
std::round(lactate)
to the weariness tracker. Note that this does not add more calories to dietary needs, it's a surrogate to represent your muscles wearing out.
- Before reducing lactate, add a number of kcal equal to
Math check and notes
If you are swinging a light weapon and building 5 strain per turn, at str8, you'll hit 50% strain after 8 swings, then swing 4 more times gaining 4*(5/8)=2.5
lactate to hit 75%, then swing 3 more times gaining 6*(5/8)
=3.75 lactate to hit >90%, then your last swing will gain you 4*(5/8)=2.5
lactate and put you at 100% strain (you can force four more increasingly weak swings in for a total of 16*(5/8)=10
more lactate). This puts you at 2.5+3.75+2.5
=8.75 lactate to reach full strain, and 18.75 if going all the way to failure. These correspond roughly to 45 weariness or 171 weariness each. The recovery will need some formulae to calculate because it's multivariate.
- Your strain, the first fight-through, will recover pretty much every second, and go down by 8. That means it will take 10 seconds to recover. In that 10 seconds, most of the time you'll be recovering lactate equal to:
(1+2+3+4+5+6+7+8+9+10)*8/80
= 5.5 lactate. That leaves you with another 2.25 to recover, which takes another 3 seconds. This may be a bit too quick, although also this isn't pushing yourself to extremes... a few swings at max strain will drive that number much higher.
Note that because your lactate doesn't decrease if your strain increases, and decreases quite slowly if you are strained until your strain gets low. At half strain, for example, you're losing 0.2 lactate per second, which is not insignificant but still pretty low. This will generally cause lactate to build up over time, even as strain recovers, because the general strategy is going to be to let your strain recover, then start fighting again. This also means that the generally good tactic of moving around and not doing strenuous things every turn will keep your strain from getting too high, but won't let all your lactate recover as readily. Once your strain drops to 10%, your lactate recovers pretty fast.
Old system - changed due to issues noted in comments
- Lactate
increases by 1 when strain
reaches 50%, 75%, 90%, and 100% of base_strain_cap
.
- After a number of seconds equal to lactate*5
, max 30 seconds, decrease lactate by 1. Reset this timer every time you gain or lose a point of lactate, up until lactate 5+... you should always lose a lactate after 5 minutes even if you're still doing stuff. This means that if you work your lactate up to 4 (the normal max), you'll need to wait 20+15+10+5=50 seconds to recover all your lactate. However, you will probably want to start fighting again before your lactate goes all the way down, unless your enemies are dead, so in a fight you presumably may be pushing it quite a lot harder.
- Just before you lose a lactate, add lactate^2 * 2 kcal
(max 100) to the weariness tracker - so at 4 lactate getting back to 0 will give you 80+45+20+5=150kcal, or 1/4 of a weariness level for a baseline character. Note that this does not add more calories to dietary needs, it's a surrogate to represent your muscles wearing out.
Effect on strain recovery
Strain_recovery runs every second if lactate
=0. If lactate>0, strain_recovery runs every n seconds where n is std::min(std::max(std::round(lactate/5),1), 5)
. This timer resets every time lactate increases. The cap prevents you from getting an impossibly high lactate and never being able to recover muscle strain. Note that the messy min-max stuff translates to "run at least every 1 second and at most every 5 seconds".
Game Balance
The basic math (assuming no weariness) for a strength 8 character should have you able to make about 16 unarmed melee attacks in a row before strain starts reducing their effectiveness, and 20 before they can't swing any more. At 16 attacks the strain meter will show as full and they'll have built up 4 lactate. It will then take 20 seconds to get all their strain back down half, but then their lactate drops to 3 so they start recovering faster and in the next 15 seconds they should recover the rest of it, as well as another level of lactate, taking it down to 1. If they continue fighting to their strain cap again then the lactate will continue rising. If they push back to their strain cap again immediately, their lactate goes up to 5, and now it takes a bit longer to get their strain back. (Note that pushing strain past the cap doesn't add more lactate. My initial plan had that in here but it made a model that requires a lot of micromanagement at the higher end of strain.)
- This number stuff will need rechecking now that I have reformulated lactate.
A strength 10 character will be able to make significantly more attacks, and will recover more strain per second, so higher strength leads to exponentially higher amounts of strain and longer fights. Ideally we should get a graph set up for this, who likes graphing?
Obviously the player and character can both extend this, eg. by maneuvering in combat - at low strain, spending a single turn to reposition would drop your strain by 8, for example, which in turn has cascading ramifications on your endurance.
- We may wish to review these numbers under the assumption that players are always going to drive their strength to the absolute limit every time. Boxing match times would be a good thing to examine. See bottom of this post.
Display in UI
I do not want lactate
to be another stat players track independently. It should be treated as a sub-element of strain
. If strain is represented as a bar in the UI, I suggest lactate be represented as blocks on the bar, adding a new block every 5 lactate to a max of 3 blocks. EG:
Strain: |||..
- player is 50% of the way to strain cap but has no lactate
Strain: █||..
- player has 5-9 lactate and is 50% of the way to strain cap
Strain: ███|.
- player has 15-19 lactate and is 50% of the way to strain cap.
Describe alternatives you have considered.
It would be possible to devise other systems to slow down strain recovery, probably using weariness, but I think this is slick.
Additional context
When we add muscle gains through exercise, building and dropping lactate will be the primary way to get exercise gains.
Lactate should allow you to build and burn strain quickly for a little while but eventually forces you to take a longer break.
Using RL statistics
- A pro boxing match is 12 rounds at 3 minutes each with 1 minute break. An amateur boxing match is usually 3 rounds at 3 minutes each with 1 minute break
- This tells us that a character should be able to get their lactate down to zero in a 1 minute break, even if it's quite high. The difference between pro and amateur can be accounted for with weariness thresholds and cardio, and at some point perhaps skill or proficiency can also reduce the impact of extended activity.
- A pro heavyweight boxer throws about 30 punches in a 3 minute round, a lightweight throws around 100.
- Since we are currently assuming all punches are equal force, we can say we're averaging that out to about 60 punches per round, conveniently about 1 per 3 seconds.
- This is an upper limit of human normal stat, not what we expect an average player to accomplish initially.