* The linked-to patch notes? https://www.ageofempires.com/news/age-of-empires-ii-definiti...
* The particular comment explaining this bug fix? https://old.reddit.com/r/Games/comments/12jbb9d/age_of_empir...
21–30 of 267 posts
* The linked-to patch notes? https://www.ageofempires.com/news/age-of-empires-ii-definiti...
* The particular comment explaining this bug fix? https://old.reddit.com/r/Games/comments/12jbb9d/age_of_empir...
This is a fantastic out of context change log. For even more abstract ones I cannot recommend enough the Dwarf Fortress Bugs Twitter account[0] The last of which is > Flying creatures keep exploding into pieces and dying: Are they crashing into trees? [0]sadly no Mastodon, https://twitter.com/DwarfFortBugs
Paradox Interactive games, especially the Crusader Kings series also have some pretty entertaining change logs. E.g., from Crusader Kings II, Patch 2.4 [0]: - Handsome and lustful men now also populate the cabins in the wild for the pleasures of people who find them attractive. - Characters who love their spouses very much are now less likely to join holy orders. - Paranoid parents should no longer worry about potent…
And I wonder about dead children. I remember one game where there could be no dead children.
ok, but who but TheViper or DauT has this issue? ಠ_ಠ (As a Japanese main, nerf Aztecs.)
Wow this comment had me reminiscing of watching AocZone tournaments replays of them in my youth Sigh
What’s the best way to play AoE on a MacBook? I can’t afford a dedicated game rig so any pointers would be of great help.
Seems to work fine with Wine and Crossover.
Fun fact: The competitive AoE2 scene remains very much alive and thriving. Several top players are able to make a living from it, thanks to streaming revenue and tournament prize money. There are even some streamers, such as T90 and MembTV, that have managed to create successful careers despite not being competitive players themselves.
T90 is probably the strongest dedicated commentator / announcer / caster in this community.
Earlier quoted context omitted.
> The formula was probably `return new_maxhp * (old_hp / old_maxhp);` ... > Due to floating point rounding errors, convert_hp(1, 55, 55) equals 0.999999940395355224609375, which is less than 1, which means the unit dies. Ahh. The old "Multiplying and dividing is associative... for the set of reals, not for the subset that you can actually afford to compute with." So it should have been `(new_maxhp * old_hp) / old_max…
Seems all that could have been avoided by using rationals in general and the condition for living being >0 instead of dying The more I write code, the more I notice how rarely one truly should want floats in most kinds of programs and how they almost always carry a whole bag of problems with them.
It's 2023: I'm still surprised that so many new languages/platforms still only provide only basic integer and IEEE-754 types for numerics: Rust, C#, Java, Swift and others still lack built-in, runtime-provided, nor even standard-library-provided rational types, which I assume really should be the preferred type for most business/domain/application values, exactly for things like RTS game unit health, for example. (and Java's lack of operator overloading makes this even more painful).
----
On a related note, can we agree that an application-programming language today should also include signed-and-unsigned "money-safe" decimal types, and IEEE-754 types should be smarter about when NaN can happen - and it should support interval types (and evaluating interval-type-based contract invariants): this would eliminate whole classes of bugs in the first place (too many programmers think single/double is appropriate for storing currency values, ugh).
From the comments: "This was caused by a floating point rounding error. A unit switching jobs internally becomes a different unit (i.e. a monk with a relic becomes a monk without a relic), and when doing that, HP is scaled proportionally based on the old and new max HP using 32-bit floating point maths." And there are YouTube videos about this https://www.youtube.com/watch?v=MytWNbpnAVY
> The formula was probably `return new_maxhp * (old_hp / old_maxhp);` ... > Due to floating point rounding errors, convert_hp(1, 55, 55) equals 0.999999940395355224609375, which is less than 1, which means the unit dies. Ahh. The old "Multiplying and dividing is associative... for the set of reals, not for the subset that you can actually afford to compute with." So it should have been `(new_maxhp * old_hp) / old_max…
Is it because 55/55 != 1, or 1*1 != 1?
I understand float can't store certain integer/finite decimal precisely, but why would a/a not be 1 if both numerator and denominator are the same (imprecise) number?
Earlier quoted context omitted.
> The formula was probably `return new_maxhp * (old_hp / old_maxhp);` ... > Due to floating point rounding errors, convert_hp(1, 55, 55) equals 0.999999940395355224609375, which is less than 1, which means the unit dies. Ahh. The old "Multiplying and dividing is associative... for the set of reals, not for the subset that you can actually afford to compute with." So it should have been `(new_maxhp * old_hp) / old_max…
Could you explain to a noob why does this happen, exactly? Is it because 55/55 != 1, or 1*1 != 1? I understand float can't store certain integer/finite decimal precisely, but why would a/a not be 1 if both numerator and denominator are the same (imprecise) number?
for an easier to follow example, say you can only work with two decimal places, hp was 1, old max hp was 3 and new max hp was also 3. if you do (3 * 1) / 3 you get 3 / 3 = 1 as intended. if instead you do 3 * (1 / 3) you get 3 * 0.33 = 0.99 which is less than 1
Earlier quoted context omitted.
> The formula was probably `return new_maxhp * (old_hp / old_maxhp);` ... > Due to floating point rounding errors, convert_hp(1, 55, 55) equals 0.999999940395355224609375, which is less than 1, which means the unit dies. Ahh. The old "Multiplying and dividing is associative... for the set of reals, not for the subset that you can actually afford to compute with." So it should have been `(new_maxhp * old_hp) / old_max…
Seems all that could have been avoided by using rationals in general and the condition for living being >0 instead of dying The more I write code, the more I notice how rarely one truly should want floats in most kinds of programs and how they almost always carry a whole bag of problems with them.