• 0 Posts
  • 3 Comments
Joined 1 year ago
cake
Cake day: June 25th, 2023

help-circle
  • You’ve clearly thought about the problem, so the solutions should be relatively obvious. Some less obvious ones:

    • It is impossible to make TCP reliable no matter how hard you try, because anybody can inject an RST flag at any time and cut off your connections (this isn’t theoretical, it’s actually quite common for long-lived gaming connections). That leaves UDP, for which there are several reliability layers, but most of them are not battle-tested - remember, TCP is most notable for congestion-control! HTTP3 is probably the only viable choice at scale, but beware that many implementations are very bad (e.g. not even supporting recvmmsg/sendmmsg which are critical for performance unlike with TCP; note the extra m)
    • If you don’t encrypt all your packets, you will have random middleware mess with their data. Think at least a little about key rotation.
    • To avoid application-centric DoS, make sure the client always does “more” than the server; this extends to e.g. packet sizes.
    • Prefer to ultimately define things in data, not code (e.g. network packet layouts). Don’t be afraid to write several bespoke code-generators; many real-world serialization formats in particular have unacceptable tradeoffs. Make sure the core code doesn’t care about the details (e.g. make every packet physically variable-length even if logically it is always fixed-length; you can also normalize zero-padding at this level for future compatibility. I advise against delta-compression at this level because that’s extra processing you don’t need).
    • Make sure the client only has to connect to a single server. If you have multiple servers internally, have a thin bouncer/proxy that forwards packets appropriately. This also has benefits for the inevitable DDoS attacks.
    • Latency is a bitch and has far-ranging effects, though this is highly dependent on not just genre but also UI. For example “hold down a key to move continuously through the world” is problematic whereas “click to move to a location” is not.
    • Beware quadratic complexity, e.g. if every player must send a location update to every player.
    • Think not only about the database, but how to back up the database and how to roll back in case of catastrophe or exploit. An append-only flat file has a lot going for it; only periodic repacking is needed and you can keep the old version for a while with a guarantee that it’ll replay to identical state to the initial version of the new file. Of course, the best state is no state at all. You will need to consider the notion of “transaction” at many levels, including scripting (you must give me 20 bear asses for me to give), trading between players, etc.
    • You will have abuse in chat. You will also have cybersex. It’s possible to deal with this in a privacy-preserving way by merely signing chat, not logging it, so the player can present evidence only if they wish, but there are a lot of concerns about e.g. replays, selective message subsets, etc.
    • There will be bots, especially if the official client isn’t good enough.
    • It’s $CURRENTYEAR; write code for IPv6 exclusively. There are sockopts for transparently handling legacy IPv4 clients.
    • Client IP address is private information. It is also the only way to deal with certain kinds of abuse. Sometimes, you just have to block all of Poland.
    • Note that routing in parts of the world is really bad. Sometimes setting up your own dedicated connection chain between datacenters can improve performance by orders of magnitude, rather than letting clients use whatever their ISP says. If nesting proxies be sure to correctly validate IPs.
    • Life is simpler if internal stuff listens on a separate port than external stuff, but still verify your peer. IP whitelisting is useless except for localhost (which, mind, is all of 127.0.0.0/8 for IPv4 - about the only time IPv4 is actually useful rather than a mere mirage).

  • There’s tends to be one major difference between games and non-game applications, so toolkits designed for one are often quite unsuitable for the other.

    A game generally performs logic to paint the whole window, every frame, with at most some framerate-limiting in “paused” states. This burns power but is steady and often tries hard to reduce latency.

    An application generally tries to paint as little of the window as possible, as rarely as possible. Reducing video bandwidth means using a lot less power, but can involve variable loads so sometimes latency gets pushed down to “it would be nice”.

    Notably, the implications of the 4-way choice between {tearing, vsync, double-buffer, triple-buffer} looks very different between those two - and so does the question of “how do we use the GPU”?


  • Let’s introduce terms here: primarily, we’re plotting “combat power” as a function of “progress level”. Both of these are explained below.

    I assume we’re speaking about a level system that scales indefinitely. If there is a very small level cap it’s not important that all this math actually be done in full (though it doesn’t); balancing of the constants is more important in that case.

    The main choice to be made is whether this function is polynomial or exponential; the second choice is the exponent or base, respectively (in either case, call it N). Note that subexponential (but superpolynomial) functions exist, but are hard to directly reason about; one purpose of the “progress level” abstraction is to make that easier.

    Often there are some irregularities at level 1, but if we ignore those:

    • in a polynomial system, if N level-1 characters can fight equally to 1 level-2 character, then N level-10 characters can fight equally to 1 level-20 character.
    • in an exponential system, if N level-1 characters can fight equally to 1 level-2 character, then N level-19 characters can fight equally to 1 level-20 character.

    The third choice is whether to use absolute scale or relative scale. The former satisfies the human need for “number go up”, but if your system is exponential it implies a low level cap and/or a low base, unless you give up all sanity and touch floats (please don’t). The latter involves saying things like “attacks on somebody one level higher are only half as effective; on someone one level lower it’s twice as effective”, just be sure to saturate on overflow (or always work at the larger level’s scale, or declare auto-fail or auto-pass with a sufficient level difference, or …).

    Note that relative scale is often similar to how XP works even if everything else uses absolute scale, but mostly I’m not talking about XP here (though it is relevant for “is it expected for people actually to hit the hard level cap, or are you relying on a soft cap?”).


    Progress level is purely a utility abstraction. If you have exponential tiers (e.g. if you make a big deal about displayed levels 10, 100, 1000 or 4, 16, 64, 256) you might choose to set it to the log of the displayed level (this often matches human intuition, which is bad at math), though not necessarily since it might unnecessarily complicate the math with the cancelling you might do.

    If you want xianxia-style “punching up is harder the higher you go” (and you’re actually), it might be some superlinear function of character level (quadratic? exponential?).

    Otherwise it is just the displayed character level (in some contexts it is useful to consider this as a decimal, including the fraction of XP you’ve got to the next level. Or maybe not a fraction directly if you want to consider the increasing XP requirements, though for planning math it usually isn’t that important).


    Combat power is usually a product of up to 6 components, in rough order of importance:

    • effective health (including shields and damage resistance, if applicable - the latter is often hyperbolic!)
    • attack damage (including math for crits - though if they’re a constant max multiple you can ignore them)
    • attack chance (as modified by enemy dodge). Often this is bounded (e.g. min 5%, max 95%) so you get weirdos trying to use peasants to kill a
    • number of targets that can (practically) be hit with a single attack. Usually this involves compromises so might be ignored.
    • distance at which the attack can be performed. Usually this involves compromises so might be ignored.
    • how long you can keep fighting effectively without a rest (regeneration is relevant for this, whether innate, from items, or from spells). This is only relevant for certain kinds of games.

    So the net result is usually somewhere between a quadratic and a cubic system relative to the scaling of the individual components. If the individual scaling is exponential it’s common to just ignore the polynomial here though.

    Things like “stats” and “skills” are only relevant insomuch as they apply to the individual components. One other thing to think about is “how effective is a buff applied by a high-level character to a low-level character, and vice versa”, which is similar to the motivation of level requirements for gear.