[HN Gopher] Aztec monks with 1/55 HP no longer die when picking ...
       ___________________________________________________________________
        
       Aztec monks with 1/55 HP no longer die when picking up or dropping
       a relic
        
       Author : danso
       Score  : 473 points
       Date   : 2023-04-12 13:48 UTC (3 days ago)
        
 (HTM) web link (www.reddit.com)
 (TXT) w3m dump (www.reddit.com)
        
       | elnatro wrote:
       | Is Age of Empires playable in MacOS? I'd like to remember my
       | childhood years but going back to windows after 15 years of
       | Ubuntu and MacOS gives me headaches.
        
       | userbinator wrote:
       | My metrication extension turned the title into "Aztec monks with
       | _13.37 W_ no longer die when picking up or dropping a relic ".
        
         | Taywee wrote:
         | That's not a lot of power for a monk. What's the typical monk's
         | TDP?
        
       | celeritascelery wrote:
       | I don't understand why they didn't just use an integer. You can't
       | have half a HP in AoE and nothing has HP so high that it would
       | overflow a 32 bit integer. Seems like it would avoid a lot of
       | problems.
        
         | mqus wrote:
         | Afaiu there are compounding factors like _1.5 here and 1.5
         | there should result in_ 2.25 and so on
        
         | adameasterling wrote:
         | I had a really fascinating conversation with the lead architect
         | at Frost Giant the other day. They're making the next
         | StarCraft, basically.
         | 
         | They don't use floating points.
         | 
         | He didn't mention (IIRC) the possibility of rounding bugs; the
         | reason he cited was something a bit more interesting: Floating
         | points are not deterministic, which makes multiplayer
         | challenging, as they simulate game state on each client's
         | computers and only send commands across the wire.
         | 
         | One wonders if the "out of sync" errors that plagued Age of
         | Empires in my youth were partially explained by FP determinism
         | issues!
        
       | vippy wrote:
       | ok, but who but TheViper or DauT has this issue? tth_tth (As a
       | Japanese main, nerf Aztecs.)
        
         | arendtio wrote:
         | The T-West video talks about a match between Mr. Yo and Valas
         | ;-)
        
         | moneywoes wrote:
         | Wow this comment had me reminiscing of watching AocZone
         | tournaments replays of them in my youth
         | 
         | Sigh
        
           | spaceman_2020 wrote:
           | Always a little surreal to see the Viper still around. He
           | used to be a legend in the forums 20 years back.
        
       | Sniffnoy wrote:
       | Rather than linking to this Reddit thread, wouldn't it make more
       | sense to either link to:
       | 
       | * 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...
        
         | danso wrote:
         | I tried to and thought I had linked to the comment. I did
         | wonder if the link went only to the thread, but the Reddit
         | mobile web interface is so broken that I couldn't tell where
         | the link was going to, though I figured the Aztec monk bug
         | would stay at the top either way so people would figure it out.
         | 
         | (the official patch notes don't explain the bug)
        
       | raldi wrote:
       | I would consider banning implicit casting of floats to ints from
       | the codebase: Nobody would have intentionally truncated in this
       | HP conversion; they would've rounded.
        
         | tourgen wrote:
         | [dead]
        
         | einpoklum wrote:
         | It's an unfortunate consequence of compatibility with C.
         | 
         | I'm pretty sure you could get the compiler to warn you about
         | this stuff though.
        
       | Reason077 wrote:
       | It's great that Microsoft are still actively maintaining a ~24
       | year old game!
       | 
       | Blizzard ought to take note. A few years back they announced
       | "Warcraft 3 reforged" with great fanfare: a remastered version of
       | the classic Warcraft 3, updated for modern systems with improved
       | graphics etc. It looked/sounded fantastic!
       | 
       | But despite it being a paid upgrade, what they released was a
       | buggy mess that did little justice to the classic original. Then
       | they immediately abandoned it and have released no updates or
       | fixes since.
        
         | roflyear wrote:
         | Broodwar is still super popular and they don't do shit there
         | either.
        
         | hannofcart wrote:
         | Even more amazing still is that it still has a large active
         | player base and YouTube streamers make money playing it and
         | casting videos of their play through. I'm routinely shocked by
         | how easily I can join online multiplayer matches.
         | 
         | For a 24 year old game!
         | 
         | A bunch of guys in my company play it. Most of them were 3 or 4
         | years when the game was released. A couple of them weren't even
         | born.
         | 
         | Real kudos to the team maintaining it. They constantly listen
         | to player feedback and keep updating the various unit stats and
         | bonuses to keep the game balanced.
        
         | potemkinhr wrote:
         | It's actually worse, they pulled the legacy version from the
         | users and forced the (effectively) downgraded version to
         | everyone. Even the owners of the legacy version were shafted as
         | they forced a update which removed much of the functionality of
         | the legacy version. To this date still the best version was the
         | pre-patch one for everyone who has the original installers and
         | the latest pre-upgrade patch
        
           | Reason077 wrote:
           | _Update:_ In fairness to Blizzard, I did just check this
           | again and it seems like they have started maintaining
           | Warcraft 3 Reforged again, releasing some small patches last
           | year and a significant update in January 2023:
           | 
           | https://news.blizzard.com/en-
           | gb/warcraft3/23896791/warcraft-...
           | 
           | I'll have to take another look now that this has been
           | released.
           | 
           | But still, there was a 2 year stretch between 2020 and 2022
           | when the game was in a very crappy state and absolutely
           | nothing was done to improve it or address the issues.
        
             | vultour wrote:
             | Blizzard also pulled the original W3 downloads and switched
             | everyone to a non-hd version of Reforged. I'm still mad
             | about that because I paid for both and I'm no longer able
             | to get one of them.
        
               | LeoNatan25 wrote:
               | But isn't Reforged using the same engine as the original,
               | with higher res assets (mostly textures)? At least, I
               | thought so, as that was part of the criticism of
               | Reforged.
        
               | Reason077 wrote:
               | Nothing wrong with that, necessarily. In fact, I'd argue
               | that's exactly what I would want out of an ideal War3
               | reforged. Exactly the same game modernised for modern
               | systems, with the "same" graphics just at higher
               | resolution and detail.
               | 
               | Besides, World of Warcraft, StarCraft 2, and Heroes of
               | the Storm were all built using upgraded versions of the
               | Warcraft 3 engine, so it's proven to be quite capable.
               | It's logical to use an updated version of the engine for
               | an updated version of the same game.
               | 
               | Trying who port the game to an entirely different engine
               | would not only be much more effort, it would risk
               | introducing subtle differences in gameplay and feel that
               | could mean it doesn't play quite the same any more.
        
           | Razengan wrote:
           | This kind of crap is actually why I will not buy Diablo 4
           | (even though I kinda want to, to have a good hack'n'slash on
           | my PS5) or put money into any Blizzard game anymore.
        
           | zelphirkalt wrote:
           | And the AoE2 DE has way higher system requirements while only
           | looking marginally better in some aspects, but looks worse
           | than 2 decades old 2d art in some other aspects. Just because
           | it "has to be 3D" these days, while there is nothing truly
           | requiring any 3D in the game.
           | 
           | This is subjective, but I liked some of the old unit designs
           | more than the new ones. For example Teutonic Knights and
           | Cataphracts looked better in my opinion. The Teutonic Knight
           | looked leaner and taller, while it now looks a bit like a
           | walking barrel. OK, somewhere they need to carry all that
           | armor ... but still I liked the old ones better.
           | 
           | Also I would have been completely fine with units having only
           | 8 or whatever directions in which they can look. I don't need
           | 360deg all-around rendering of units. It even substracts a
           | little from AoE2's abstract character.
           | 
           | What I do like is, that they added more civs.
        
             | VoodooJuJu wrote:
             | I think DE actually looks worse. Old graphics hold up great
             | - just look at this: https://imgur.com/hWSZBpb. The new
             | ones lost the soul. They have a weird glow/fuzziness.
             | Nonetheless, DE has better multiplayer performance and cool
             | new civs, so it's the one I play.
        
             | qwytw wrote:
             | > I don't need 360deg all-around rendering of unit
             | 
             | But you don't have it anyway? I assumed the game is still
             | actually isometric and not 3d but even if it is the camera
             | is still fixed.
        
           | Laaas wrote:
           | I'm surprised this isn't illegal.
        
             | maeil wrote:
             | I would imagine that in certain countries with strong
             | consumer rights (e.g. Australia or Germany) it may well be,
             | just understandably no one bothers with the expensive legal
             | fight it would take.
        
         | tetha wrote:
         | Yeah it appears my Diablo 2 has been pulled from my Blizzard
         | account when the D2 Remastered was launched, at least I can't
         | find it anymore. And now I either have to dig up the CD and key
         | and hope those still work or pay $60 or something to play D2.
         | My desire isn't really that big.
        
           | alewi481 wrote:
           | I see this link on the Blizzard support site that provides
           | downloads to classic D2 and LoD but I haven't tested them out
           | to see if they ask for a CD key or not.
           | 
           | https://us.battle.net/support/en/article/13867
        
         | yen223 wrote:
         | It's not just maintenance, Microsoft has released several
         | expansion packs to the original Age of Empires II, plus an Xbox
         | port to boot.
        
         | spaceman_2020 wrote:
         | The amazing thing is that this new version of AoE was a fan
         | made upgrade, and they did such a good job that MS brought them
         | in house.
         | 
         | The Forgotten Empires team is brilliant and I can't be more
         | grateful to them for bringing back a childhood favorite.
        
       | mcraiha wrote:
       | 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
        
         | intrasight wrote:
         | I woke up in the middle of the night and of course opened up
         | HN. This was near the top and was an interesting read.
         | 
         | I told my GF that last night I had insomnia and found myself
         | researching why Aztec monks die when picking up a relic, and
         | that doing so resulted in plumbing the depths of an important
         | computer science topic.
         | 
         | She said "Sounds like you were reading Hacker News". She knows
         | me so well now :)
        
         | ReactiveJelly wrote:
         | > 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_maxhp`. ffmpeg has a whole AVRational class for
         | the same type of problem when converting timestamps accurately
         | between 2 timebases.
         | 
         | Reminds me of the TF2 ammo boxes that give 40 metal on Linux
         | and 41 on Windows
         | https://www.youtube.com/watch?v=QzZoo1yAQag&pp=ygUfdGYyIGhlY...
         | 
         | Apparently the compiler used on Linux chose a less-accurate
         | multiply instruction for 0.2 * 200.
         | 
         | Two wrongs make a right - After being less accurate but
         | rounding up where you don't need to, the Linux binary correctly
         | arrives at 40, and the Windows binary arrives at 41.
         | 
         | For percents (and dollars) I always do everything in cents and
         | divide by 100 at the very end. "20 * 200 / 100" can be done
         | with pure ints, and f32 can represent i16 exactly.
        
           | zelphirkalt wrote:
           | Seems all that could have been avoided by using rationals in
           | general and the condition for living being >0 instead of
           | dying <1. Not sure in what case it makes sense to have dying
           | at smaller than 1. To the user all the numbers are integers,
           | so why not use rationals instead of floats?
           | 
           | 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.
        
             | avgcorrection wrote:
             | > Seems all that could have been avoided by using rationals
             | in general
             | 
             | Why can't they store HP internally as "millihealth"
             | integers, do integer arithmetic (including division), and
             | then divide that by 1000 purely for display?
        
               | firstlink wrote:
               | This would require competent senior programmers who are
               | way too expensive, and anyways once hired are used for
               | bullshit tasks like project management.
        
             | tialaramex wrote:
             | The trouble you have is that you really wanted _reals_ and
             | the _rationals_ are not a much better substitute than the
             | _floats_ (which are mostly just one particularly
             | specialised flavour of rational)
             | 
             | It doesn't feel immediately like there ought to be a
             | problem, but witness, mathematically _almost all of the
             | reals are Normal_ and unfortunately Normal doesn 't mean
             | "normal" it's a technical mathematical word, that for our
             | purposes means roughly "completely batshit".
             | 
             | Normal numbers are non-computable, so we definitely don't
             | want to try to use them in software, which is a problem
             | because again, _almost all of the reals are Normal_.
        
               | l__l wrote:
               | Huh? This isn't making a lot of sense to me. Why do you
               | say the rationals aren't much better then floats? In
               | situations like this surely we absolutely want to be
               | working in the rationals, and they're a very easy way to
               | do these calculations correctly without having to worry
               | about tolerance bounds for arbitrary-precision float
               | calculations
        
               | hgsgm wrote:
               | Algebraics plus pi avoid almost all the concern you
               | mention, but still have the same problem for software.
               | All those nasty reals are _unreachable_ by your program.
               | 
               | The actual problems with irrationals is that it is
               | extremely hard to measure their size without converting
               | to rational approximation, so you are back to the
               | original problem.
        
             | CodesInChaos wrote:
             | The big problem with rationals is that it's easy to end up
             | with both nominator and denominator growing without bounds,
             | which IMO is an even bigger pitfall than those of floating-
             | point or fixed-point.
        
             | fendy3002 wrote:
             | In terms of hp without decimal in gaming and related with
             | max hp changing, the best solution should be to rounding
             | down (or nearest zero rounding) the result, and keep
             | minimum hp to 1 when max hp changes. In case for minimum hp
             | unit (1 in this case) to have changed into lower max hp
             | then they'll die too, hence why minimum 1 hp as the result
             | is logical.
             | 
             | This is exactly why heroes won't die in Dota2 when toggling
             | off armlet while having 1 hp
        
             | ilyt wrote:
             | > Not sure in what case it makes sense to have dying at
             | smaller than 1. To the user all the numbers are integers,
             | so why not use rationals instead of floats?
             | 
             | If killing unit takes X attacks, user buys 10% attack
             | damage upgrade, and it still takes X attacks they will be
             | annoyed. If rounding down takes that to x-1 attacks it
             | might be preferable.
             | 
             | That being said you're absolutely right. just use HP*100 in
             | calculations and display HP/100 to the user, and when you
             | need to round into specific direction do it explicitly
        
             | DaiPlusPlus wrote:
             | > Seems all that could have been avoided by using rationals
             | in general
             | 
             | 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).
        
               | jacquesm wrote:
               | > can we agree that an application-programming language
               | today should also include signed-and-unsigned "money-
               | safe" decimal types
               | 
               | You mean like COBOL?
        
               | marcosdumay wrote:
               | Yep, like COBOL, or SQL.
               | 
               | But, in fact, I don't know of any language that doesn't.
               | They are just not basic types.
        
               | Kiro wrote:
               | > and Java's lack of operator overloading makes this even
               | more painful
               | 
               | Why?
        
               | bombolo wrote:
               | Because you can't make your own types that work better.
        
               | hgsgm wrote:
               | Of course you can. You just can't use operators for them.
               | 
               | Unless you go Haskell where anything can be an operator
               | name, operators are just a minor convenience hack .
        
               | bombolo wrote:
               | So you can but you need to rewrite everything instead of
               | just changing a type, and the code will look completely
               | impossible to read.
        
               | cwillu wrote:
               | Some aspects of the problem, exagerated for effect:
               | boolean isDead = (new I("0.9")).divide(new
               | I("55")).multiply(new I("0.1")).lessThan(new I("1"))
        
               | marcellus23 wrote:
               | Foundation has the Decimal type (not technically part of
               | the Swift standard lib, but in practice available for
               | most uses of Swift).
        
               | fisf wrote:
               | Java pretty much provides BigDecimal for that
        
               | pclmulqdq wrote:
               | I'm increasingly convinced that peoples' problems with
               | 754 types are psychological. With integers, they are very
               | careful about order of operations, precision, rounding,
               | etc. With doubles, the average programmer fires and
               | forgets. Sometimes that has bad consequences.
               | 
               | A decimal type is definitely necessary, and you can use
               | IEEE decimal floating point for that, too! Other decimal
               | types are very useful. Rationals are a different story:
               | rational numbers are great for addition and subtraction,
               | but if you're going to be doing a lot of multiplication
               | and division, you are going to find yourself also doing a
               | lot of slow gcd calculations to reduce/renormalize the
               | fractions (incidentally, decimal types also need
               | renormalization, but it's a lot cheaper). Most cases
               | where rational types are used today are very careful
               | about not having long chains of these operations.
               | 
               | As to NaNs: those are all considered pretty carefully,
               | and 754-2019 actually reduced the amount of NaNs that
               | propagate around quite a bit. For example, make sure you
               | are using fmax(a, b) instead of std::max<double>(a, b).
        
               | Someone wrote:
               | > It's 2023: I'm still surprised that so many new
               | languages/platforms [...] still lack built-in, runtime-
               | provided, nor even standard-library-provided rational
               | types
               | 
               | Because numerators and denominators can blow up easily,
               | rational types effectively require storing them as
               | bigints. That makes them bad from a performance
               | viewpoint.
               | 
               | Assuming your application will work fine with fixed-size
               | rationals (which, IMO, is highly unlikely), the natural
               | way to store rationals in fixed-size storage wastes lots
               | of room.
               | 
               | For example, in a _(int32 nominator, int32 denominator)_
               | pair, there are about 232 ways to encode '1' that way,
               | 231 to encode 1/2, etc. I also think such a fixed-size
               | rational type isn't very natural to work with
               | 
               | Rationals also require regularly computing a gcd when you
               | add (1/3 + 1/6 isn't 9/18 but 1/2) or multiply (10/21 x
               | 7/5 isn't 70/105 but 2/3) them, slowing down things more
               | (you can use heuristics to avoid some of those
               | simplifications, but not doing one when that's possible
               | may mean your rationals keep huge numerators and
               | denominators, slowing down operations)
        
               | e12e wrote:
               | > Because numerators and denominators can blow up easily,
               | rational types effectively require storing them as
               | bigints. That makes them bad from a performance
               | viewpoint.
               | 
               | While hardware support for full number towers might be
               | neat, I don't think anyone is suggesting that int and
               | floats should not have language/standard library support
               | - only that there should be a blessed option for precise
               | arithmetic.
               | 
               | Usually the slow correct answer is preferable to the
               | quick wrong answer (esp in business logic).
        
               | kortex wrote:
               | I'm (vaguely) surprised there hasn't been a hardware type
               | for rationals. Then the GCD operation could be done in
               | hardware and everything would be fast (at least as fast
               | as floats). Not surprising, because adding new hardware
               | data types is hard, but the financial institutions would
               | benefit from something like that.
               | 
               | A rat64 could be:
               | 
               | - 1 sign bit
               | 
               | - 3 format bits. Determines where the decimal is located,
               | e.g. 30.30 vs 52.8.
               | 
               | - 60 bits of numerator and denominator. The split is
               | determined by the format bit. 50.10 would be handy for
               | any percent/ppt operations (e.g. Most USD operations)
               | 
               | Might need an error bit, but that's my 5 minute gist.
        
               | Someone wrote:
               | > Then the GCD operation could be done in hardware and
               | everything would be fast (at least as fast as floats).
               | 
               | Could it? Is there a fast way to do GCD in hardware?
               | 
               | Googling gave me
               | https://en.wikipedia.org/wiki/Binary_GCD_algorithm, which
               | needs _O(log2(max(u, v)))_ operations to compute
               | _gcd(u,v)_ , but I wouldn't know whether that's the best
               | we can do in hardware, or whether that's 'as fast as
               | floats'.
               | 
               | Also, I don't see how your format describes a rational
               | type. Rationals store numbers as _p /q_ for integer _p_
               | and _q_ , not with a decimal point.
        
               | kortex wrote:
               | Sorry, abuse of notation. 30.30 would be 30 bits
               | numerator, 30 bits denominator.
               | 
               | I would imagine it would not need to fully reduce after
               | every operation, there's probably tricks where if you
               | know the factors going in, there will be obvious ones
               | after multiplication/division.
               | 
               | It's not my best idea :p
        
               | justinpombrio wrote:
               | > 60 bits of numerator and denominator.
               | 
               | The trouble is this isn't enough, even for ordinary
               | usage. The numerator and denominator _grow in proportion
               | to the total number of operations you have performed_.
               | For example, if you start with `1` and then multiply by
               | `80 /81` a thousand times, you get a number that's around
               | 1e-6, but when expressed as a rational the numerator and
               | denominator have _hundreds_ of digits:
               | 
               | https://www.wolframalpha.com/input?i=%2880%2F81%29%5E1000
        
               | kortex wrote:
               | But how often are people using decimal types to do that?
               | Most of the uses I could see this type being used for -
               | currency, percent scaling, datetimes, audio/video codec
               | frame rates - all are basically fixed point operations.
               | Anything involving powers of 80/81 would probably need
               | bigint based rationals anyways.
               | 
               | Actually if you had an int64 type which was scaled by
               | flicks, that'd give you quite a lot of latitude for most
               | day to day stuff.
               | 
               | https://en.m.wikipedia.org/wiki/Flick_(time)
        
               | justinpombrio wrote:
               | Yeah, fixed point is different from rational, and all
               | those examples you gave sound to me like fixed point. And
               | that can be implemented efficiently without dedicated
               | hardware support: the denominator is fixed, and you store
               | the numerator as an integer.
               | 
               | A 1/3 off discount on a $10 item is $6.67 (or $6.66 if
               | rounding in the customer's favor), _not_ $10 /3.
               | 
               | (Except datetime, did you mean timestamp? A timestamp is
               | an instant in time, and it often makes sense to store it
               | in high precision because you're saying exactly when
               | something happened. A datetime is for communicating
               | between humans who are using some particular calendar; it
               | rarely makes sense to have more than minute precision.)
        
               | carlmr wrote:
               | >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
               | 
               | C# has the base-10 high accuracy decimal type:
               | https://learn.microsoft.com/en-us/dotnet/csharp/language-
               | ref...
        
               | colejohnson66 wrote:
               | There are two problems with the `decimal` type: it's not
               | IEEE-754 compliant, and it is 16 bytes, which prevents
               | atomic reads/writes (without locks).
               | 
               | This is a problem in one of my company's applications, as
               | we have a background thread doing work entirely with
               | `decimal` objects, but providing a readout for the GUI
               | necessitates a lock on every write (even if never read),
               | and another for the read by the GUI. There's barely any
               | overhead (nanoseconds worth), but the logic to work
               | around it is a pain, and, if you forget to actually use a
               | lock, good luck with that bug.
               | 
               | .NET 8, however, will introduce[0][1] proper IEEE-754
               | decimal float types, including `Decimal32` and
               | `Decimal64` which will allow atomic reads/writes.
               | 
               | [0]: https://github.com/dotnet/runtime/issues/81376
               | 
               | [1]: https://github.com/dotnet/runtime/issues/79004
        
               | bawolff wrote:
               | > It's 2023
               | 
               | This is a game originally from 1999. Although i doubt it
               | would make a difference.
        
               | ouid wrote:
               | why would rationals be good for RTS.
               | 
               | If all of your operations occur with some reasonable
               | minimum denominator, just use ints. If not, that
               | arithmetic is going to become unbounded really fast.
        
               | PhilipRoman wrote:
               | Aren't rationals subject to exponential blowup in
               | storage/precision? IMO the best approach for business
               | logic values is picking the smallest meaningful unit and
               | representing it with integers. Precision and storage
               | requirements become very easy to reason about.
        
               | DaiPlusPlus wrote:
               | > IMO the best approach for business logic values is
               | picking the smallest meaningful unit and representing it
               | with integers.
               | 
               | How would you design an RTS unit stats system to avoid
               | this AoE Monk HP bug using only integer types?
        
               | CodesInChaos wrote:
               | `(new_maxhp * old_hp) / old_maxhp` avoids the original
               | problem when using integers. Though you still need to
               | make sure your type is big enough to not overflow.
               | 
               | Or using fixed-point (which are a relatively simple
               | abstraction over integers): `old_hp * (new_maxhp *
               | old_maxhp)`.
        
               | rvnx wrote:
               | They probably just added: return ROUND()... and went back
               | to play
        
               | ilyt wrote:
               | max(1,...
        
               | bruce343434 wrote:
               | How much health precision do you need? Make hp an int. Or
               | like a /64 fixed point.
        
               | falcor84 wrote:
               | Well, in this case, if a unit is converted to a different
               | type and back, it would just keep its (absolute) value of
               | HP (in this case 1), no need to scale anything.
        
               | db48x wrote:
               | You haven't thought it through yet. In this case, HP _is_
               | an integer, and so is the MaxHP for the unit type. And
               | yet the bug still occurs! Storing the HP as an integer
               | does not prevent the problem from ever occurring, because
               | fundamentally the user still expects HP to be
               | proportional.
               | 
               | That is, suppose a unit is sitting there at full health,
               | and you research a tech that increases that unit's max
               | hp? Should the unit now be at less than full health, as
               | if it had been in combat? Users probably don't like that.
               | 
               | Suppose it is at full health, and then it gets downgraded
               | to a type with less maximum health. Does it stay at it's
               | current HP? Users probably won't like being attacked with
               | supercharged units that have extra HP; they'll think that
               | the other player was cheating somehow.
               | 
               | What if it is damaged and at half health, then gets
               | upgraded. Should it be fully healed? Gain HP equal to the
               | difference between the new and old maximum HP? Or should
               | it gain half of that, so that it stays at half health?
               | 
               | Or perhaps HP is too limiting, and the game should do
               | what Dwarf Fortress does. DF knows the approximate
               | surface area of every body part (based on each creature's
               | body plan, plus individual stats such as strength,
               | fatness, size, etc), and the size of every weapon. Every
               | attack therefore deals damage to a certain area, measured
               | in square inches, and individual body parts will be
               | destroyed once a sufficient percentage of that surface
               | area is damaged by wounds.
               | 
               | Or maybe you are designing this game in the 90's, and you
               | have to worry about squeezing unit updates for 200 units
               | per player into the bandwidth provided by the average
               | modem of the day (probably 28.8kbaud), so you stick to
               | one integer because it's the simplest think that can
               | possibly work, and the number of bytes per update can be
               | calculated in advance. And then some other schmuck gets
               | stuck with the job of handling unit type changes (but be
               | warned that the schmuck might be yourself in six months).
        
               | ilyt wrote:
               | >That is, suppose a unit is sitting there at full health,
               | and you research a tech that increases that unit's max
               | hp? Should the unit now be at less than full health, as
               | if it had been in combat? Users probably don't like that.
               | 
               | >Suppose it is at full health, and then it gets
               | downgraded to a type with less maximum health. Does it
               | stay at it's current HP? Users probably won't like being
               | attacked with supercharged units that have extra HP;
               | they'll think that the other player was cheating somehow.
               | 
               | >What if it is damaged and at half health, then gets
               | upgraded. Should it be fully healed? Gain HP equal to the
               | difference between the new and old maximum HP? Or should
               | it gain half of that, so that it stays at half health?
               | if (oldHP == maxOldHP) { newHP = newMaxHP }          else
               | { newHP = max(1,scale(oldHP,oldMaxHP,newMaxHP))
               | 
               | not exactly complex
        
               | ghusbands wrote:
               | Sure, if you fully predict all bugs in advance and
               | compensate for them, nothing is complex, but that's not
               | really feasible.
        
               | db48x wrote:
               | Not exactly complex, but you posted too quickly to have
               | found the best solution.
               | 
               | A better solution is to kill the unit when it drops below
               | zero HP, not when it drops below 1.0; scaling will never
               | move the current HP past zero in either direction. Having
               | 0.9999994 HP instead of 1.0 HP would not cause any
               | problems then; the unit is still one hit away from death
               | in either case.
               | 
               | The best solution is probably to store the current HP as
               | a percentage of the max, because then you never have to
               | rescale it in the first place.
        
               | InitialLastName wrote:
               | > The best solution is probably to store the current HP
               | as a percentage of the max, because then you never have
               | to rescale it in the first place.
               | 
               | Doesn't this solution make the more common calculations
               | more expensive, complicated, and error-prone to make one
               | rare calculation easier? It seems like far more of the
               | operations on the unit's current HP will be "gets damaged
               | by X hp" or "gets healed by X hp", both of which would
               | require the equivalent of the above conversion to
               | establish a result.
        
               | db48x wrote:
               | If you've changed HP to a percentage, why wouldn't you
               | also change the damage and other related numbers (such as
               | damage reduction and so on) to percentages as well?
               | Nobody would be dumb enough to store them in different
               | units and then convert every time they manipulate them.
        
               | mlyle wrote:
               | > why wouldn't you also change the damage and other
               | related numbers (such as damage reduction and so on) to
               | percentages as well?
               | 
               | Because a sword might do 5HP of damage, not 10% of
               | anyone's damage (whether they've got 50 or 5000HP).
               | Otherwise, hit points are meaningless: 10 attacks with a
               | 10%-damage weapon kills a lowly serf or a mighty dragon.
               | 
               | And because damage reduction might reduce a fixed amount
               | of damage from an attack. Etc.
        
               | db48x wrote:
               | Obviously a dragon doesn't get damaged at all by a puny
               | little sword. You can't just say that hitting a dragon
               | with a sword a thousand times would kill it, when none of
               | the attacks can get through the dragon's armored hide. On
               | the other hand, it will die immediately if pierced by an
               | arrow provided that arrow hits the one spot where the
               | hide is missing a scale. The dragon has 100% DR, except
               | in that one spot.
        
               | mlyle wrote:
               | > A better solution is to kill the unit when it drops
               | below zero HP
               | 
               | Then you have the situation where units are displayed as
               | having 0 HP but are still alive.
        
               | db48x wrote:
               | So what? The players already know that attacks deal
               | fractional HP damage.
        
               | mlyle wrote:
               | > The players already know that attacks deal fractional
               | HP damage.
               | 
               | Top-end players who analyze the engine enough do.
               | 
               | Otherwise, they shrug and say "sometimes 1, sometimes 2
               | HP"
               | 
               | And even so, we're used to seeing 0/50HP and it meaning
               | "dead" across many games. Seeing 0/50HP and it meaning
               | "just a small amount of HP left" isn't ideal.
               | 
               | Not to mention that weird situations with
               | 5.551115123125783e-17 HP remaining aren't great either
               | (where a player may end up with effectively an "extra"
               | hit point).
        
               | db48x wrote:
               | Again I say "so what?". The unit is one hit away from
               | death either way, and the player will know it.
        
               | mlyle wrote:
               | At this point, with your Smaug-related comment, etc--
               | I've become convinced you're trolling.
               | 
               | But just to humor you: the desire is to have a system
               | where floating point neither surprisingly kills nor gives
               | characters extra hit points. There's a lot of subtlety in
               | this. Schemes where all damage are percents don't
               | preserve the essential pieces of RPG combat systems.
               | Moving the threshold from 1 to 0 HP violates the
               | conventions of the art and doesn't eliminate the problems
               | (you can still end up with hit points epsilon away from
               | 0, instead of 1).
        
               | rini17 wrote:
               | Did this exponential blowup ever prove troublesome in
               | practice? I think it can be easily solved by
               | strategically placed rounding steps. But explicit
               | rounding is advisable in many cases anyway.
        
               | jrochkind1 wrote:
               | But strategically placed rounding was in fact the
               | solution to the original problem that in this thread
               | Rationals were proposed as solving...
               | 
               | Rational types are not super popular and don't get used
               | that much, most (not all) people that end up using them
               | do it very intentionally and consciously and know what
               | they're dealing with -- if they were more ubiquitious, I
               | suspect more trouble would be seen "in practice".
               | 
               | While it's probably true that any digital number format
               | will have edge cases that in fact come up in practice, my
               | personal choice of "Why the heck isn't this more popular,
               | why doesn't every language support it, why isn't it in
               | fact the default representation of a numeric literal" --
               | is floating point "decimal" types, like ruby (or I think
               | Java?) BigDecimal, rather than rational. I think they
               | mostly work matching programmer's mental models of
               | numbers, and for many/most common uses on 2023 platforms
               | the performance is just fine. (this would not have been
               | true 30 years ago).
        
               | rini17 wrote:
               | Or it's vendors that don't care about correctness, like
               | providing decimals by default and high performance
               | floating point as an "optimized" option. Python did
               | something like that by using bigints as a default for
               | integers.
               | 
               | What would not have been true 30 years ago? I remember
               | "real" fixed-point type built-in in Turbo Pascal and
               | explicitly documented as suitable for money. Common Lisp
               | has had first class fractions support since the start.
        
               | jrochkind1 wrote:
               | I meant to suggest that 30 years ago the performance
               | difference between "floats" (floating-point binary) and
               | "BigDecimal"-style arbitrary-precision floating-point
               | decimal would have been much more significant to many
               | more real-world use cases, compared to now. So that may
               | have been a reason not to make them the _default_ when
               | you simply write a literal `5.4` in code, but that
               | argument is less now.
        
               | GreymanTheGrey wrote:
               | Slight correction, from a former Turbo Pascal (and
               | subsequent Delphi) programmer...
               | 
               | "Real" types were platform-dependent floating-point
               | types, not suitable for monetary calculations whatsoever.
               | and would map to either Single or Double depending on the
               | underlying CPU architecture. Sort of like a C-style "int"
               | that would map to an 8-bit, 16-bit, 24-bit, 32-bit,
               | 36-bit, 60-bit, 64-bit etc integer, depending on the
               | compiler and the compile target.
               | 
               | Turbo Pascal did have an 8-byte, fixed point, "Currency"
               | type suitable for monetary calculations, however using it
               | was very, very slow compared to pure floating point ops -
               | just as the comment you replied to suggested. If that
               | weren't enough, library support (both built-in and 3rd-
               | party) for math and other utility functions was either
               | limited or non-existent.
        
               | fm77 wrote:
               | Slight correction from an Turbo Pascal expert ;-)
               | 
               | Turbo Pascal did NOT have an 8-byte fixed point
               | "currency" type suitable for monetary calculations. It
               | did have an int64 type called "comp" though which was
               | handled as a float by the FPU and hence not slower than
               | the types "single" (f32), "double" (f64) or "extended"
               | (f80).
               | 
               | IIRC, "currency" came with Delphi V2.0 (or even later),
               | but then still it wasn't slower than other floats when
               | you did heavy calculations with it as it was also handled
               | by the FPU. Only reads and writes from and to such
               | variables were expensive as there was always a scaling
               | (multiplication by 1e4 and 1e-4) involved -- internally
               | it was that int64 "comp" type. (But here I might be
               | wrong, I never used "currency" and my Delphi knowledge is
               | quite fuzzy).
        
               | MobiusHorizons wrote:
               | Fun fact many processors actually support decimal types
               | in hardware. I believe you can use them in c with
               | _Decimal. Worked on my Ryzen 3700x and (if memory serves)
               | my M1 mac
        
               | samtho wrote:
               | > On a related note, can we agree that an application-
               | programming language today should also include signed-
               | and-unsigned "money-safe" decimal types
               | 
               | I'm willing to bet that this has to do with how we
               | historically have handled monetary values which is
               | storing and computing it in the respective currency's
               | fractional unit. The advantage is that you can still do
               | math operations directly on this value (as opposed to an
               | object that contains it) and at most you'll be off by, in
               | the case of the US dollar, one cent. Because we cannot
               | represent fractional units of the already smallest unit
               | of currency, we have to choose a value anyway to charge
               | or dispense. Unlike, say, if we stored it in dollars as a
               | floating point and we can start compounding errors from
               | the floating point type.
               | 
               | What interesting is that pre computers, we didn't even
               | treat currency values as an real decimal (for base 10
               | currencies) and the decimal point was simply a convenient
               | way to store partial values. I note this because you
               | don't see old ledgers where they store more than 2
               | decimal places, therefore IMO, this was just an integer
               | in disguise all along.
               | 
               | Old habits die hard?
        
               | hermitdev wrote:
               | > Because we cannot represent fractional units of the
               | already smallest unit of currency, we have to choose a
               | value anyway to charge or dispense. Unlike, say, if we
               | stored it in dollars as a floating point and we can start
               | compounding errors from the floating point type.
               | 
               | I've spent 20 years mostly working in finance. You'd be
               | surprised at the prevalence of floating points used to
               | represent currency. I cringe every time I see it, but
               | it's surprisingly common (and wrong).
               | 
               | More correctly, pricing of securities (from exchanges) is
               | done with integers and a scaling factor. The factor is
               | typically static and doesn't need to be transmitted on
               | every tick. The factor tells you effectively how many
               | fractional digits are present (e.g. the actual price is
               | multiplied by 10^-factor). Factors of 4 or even 6 are
               | somewhat common, but some securities have to go with less
               | precision. I remember in particular Berkshire Hathaway
               | causing issues with overflow using 32-bit ints in the
               | last decade (because they never split their shares, the
               | share price is quite large).
        
               | wruza wrote:
               | _You 'd be surprised at the prevalence of floating points
               | used to represent currency. I cringe every time_
               | 
               | We all cringe but then there's a "floating point or gtfo"
               | ultimatum that most languages present to you. People
               | would be happy to not use FP. But the reality is, you can
               | have a monetary column in a 30 years old database engine,
               | but not in a 5 years old language runtime. When you only
               | have a hammer...
        
             | wruza wrote:
             | Tbh, the only practical non-integers we all need (apart
             | from science-ish pretence and graphics) are .00, .000,
             | .0000 and .000000 decimals (money, qty, percent.00,
             | milliqty). Literally all business and in-game accounting
             | needs get covered by these. Even edge/rounding issues are
             | irrelevant, because everyone is ready to just agree to
             | either way of doing it, and that agreement is much more
             | important than the method itself. This floating bullshit is
             | basically of none, zero, nil practical use outside of
             | academics.
             | 
             | Modern CPUs can effectively emulate whole subroutines in
             | microcode, but somehow we got stuck with that useless
             | binary floating point standard for numbers. And on top of
             | that, languages provide them as a default numeric system
             | for all new software.
        
             | gabereiser wrote:
             | bool is_alive(const Entity* unit) {         return unit->hp
             | > ZERO_FLOAT;       }
             | 
             | Super easy...
             | 
             | Likewise I'm with you, the more I code, the more I see
             | these kinds of things as bad design choices. If you're
             | going to deal with a type, keep it that type. If you're
             | going to convert, you need to take care of edge cases like
             | OP. And don't just add 0.0001f; that's an exploit waiting
             | to happen by someone with a macro to pickup/drop relics.
        
             | perihelions wrote:
             | Rationals fail in their own spectacular ways. Take a simple
             | approximate exponential decay: x - (99/100) * x. In exact
             | rational arithmetic, your program will happily calculate
             | integers (99^k) and (100^k) for unboundedly large values of
             | _k_ (there 's no reduction - they're all coprime), grinding
             | the program to a halt until it runs out of memory.
             | 
             | "Oh, the solution is obvious: you should simply round that
             | to an approximate fraction..." -- yes, and that's floating-
             | point arithmetic. Point is, you can't use either with
             | thinking, about numerical representations and their
             | possible failure modes.
        
               | zelphirkalt wrote:
               | The difference is, that with rationals you do not lose
               | any precision on the way. I doubt, that 99^k is part of
               | AoE2 code. For the simple calculations in AoE2 regarding
               | unit health, rationals will do a fine job. With rationals
               | the rounding does not happen during the precise
               | calculation. It happens explicitly, only once, at the end
               | of the calculation, at the programmer's wish and
               | conscious decision, thereby avoiding the kind of issue we
               | see here.
               | 
               | Rationals might not be the solve all problems type, but
               | here they seem very applicable.
        
               | perihelions wrote:
               | It's a very common pattern, in its more general forms.
               | Sure, you can abstract away the naive re-implementation
               | of exp()                   loop { x - a*x }
               | 
               | as one function call x(t) = exp(-k*t), so that one looks
               | pointless. But moving-average [0] type expressions
               | loop { x - a*x + f(t) }
               | 
               | are more interesting. You can't generally refactor those
               | into exp() calls, like x(t) = sum[ f(t_i) * exp(-k*(t -
               | t_i)) ], since that introduces a memory leak - you need
               | to hold an explicit list of all the past values of f(t),
               | in order to compute it this way. That's why the pattern
               | [0] is uniquely useful: it's a trick to incrementally
               | accumulate a moving average using just one variable of
               | state.
               | 
               | [0] https://en.wikipedia.org/wiki/Moving_average#Exponent
               | ial_mov...
               | 
               | Here's one uncontrived way this pattern might show up:
               | "the NPC has a numeric disposition towards the player,
               | and the player's actions have positive and negative
               | effects on it. These effects have _finite_ duration, and
               | disposition _decays_ back to the neutral value ". Another
               | way: you have a large list (like a time series) you want
               | to reduce to a small one by interpolation. Another way:
               | you have some PID-like controller in your program, and to
               | make it behave, you need a cheap filter to clean a
               | jittery input signal. (I'm building these for my Factorio
               | factories right now - it's the only reason this example
               | occurred to me. "x - (99/100) * x" is a factory control
               | component I had to implement out of int32_t's).
        
               | hinkley wrote:
               | In a simulation nobody is calculating n x m^k. They're
               | calculating n X m in k loops spread over k ticks. And
               | given that fact, if you round or truncate at the end of
               | each cycle, as one often does with game points (eg, fire
               | resistance in D&D), then there is no problem.
               | 
               | For money systems, we also tend to round on every step to
               | the nearest penny.
               | 
               | What you don't have with rationals is the problem of
               | 10.00 - 4.10 + 3.20 ending in a 9.
        
             | still_grokking wrote:
             | > 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.
             | 
             | This! Imho you more or less _never_ want floats under
             | "normal" conditions.
             | 
             | Also in most application domains it makes exactly no
             | difference that floats have HW support, as the bottlenecks
             | are there usually elsewhere. And in case you really need
             | real fast floating point math (say for simulations) you
             | would do it anyway on the GPU nowadays.
             | 
             | The only langue I know of that is sane in this regard is
             | Pyret:
             | 
             | https://www.pyret.org/
             | 
             | [CTRL-F: Pyret has numbers]
             | 
             | I'm still baffled no other new language thought about
             | something so basic like number ever again. Everybody is
             | just using what the HW provides natively since the year of
             | yore. As a result there is no motivation for HW vendors to
             | update the status quo form the state of the art of the 60's
             | of the last century. Imho having support for rationals and
             | (fixpoint) decimal numbers in std. hardware is long
             | overdue! But it would only happen if there would be serous
             | demand form the runtime / language vendors. Classical
             | chicken egg problem.
        
               | marcosdumay wrote:
               | Floats are a computer representation of scientific
               | notation, used for scientific computation.
               | Coincidentally, hardware support is extremely important
               | for scientific computation. (A bit less now than in the
               | 90's but still a showstopper.)
               | 
               | Hardware support is also extremely important for games,
               | but floats are not a good representation for most of it.
               | IMO, game data should be composed exclusively of integral
               | numbers, but it is natural to want a little bit more of
               | precision some times.
        
               | still_grokking wrote:
               | > Floats are a computer representation of scientific
               | notation
               | 
               | No, they aren't. They are IEEE 754 binary floating point
               | data. Something extremely weird, without precedent
               | outside of computers science.
               | 
               | > Coincidentally, hardware support is extremely important
               | for scientific computation.
               | 
               | Maybe it was once, but today it isn't.
               | 
               | If you need to do any serious number crunching you use
               | nowadays dedicated hardware for that (which isn't part of
               | the main CPU usually). (You could use CPU integrated GPUs
               | or FPGAs for that, sure, but the point is that the FP
               | unit on the CPU is usually way to slow for anything more
               | serious.)
               | 
               | > Hardware support is also extremely important for games
               | 
               | That's more or less the only valid usage in mainstream
               | left by now. But even there:
               | 
               | > but floats are not a good representation for most of
               | it.
               | 
               | Exactly!
               | 
               | As said, you could and should use ints for most things.
               | For the rest you want actually _rationals_. Only in some
               | very special circumstances floats are a good enough
               | approximation. But the cases where this is true are
               | mostly related to rendering, as it doesn 't matter if
               | some pixels shown for the fraction of a second have a
               | marginally wrong color or are marginally off. But
               | rendering is done on the GPU anyway. So again no reason
               | to use FP features on the CPU.
               | 
               | Even games using vector and quaternion math extensively
               | this is just lib code in the engine. So there is no real
               | reason this couldn't be moved to the GPU also, given an
               | adequate architecture of the game engine. Such an
               | approach brings even amazing possibilities for games;
               | just have a look at for example https://store.steampowere
               | d.com/app/1468720/Ultimate_Epic_Bat... Want to animate 10
               | million of individual NPCs? No problem, if you do it on
               | the GPU!
               | 
               | So imho the case for FP on the CPU is very shallow. But
               | the need for rationals and decimals is a real thing.
               | That's what the average cooperate developer needs day to
               | day.
               | 
               | It's a shame HW is stuck in the past since decades and
               | there is still no promise of progress on the horizon.
               | (Maybe FPGA accelerators build into CPUs will change that
               | at some point. But we still don't have that, even it's
               | overdue.)
        
             | gizmo wrote:
             | You mean fixed point numbers presumably, because at the
             | time using integers that use a variable amount of memory
             | was totally out of the question. Today it's still terrible
             | for performance because you can't use caches and simd
             | effectively when numbers can't be packed directly in
             | arrays.
        
               | zelphirkalt wrote:
               | Does AoE2 do that? Packing numbers directly into arrays?
               | Or does it rather deal with objects, which are variable
               | size anyway? I am not sure how much high performance work
               | has been done in AoE2. Maybe not that much.
        
             | camgunz wrote:
             | I dug into this problem a while ago and came to the
             | conclusion that these are all rounding problems, which you
             | still can't escape with arbitrary precision systems,
             | because memory is finite. You're always making a tradeoff
             | between precision and space, and you have to rely on
             | programmers to make that tradeoff themselves.
             | 
             | Enter the typical floating point types that do some of that
             | work already: float/double. These let you not think (as
             | much) about the memory your math might take up, but they
             | still need you to think about rounding, which almost no one
             | does.
             | 
             | This is a long winded way of saying that if you set up your
             | arbitrary precision arithmetic library to use 4 bytes and
             | the same rounding mode (the default is round to nearest) as
             | was used here, you'd reproduce the bug. You'd also
             | reproduce the 64-bit bug moving up sizes, and the
             | 128/256/etc bug. You gotta round.
             | 
             | The good news, though, is that actually solves it! You can
             | even use floats for money!
        
           | pclmulqdq wrote:
           | If you have integers that are stored in floating point types
           | and don't need more than [mantissa] bits of precision, you
           | can operate on them like ints and always* get the right
           | result. Nobody would expect that the formula `return
           | new_maxhp * (old_hp / old_maxhp);` would work with ints, but
           | they see floats as magic that allow them to drop their guard
           | about precision and rounding of operations. Unfortunately
           | not.
           | 
           | Rationals have a big problem where under multiplication and
           | division they need to be re-normalized periodically, and that
           | re-normalization is slow (running gcd). Under addition and
           | subtraction, rationals are great.
           | 
           | *the one big difference is that division rounding is
           | different
        
           | filmor wrote:
           | None of these would be a problem if people would round
           | instead of truncate by default.
        
           | BeetleB wrote:
           | > The old "Multiplying and dividing is associative... for the
           | set of reals
           | 
           | Psstt... Dividing is not associative for the reals.
        
             | kaslai wrote:
             | I think the original intent was referring to the fact that
             | given the same operand, the order of multiplication and
             | division shouldn't matter, e.g. A * X / X should give the
             | same result as A / X * X, but in reality they can give
             | different results when done by a computer due to precision
             | limits, overflow, etc.
        
               | BeetleB wrote:
               | Ah that makes sense.
        
           | thrdbndndn wrote:
           | 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?
        
             | zem wrote:
             | it's because the calculation (1/ 55) is less than the real
             | number 1/55, so when you multiply it by 55 you get a result
             | less than 1.
             | 
             | 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
        
               | zelphirkalt wrote:
               | This effect becomes more pronounced, when substracting
               | (but probably also adding) small amounts from big
               | amounts, because more bits are needed to store the big
               | number's more significant digits and floats then lose
               | bits for the less significant digits. That is why, if one
               | needs precision, needs to sort numbers first and then
               | start calculating with the smallest numbers first,
               | working ones way up to the bigger numbers.
        
               | thrdbndndn wrote:
               | Thanks!
               | 
               | I misread the equation and thought it's `old_hp *
               | (new_maxhp / old_maxhp)`.
        
             | foota wrote:
             | It's neither. Diving 1 by 55 gives some number x where x is
             | arbitrarily (there are standards for how to choose) chosen
             | between the closest 2 representable floats to 1/55. Then,
             | they multiply x by 55. If x is bigger than 1/55 here then
             | the result is > 1. But, if it was rounded down then it will
             | fail because x * 55 will be less than 1.
        
           | layer8 wrote:
           | > So it should have been `(new_maxhp * old_hp) / old_maxhp`.
           | 
           | I don't see how that couldn't run into similar problems. I
           | would use `clamp(old_hp * (new_maxhp / old_maxhp), 1,
           | new_maxhp)` (assuming that old_hp >= 1).
        
             | iforgotpassword wrote:
             | It could, but only at very large values. It's at least the
             | much more robust solution.
        
             | tpolzer wrote:
             | If you assume that all three numbers are small integers,
             | doing the division last minimizes precision loss (and
             | doesn't have any loss if the accurate result is
             | representable as a float).
             | 
             | Otherwise you get loss from the division and then multiply
             | that loss (by >1).
        
         | m463 wrote:
         | I wonder if the fix would be vulnerable to "nudging" hit points
         | up by picking up and dropping a relic? :)
        
       | ummonk wrote:
       | Suppose someone with 1 hitpoint actually transformed into a unit
       | with less hitpoints - should they then die? Seems to me the fix
       | should be to represent hitpoints as integer values, and round up
       | during conversion.
        
       | vishnugupta wrote:
       | 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.
        
         | rwalle wrote:
         | Probably not the answer you were expecting, but you may want to
         | consider getting an Xbox Series S (that is often on sale at
         | $250 or even $200), plugin your mouse and keyboard and you'll
         | have a great experience. Yes that is a "dedicated game rig" but
         | much more affordable than a usable gaming PC. Of course, only
         | if you also play other games on that xbox and can live with no
         | disc. This is what I would do in your situation.
        
         | Sakos wrote:
         | This is what I found for the M1
         | https://old.reddit.com/r/macgaming/comments/ms8081/age_of_em...
         | 
         | Seems to work fine with Wine and Crossover.
        
         | steivan wrote:
         | I use a small machine at Paperspace (P4000 with 100GB) and play
         | it remotely with Parsec. The experience is very good and costs
         | me about 10-15$ a month.
        
           | moffkalast wrote:
           | How is it that a modern laptop manages to be worse at gaming
           | than the average PC 20 years ago? Surely you one can at least
           | run it in an emulator of some kind?
        
             | qwytw wrote:
             | You can just run with Wine, it seems to work mostly fine.
             | 
             | Also you could probably just install ARM Windows in a VM,
             | not sure how good is the Windows translation thing compared
             | to Rosetta though you might end up with a worse experience
             | than just by using Wine.
        
       | qprofyeh wrote:
       | Guess that's what those Epsilon checks are for.
        
       | riffraff wrote:
       | 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
        
         | a_e_k wrote:
         | 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 potential plots
         | against dead children.
         | 
         | - Fixed that bedtime stories can no longer be told to dead
         | children
         | 
         | - You no longer punish yourself when a prisoner tries to flee
         | from your prison by charming the guard.
         | 
         | - The Orthodox Patriarch is now concerned if Orthodox rulers
         | have heathens in their court.
         | 
         | - Discovering two vassals of the same sex engaging in carnal
         | activities during pagan feasts no longer let's the liege join
         | in on the fun.
         | 
         | - Ambitious claimant adventurers now move out of the realm if
         | they are targetting their liege
         | 
         | - No longer possible to banish your spouse or concubine
         | 
         | - Decreased monthly decadence gain.
         | 
         | [0]:
         | https://forum.paradoxplaza.com/forum/threads/patch-2-4-chang...
        
           | m463 wrote:
           | Wow, the logic to model these kinds of social structures
           | makes my head hurt. But you would have to do it.
           | 
           | And I wonder about dead children. I remember one game where
           | there could be no dead children.
        
             | SXX wrote:
             | And in CK you can actually sacrifice a child for some
             | gains.
        
             | djur wrote:
             | Starting sometime in the mid-'00s it became common practice
             | to prevent players from gratuitously killing child NPCs. As
             | I understand it, this was to comply with ratings boards. A
             | good example of the change is Fallout. Fallout 2 had child
             | NPCs with no invincibility, and in the German release they
             | just made the children invisible and untargetable. This
             | caused some issues. In Fallout 3 children can't be damaged
             | by the player, but you can cause their offscreen deaths
             | (with a nuclear bomb, even). Similarly, I think Crusader
             | Kings can get away with it because there's no visual
             | depiction.
             | 
             | As an aside, while I understand the motivation for this
             | kind of thing, I remember finding a child's body after a
             | firefight in Deus Ex and having a moment of moral anxiety
             | as I wondered whether it had been one of my stray bullets
             | or someone else's (or even unrelated to the fight).
        
             | mcv wrote:
             | If you don't want to see terrible things happening to
             | children, I'm afraid CK is not the game for you. The game
             | goes out of its way to model the worst aspects of medieval
             | life.
        
               | [deleted]
        
             | boomboomsubban wrote:
             | The logic is less complex than you'd think. It's generally
             | something like "has flag paranoid, has flag child (patched
             | to has flag alive child), then 1% yearly chance of fearing
             | a plot." It feels complex, thinking of the actual logic
             | killed a fair amount of the fun for me.
             | 
             | >And I wonder about dead children. I remember one game
             | where there could be no dead children.
             | 
             | Probably Skyrim, though it's not rare. Crusader Kings
             | somewhat encourages infanticide, as your brother's bastard
             | could stop your character from inheriting a title.
        
               | tetha wrote:
               | > The logic is less complex than you'd think. It's
               | generally something like "has flag paranoid, has flag
               | child (patched to has flag alive child), then 1% yearly
               | chance of fearing a plot." It feels complex, thinking of
               | the actual logic killed a fair amount of the fun for me.
               | 
               | But doesn't this overlook the emergent complexity from
               | many actors running these simple rules, plus interactions
               | between them?
               | 
               | A lot of the shenanigans in Dwarf Fortress arise from
               | relatively simple actors following their relatively
               | simple plan, until those plans clash or interact with
               | each other and crazy things start happening - such as
               | prioritizing drinking booze over pulling a lever to lock
               | out a monster.
        
               | boomboomsubban wrote:
               | >But doesn't this overlook the emergent complexity from
               | many actors running these simple rules, plus interactions
               | between them?
               | 
               | Somewhat, but it more feels like "wait for good flag, try
               | to activate good flag events. Wait for enemy to have bad
               | flag, try to activate bad flag events." Other's find it
               | has lasting fun despite that, but I found I lost
               | interest.
        
           | riffraff wrote:
           | These are great, they make me want to play the game!
           | 
           | Although I'm not sure why a threesome during a pagan festival
           | has become illegal, I'd have considered that a feature, not a
           | bug.
        
         | acheron wrote:
         | There was a bug fix in Nethack a few years ago to make eating
         | parchment spellbooks break vegetarian conduct. Which at one
         | level makes perfect sense: of course parchment is an animal
         | product. But the fact that it comes from the intersection of
         | several kind of weird systems in the game made it such a great
         | Nethack bug. First, that the game tracks vegetarianism at all.
         | Then, the randomized unidentified item descriptions that are
         | mostly flavor text but occasionally have gameplay properties.
         | And finally, why are you eating spellbooks in the first place?
        
           | schoen wrote:
           | As opposed to eating rings, which can permanently give you
           | the associated magical intrinsics (without having to wear the
           | ring). This, in turn, means that which specific intrinsics
           | you can get that way various from game to game (because you
           | can temporarily polymorph into a metal-eating monster, but
           | some rings are metal and others aren't).
           | 
           | https://nethackwiki.com/wiki/Eating_jewelry
        
       | agumonkey wrote:
       | When read with a history bias, that title makes for a long and
       | deep source of confusion.
        
         | dragontamer wrote:
         | I mean, Age of Empires 2 is a very historical game. Everyone
         | knows that the Halberdiers of the Inca are pretty good
         | especially when paired up with Inca's famous archers: their
         | Crossbows and Arbalesters. /s
         | 
         | These historical games have two ways of doing things. Staying
         | historically correct (which is unbalanced... such as Hearts of
         | Iron where Soviets + Germany will likely crush Poland in the
         | first move every game), or you balance the game so that
         | everyone has a fair chance like AoE2. But balancing the game
         | naturally introduces significant ahistorical aspects.
         | 
         | --------
         | 
         | AOE2's historical campaigns are pretty good though, and
         | somewhat representative of famous battles throughout history.
        
       | sprain wrote:
       | Not knowing it was about a game, that was one of the strangest
       | headlines ever to read on here.
        
         | 0xpgm wrote:
         | Likewise, I thought it was something to do with blood pressure
         | :)
        
         | neom wrote:
         | Conversely, I didn't even have to even click in to understand
         | it hah. Been playing AoE for around 20 years.
        
           | alternatetwo wrote:
           | I was confused as well, because I just had chatted with
           | someone about whether the aoe2 monk conversion ability on
           | certain buildings was hardcoded or not (they can't convert
           | i.e. Town Centers, Monasteries) ... turns out it at least
           | used to be, no idea about now.
        
       | 1024core wrote:
       | I had no clue what the headline was all about. I imagined some
       | Aztec monks with a 1/55 HP motor trying to do something and just
       | dying for no reason.
        
       | shilgapira wrote:
       | What a weird coincidence. I thought at first this would be about
       | the Guild Wars game, but turns out they both have something to do
       | with monks and 55:
       | 
       | https://wiki.guildwars.com/wiki/55_Monk
        
         | hobofan wrote:
         | Yeah, really the only word out of place in the headline was
         | "Aztec". All the rest could have applied to GW.
         | 
         | Fun fact: In the early days (I think the first 6 months or so)
         | of GW there were some item attribute modifiers that I think
         | allowed for going as low as 5 hp, making the character
         | basically invincible, as all damage was rounded down.
        
         | dvngnt_ wrote:
         | farming ectoplasm lfg
        
         | voz_ wrote:
         | Ahh nostalgia. I thought so too. This was such a fun build. I
         | miss GW1 a lot, GW2 missed the mark, and is no true successor.
        
           | Sakos wrote:
           | Man, I miss GW1. That skill/class system was amazing and
           | still goes unmatched in its flexibility and depth today.
        
             | dimgl wrote:
             | I want to create a spiritual successor to this but I'm only
             | a dev, and not an artist. It's really killing me. I want to
             | make a game like this before I die, but it's looking
             | unlikely.
        
           | zeeZ wrote:
           | Depends on what you're after. In terms of mechanics it's a
           | completely different game, but lore wise a phenomenal amount
           | is tied in to GW1. It's almost fan service: the game.
        
         | dimgl wrote:
         | Wow, another 55 monk enjoyer. I used to farm hydras outside of
         | the Crystal Desert with this.
        
       | system2 wrote:
       | I was stopping myself from buying Definitive Edition but I guess
       | I am buying it today...
        
         | dragontamer wrote:
         | Note that the April 2023 patch is live, and perhaps one of the
         | biggest changes to the meta ever. I don't think anyone really
         | knows how the new meta will shake out.
         | 
         | Build orders will absolutely change this patch. Though I guess
         | that only matters if you were hoping to get to a competitive
         | level.
         | 
         | Definitive Edition does have a very good tutorial mode for
         | people interested in online combat, and "Extreme" AI is 100%
         | legitimate (AoE2 Extreme does NOT cheat, you're fighting it
         | fair-and-square, but Extreme does a proper build order so it
         | just "feels" quick if you haven't studied build orders yet)
        
           | fauxpause_ wrote:
           | What's new? Seems kind of crazy to introduce a meta changing
           | past to an old game like this that has managed to stay
           | popular.
        
             | dragontamer wrote:
             | Pre-patch, the meta was growing a bit stale and settling
             | upon Crossbows play, or Knights play. (Or sometimes:
             | Crossbows + Knights). An odd greedy defender might go
             | Mangonel (aka: Anti XBow) + Monks (aka: anti-knights), but
             | the game pretty much revolved around Archers (for civs like
             | Britons) or Knights (for civs like Franks), and their
             | closely related counter-units.
             | 
             | EDIT: case in point. Vikings, a civilization with numerous
             | infantry buffs, was played as an Archer civ in practice
             | because even with all their infantry buffs, XBow / Arbalest
             | was considered stronger.
             | 
             | The biggest change this patch is a new tech for +1 pierce
             | armor to the Longswords line, which can be researched in
             | Castle Age. Furthermore, the entire line of infantry can
             | upgrade faster, and cheaper. Finally, most infantry civs
             | (Malians, Goths, etc. etc.) have gotten significant economy
             | buffs to make them even stronger. Whether or not its enough
             | for infantry to shine is to be seen.
        
             | qwytw wrote:
             | > old game
             | 
             | They have been releasing expansion adding new civilizations
             | every ~6 months or so together with a bunch of balance
             | changes.
             | 
             | > stay popular
             | 
             | It didn't. Not too such an extent as it is now. For years
             | it had only community support and a fairly small player
             | base. Microsoft resurrected it not so long ago.
        
             | pomber wrote:
             | To be fair, it may be the "biggest change ever", but it
             | doesn't change too much. It buff some units that were bad,
             | nerf some units and techs that were too good. But the
             | general meta remains the same. The devs are really good at
             | introducing changes.
        
             | dayjaby wrote:
             | Several infantry civs like inka, sicilians, slavs, vikings,
             | celts and dravidians got _many_ huge buffs. Main change to
             | like 50% of the civs is called gambesons, which adds 1
             | pierce armor to the militia line, basically reducing the
             | damage they get from skirmisher by 50%.
        
             | CorrectHorseBat wrote:
             | More buffs to infantry. Players got better and infantry was
             | barely used.
        
             | arbitrandomuser wrote:
             | There has been drastic meta changes before too but over the
             | years the units that have been made has remained the same.
             | The earlier meta mostly used to be around archers or
             | cavalry , infantry was simply not viable except with
             | certain civilization(goths) and that too only once you had
             | the whole economy set up to spam their cheap infantry
             | continuosly.
             | 
             | Theve been trying to push infantry with small buffs , and
             | this is patch has seen some more buffs to infantry and
             | infantry civs.
        
             | the__alchemist wrote:
             | In addition to the infantry gradual bufs, full-walling in
             | Feudal using a mix of palisades and houses became
             | ubiquitous. Pallisades, buildings-as-walls etc were
             | gradually nerfed. Still popular, but now there's a higher
             | associated cost. I wonder if they're done with those nerfs,
             | or if there's another coming. (Since this technique is
             | still very strong... Or maybe it's that scout rushes are
             | strong!)
        
               | fauxpause_ wrote:
               | How was walling with housing nerfed? I remember doing
               | that a lot.
        
               | the__alchemist wrote:
               | In-progress buildings are easier to destroy IIRC.
               | Palisades cost more and are slower to build.
        
           | vippy wrote:
           | What's up with all of these 18 feudal builds?? And to Hell
           | with the Portuguese fast castle, organ gun OP on arena.
        
             | dragontamer wrote:
             | It was always possible to do 18 pop feudal, but today's
             | focus on luring deer is perhaps the biggest change to the
             | meta?
             | 
             | There wasn't any real balance patch. Just people who have
             | gotten much better at luring deer to improve early build
             | orders. Like 3 years ago, you'd just assume 2x Boar + 8x
             | sheep as your early food source. But today, people are
             | adding 2x or even 3x deer to that.
             | 
             | As deer + boars collect food at the fastest rate, you can
             | more consistently go up at lower villager counts. There's
             | also much more precision: killing boars / deer / sheep
             | under the town center more consistently today than before.
             | 
             | --------
             | 
             | TL;DR: Skill improved, people can do 18 pop today even
             | though its always been possible. So people are just getting
             | faster and more optimized.
        
               | dayjaby wrote:
               | Hera made a video about this recently and attributes it
               | more to all the QoL updates like shift queueing. Sheep
               | actually spawn next to your TC now. Before DE you had to
               | find your first 4 as well.
        
       | devjab wrote:
       | The thing I like the most about this is how the game actually
       | switches out a unit with a different unit depending on the task
       | the unit needs to do. It's not exactly in the reddit post, but
       | rather in the video https://www.youtube.com/watch?v=MytWNbpnAVY
       | where the error is outlined. What a brilliant way to keep
       | complexity down.
        
       | olalonde wrote:
       | 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.
        
         | dragontamer wrote:
         | Note that T90 is like 2300 Elo. Which isn't "top level pro",
         | but I'm pretty sure he'd kick most of our asses.
         | 
         | T90 is probably the strongest dedicated commentator / announcer
         | / caster in this community.
        
           | justrealist wrote:
           | tbh I credit ZeroEmpires with keeping the community alive
           | through the slower times though.
        
         | arendtio wrote:
         | And some players have come a long way. One of my highlights
         | was, when DauT won the Red Bull Wololo 3 tournament as someone
         | who is playing the game 20+ years. And some fans are doing
         | great stuff too, like this video that was released shortly
         | after (DauT is back):
         | 
         | https://www.youtube.com/watch?v=_u-SisO8ers
        
       | syspec wrote:
       | This game is still amazing. I never played it as a kid, but
       | bought the Definitive Edition last year on steam. Already have >
       | 600 hours on it.
       | 
       | Beat RTS ever in my opinion.
       | 
       | Check out big tournament to get an idea of the current scene:
       | https://youtu.be/6krbA4jTUqE
       | 
       | Also I didn't play multiplayer until last month, most of that 600
       | hours was spent in the many many excellent single player
       | campaigns
        
       | winrid wrote:
       | Immediately noticed how the font on the MS's site is very
       | readable. "noto-sans", evidently
        
       ___________________________________________________________________
       (page generated 2023-04-15 23:01 UTC)