This story is going to be a counter to a much more academic study of projectile physics, several stories on Unity game design and further. Or so I think at the moment. We’ll look at the games Counter-Strike 2D, Dungeons of Dredmor, RimWorld and two games still in development, one by me and one by NoImageAvailable.
Over the years I’ve managed to spend quite a bit of my time watching Youtube videos, playing Runescape, Team-Fortress 2 and other free-to-play games. I spent absolute ages in some games and video series, but somehow, without compromising (much) on grades in education, I also spent a lot of time drawing pixel art, editing sounds, designing maps, writing Lua, C# and proprietary game code. Eventually, I decided to wrap up these skills into a neat package which will be my game currently in development.
Here’s a bit of a screenshot of something I made today, as a teaser of what’s to come after the personal story. Any idea what’s going on? It’s a bunch of bullets ricocheting off of a piece of pixel art. Neat-o!
In The Beginning, The Counter-Strike 2D Era
Entering middle school, I was prone to download and play random, free games online. One time I stumbled upon CS2D, a game developed by Peter Schauss a.k.a DC, a single developer of his Unreal Software game studio. Being a little, obnoxious teenager, I would look at the File Archive on the site and comment on every file how much better I could do it. In-game, I met someone in an AWP server and we decided to set up the clan Counter-Strike Professionals (CSP). While I’ve never met the guy again after about a month of working together, I kept the tradition alive of calling all my files after the make-believe clan.
In the beginnings, I was always intrigued with the map editor that came with the game. The simplicity of 32×32 pixel tiles, a pretty versatile Lua scripting language and huge amounts of tricks you could apply in the map editor itself meant I had a field day making maps. Most of them weren’tquiteamazinglooking, but I didn’t care. I got likes and comments, I even got myself into a Skype server of like-minded (e.g obnoxious) teenagers and we had fun talking shit on everyone on the site.
I realised that map making wasn’t a valued skill on the site when I uploaded a pixel art gun sprite and it got more likes than any one of my maps. It was a piece of shit! I started to shift my focus a bit towards graphics design instead of map design. Making guns became one of my favourite pastimes. The sprite of a gun was just the start – some of the most important parts of a good gun were the sounds. As I drew more and more guns, my skills in drawing them became more and more refined and I made somemodernmasterpieces, to my mind at least.
Then, I saw how many likes some of the bigger Lua scripts got and I decided this is the way forward. Some of my first scripts didn’t get much attention, but I soon joined several projects not only as map maker, but also as graphics designer and Lua scripter. I started making maps, graphics and scripts to blend into a single map. I took on requests from the forums to makespecificscripts. Some of my favourite work was the Gore Extension, which featured more extreme amounts of blood, body parts and bones upon death. Walls would become painted with blood, floors would turn into blood baths. I loved writing it, play testing it, uploading it and seeing the responses. It was such a high.
In the final days of my Unreal Software adventures, I joined a map making competition with a $50 first prize and your map added to the game! One of the judges disliked my map and nothing can be found about the competition anymore. I went all in. I spent the good part of the competition time of about a month drawing the map, play testing it in an early state for fun and completeness. I hand-crafted pixel art specifically for the map which is still used by a large amount of map makers for the game. This was during the time of my middle school exams, and I barely studied. All my waking hours went into this thing for about three weeks straight. And.. I got first place! DE_Vantage was added to the game and is now found in all of the promotional material. It’s a gorgeous map, but most importantly, I made everything myself. I earned it.
The Dungeons of Dredmor Era
After getting a taste of gun sprite crafting, I started watching TotalBiscuit on Youtube and in his WTF Is..? series he was rather positive about this indie game Dungeons of Dredmor. Sadly, the developers Gaslamp Games seem to have gone bankrupt after their second game Clockwork Empires failed to profit… or so I think… I had a great time drawing sprites for this beautiful game with gorgeous pixel art. I learned a huge deal of pixel art techniques just from looking at the sprites that were crafted for this artistic masterpiece.
I went the whimsical route with myuploads to the Steam workshop, but I quickly started crafting some things which I was absolutely in love with. There’s something so quaint about pixel art, much like making hand-sewn mosaics, where a single pixel makes a world of difference. Some of my favourite pixel art work is in Mo’ Steampunk and the aptly named TF2 Mod, while I improved upon some of my CS2D work in Dredrim, published by Enderborn with all the art by me. The fact that Enderborn wrote the stats for the armours meant I could focus completely on the graphics and it was beautiful.
The RimWorld Era
My years working with RimWorld were a blast, but as all things do, they simmered down. Me and NIA discussed in detail the design purposes of many of the parts of CombatRealism, and with every new Alpha version of RimWorld (which happened about once every 6 months) I would join the team in fixing all bugs I could. The main author, however, had several other ambitions and often left the mod for what it was in-between alphas, for which I personally can not blame him. My own mod, Rimfire, which I had published all the way back in RimWorld A8e, January 22nd of 2015, had been updated over the years, but as the game updates slowed down, so did my mod updates.
I joined CombatRealism (now Combat Extended), a mod for RimWorld, to implement the best possible gun system I could with the knowledge I had. The main author, NoImageAvailable, left me to work on this undertaking on my own. After implementing simple Newtonian bullet paths, adding a “Z-level” to the game, somewhat helping with implementing the in-depth ammo system, adding ammo explosions, adding ways for projectiles to interact with top- and bottom surfaces (which didn’t exist in the base game) and adding more accurate hitboxes for the 2D, top-down game… I wasn’t done quite yet. I wanted more – ricochets, penetration, non-Newtonian projectile physics.
The Redacted era
As time went on, NIA became less involved with Combat Extended and more involved with developing his own game, blending RimWorld with XCOM. Being not particularly intrigued with it, I spent the rest of my time improving upon the Combat Extended projectile framework and adding ricochets. I figured that the way in which projectiles were handled was fundamentally wrong to do anything more exciting than I had already done – we used simple Newtonian paths, and I wanted to have a force-based approach where forces were applied and handled each tick, much like in Unity.
When I joined the project I was assigned to what I did best – projectile physics. I wrote some very in-depth system in Unity C#, pretty much as I would have wanted to implement it in Combat Extended but now I had the ability to do so. I spent entire days researching ricochet and penetration still, although NIA couldn’t care less about it. The game was in a terrible state in terms of performance, so I preferred leaving the script open and ignoring the game files otherwise. After a while, development also slowed down and I had a very busy period in university – we’re talking around February 2019.
Developing an own video-game
I want to develop and publish at least one video game in my life. Pulling up my trousers, I decided to get done with it.
It’s not much yet in terms of content, but that only means I have a great time working on the mechanics. I don’t quite know what to say about the game yet, so above you can see in-game a bunch of projectiles ricocheting off of a metal roof (bottom left) and below you can see a bit of concept art.
The idea right now is to make an extremely violent simulation of guns, bullets, projectiles, explosions, impacts, ricochets, etcetera. All of this would take place in a predominantly pixel art world. Right now I am toying with the idea to make absolutely every pixel in the game “destructible”. It’ll be cool.
The End
I hope you’ve had fun reading about some of my previous work. I hope to eventually release the game related to the screenshots above. It’ll be fun to see the fruits of my long term labour.
When do you understand a system? When you can translate, model and simulate it. Biochemical systems are no different, however their simulation hits several hurdles: few systems are analytically solvable, then normal ordinary differential equation (ODE) solvers may not handle large numbers of species types, ODEs become too complicated to handle or a combinatorial explosion in species types exists. The following is a casual stroll through solutions to these problems.
If you come from a chemical engineering background like me, your first instinct will be to write a mass balance: accumulation = in – out + production. If your system is very small, you might be able to integrate the resulting ordinary differential equations (ODEs) by hand. If you crave mathematics and you’ve got a weekend to spare, you might even find some regularities in a larger set of mass balances and get close to an analytic equation for the system. But after these safe systems, you come into different territory:
Hurdle 1: The system is not simple and not regular
Almost any biochemical system will contain extra reactions that spoil the simpler approaches. If the amount of mass balances to be written is relatively small, you might get away with software like Tenua or other lightweight ODE solvers that rely on Runge-Kutta methodologies. A few dozen ODEs remains manageable, or a reaction like chain polymerization where you copy-paste the same ODE and change numbers. With more and more equations you’ll look into Excel or Google Sheets to automate the task of writing these ODEs, and at one point you’ll come across the more intense ODE solvers like Copasi. The major problem with such solvers is the learning curve for their interface, which is why I personally went with BerkeleyMadonna (BM). Here you can generate large numbers of ODEs by using array formulas. If your reaction is “stiff” enough you’ll want to graduate to the Rosenbrock solver of BM rather than the Runge-Kutta or Euler integration schemes. Almost every enzymatic reaction system is stiff, and the benefits of Rosenbrock outweigh the loss in accuracy if you properly set up the error tolerances.
Biochemical systems involving gene expression, polymerization and whole-cell simulation are however particularly prone to bad computation time scaling. BM and other (stiff) ODE solvers will run particularly slow as the number of species increases – this is because matrix inversion of all ODEs scales in computation time by the cube(!!) of the number of ODEs. If you can simulate 100 species in 1 minute, then 200 species will take you 8 minutes. On the flipside, the contents of those ODEs can be as complex as you want because their integration by matrix inversion is the most taxing step. The way most ODE solvers are set up, writing your own ODEs is grueling manual labor and it can take days, weeks or months to complete some of the more involved reaction networks. Being unable to run these models performantly on completion is a massive problem. Luckily there are different algorithms with better scaling properties:
Hurdle 2: The system is too species-heavy to simulate
The problem with ODEs is that you need to properly integrate (i.e.: matrix-invert) every single one of them at once, even when nothing happens in the majority of the ODEs. Stochastic simulation instead solves reactions only for those species that currently exist, and rates are scaled by integer counts of those species. Depending on the implementation this can result in computation times scaling with the square to the number of reaction events. Stochastic simulation is best performed with software like RuleBender which uses the BioNetGen Language (BNGL) suite of tools.
Stochastic simulations tend to follow the Gillespie Stochastic Simulation Algorithm (SSA), however not all simulators lend themselves to the needs of a biochemical reaction network. The majority of biological systems can be classified far easier and better by listing the rules of interactions rather than every individual interaction – for example, signalling cascades tend to aggregate large numbers of bi-/tri-functional proteins or small molecules. Writing every single ODE for all states these aggregates can be in is inefficient, and for interactions with tri-functional species it is a combinatorial explosion that even BerkeleyMadonna’s arrays cannot easily handle without clever and time-consuming coding. This all to introduce rule-based methods, where you could write all of these interactions as a handful of rules. Non-splitting step polymerization could for example be handled in BNGL using A(x!?,y)+A(x,y!?) => A(x!?,y!1).A(x!1,y!?). BM would require several arrays to be set up to handle this elegantly.
Rule-based stochastic simulation is then split up into two approaches. First, a front-heavy ‘network-based’ approach where the rules are converted to individual reactions to be simulated by ODE- or SSA solvers. The benefits of the network approach are computation time scaling linearly(!!!!) to the number of events to be simulated. You can simulate 1,000,000,000 events in 1 minute, so 2,000,000,000 events take 2 minutes. The up-front cost of generating the network can be particularly inconvenient for complex reaction networks of large polymers, for example the BNGL rule above when applied in reverse results in all N species generating N splitting reactions. The upfront computation costs scale by the number of existing- and generated reactions, reaching several hundred seconds by the sixth iteration of the rules and growing exponentially. If you limit the size of the resulting network to six steps, your polymerization network will also be quite underwhelming. This issue is a major hurdle for reaction networks with universally applicable rules:
Hurdle 3: The system is too rule-heavy to pre-build
Luckily RuleBender affords us with a runtime-heavy ‘network-free’ approach through NFsim. Instead of building a network and coasting on linear computation time, the network-free approach applies rules directly during each simulation step. The computation costs now scale to the square of the number of reaction events, however the resulting species can become as aggregated as the rules dictate. You will have to compromise with this reaction by limiting the number of species – about 50,000 species that react once (polymerization) will take 1 minute, but if they react twice it takes 4 minutes. You’ll want to simplify every rapid equilibrium away, as this type of rule drains event budget significantly – a mutarotation equilibrium of fructose takes about 45 reactions per species over 1 hr of simulated time; glucose takes 22 reactions per species.
NFsim and apparently KappaSim are the best way to start building a complicated, highly branched polymerization network before introducing simplifying assumptions to allow the system to be easily transferred into a network-based method.
Representative scaling
Rosenbrock
Network-based
Network-free
Scaling tCPU
∝ ODE3
∝ events
∝ events2
1 minute solves +/-
100 ODEs
1,000,000,000 events
50,000 events
Above: CPU time in seconds of 1 to 100,000,000 species (rows) that react in 1 to 1,000,000,000 events (columns). The events up until 10,000 are simulated by NFsim, and the column labelled ODE by BerkeleyMadonna’s Rosenbrock. Events from 100,000 are simulated by Gillespie SSA.
Guns are extremely complex mechanical marvels. Or, at least they are from the perspective of implementation into video games.
One of the most complex gun handling systems I have encountered is in the game Receiver, which contains the 1911 Colt (Colt M1911A1), Glock (Glock 17) and Revolver (S&W Model 10 “Victory”) according to the fan-made wiki. Decompiling Receiver’s source code (specifically Assembly-CSharp.dll’s GunScript.cs) shows us the level of programming at play here to make each gun handle realistically.
To understand the intricacies of programming, let me take you through each component of gun mechanics, the way Receiver implemented it and how it should be extended to handle all possible guns.
Down the rabbit hole; starting from ActionType
ActionType is an enum holding the values DOUBLE and SINGLE, to handle a Double Action Only (DAO) or a Single Action Only (SAO) gun. DAO guns cock their hammer twice when the trigger is pressed, while SAO require the hammer to be cocked manually. Out of these guns, only the Glock has a DAO trigger. In real-life, there are additional actions such as SA/DA which allows both actions with the DA-action taking a longer trigger pull. Additional actions can also be found in Wikipedia. Furthermore, triggers serve vastly different purposes on guns other than hand guns and the indication “DAO” applied to semi-autos has a completely different meaning in terms of actions required for shooting compared to DAO revolvers.
The entire DAO/SAO distinction is handled in only three methods in the GunScript.cs code. What is nice to notice is that ApplyPressureToTrigger() first handles input – pressure_on_trigger is set depending on if the trigger is held for 1 or for more ticks to PressureState.INITIAL or PressureState.CONTINUING. A check is performed whether yolk_stage is YolkStage.CLOSED for swing-out cylinders (typical on revolvers). Afterwards, pressure on the trigger is handled first by a behemoth of an AND-chained IF-statement. After the trigger is pressed, DAO guns are cocked twice after their cock state is reset – meaning, all DAO-like guns in Receiver are self-cocking double actions (SCDA). In real-life, such an SCDA would have a stronger trigger pull on the first round, although this isn’t simulated in Receiver.
Gun types are distinguished using gun_type, both of which have different forms of handling. Furthermore, AUTOMATIC guns are checked to have their slide closed. Receiver now knows that a hammer should fall – therefore, a sound is played regardless. For REVOLVER, the cylinder is cycled one forward before firing a round — in real-life, it is often cocking the hammer which causes cylinder cycling, not pulling the trigger, although it varies between guns. If there’s a bullet in the chamber, it is fired and a sound is played. The round is destroyed, an empty round is created, along with a muzzle flash and a physics-enabled bullet. The slide is pulled back on an AUTOMATIC. Recoil is simulated.
What is immediately noticeable is that ApplyPressureToTrigger() can be split up into a few parts:
Input handling — distinguish pressing/holding a button through PressureState
Pre-action exception handling — if the yolk isn’t closed on a swingout, we can’t shoot
Pre-action AND-chained IF-statement — a list of conditions is checked for, most only relevant for one gun
Action distinctions — distinguish between gun_type REVOLVER and AUTOMATIC
Action — the hammer is dropped
Action — a bullet is fired if there is one present
Post-action — (self-cocking) DOUBLE action triggers are cocked twice.
Some of the code for revolvers is completely useless for automatics, while some code for double-action isn’t necessary for single-action. Some code is also shared, however. Furthermore, there is a very clear pattern of Input — Pre-Action — Action — Post-Action. Ideally, GunScript would be split up into each gun on its own for a high level of customization, yet there should be a consistent basis which could be shared. For example, all guns will spawn some bullet. All guns will react in some way to their trigger being pressed — although, some guns will resist trigger presses in certain mechanical states.
Handling commonalities between firearms
All firearms shoot a projectile. First, a projectile along with whatever is associated to it or whatever is required to launch it is loaded into a chamber. Second, some contraption ensures acceleration of that projectile, be it through gas expansion or perhaps by using magnets. Many firearms use a trigger, although matchlock guns (and similar guns) do not. Many firearms use a hammer, although some use a striker; and matchlocks, of course, do not use any form of hammer/striker, instead relying on a lit fuse to ignite gunpowder. While many firearms have some form of feed system (a cylinder, a magazine), many break-action/double-barrel hunting shotguns do not, along with older rifles such as, again, matchlocks. The vast majority of firearms uses a barrel to aid in directing gas expansion, although perhaps alternative directing components could be thought of.
In its most bare form, a firearm therefore requires these components:
A projectile
A way to accelerate the projectile
A way to direct the projectile towards any target
The projectile in its initial state is very different from its accelerated state, furthermore the flying projectile’s properties beyond its initial properties (when leaving the firearm) do not generally rely on the firearm — although in some games, rockets can be steered in flight, of course. However, let us assume that a steered rocket relies on the player’s aim rather than on any properties of the gun — or, if it relies on the signal sent from the gun, let us assume that it is only the signal which matters, not the rest of the gun. Then, we can separate the flying projectile from a “cartridge” as it is loaded into the gun.
With our cartridge separated off, we can expand definitions. Unless our gun somehow expands into our universe, the location of the cartridge in the gun is of importance. If the cartridge is outside of a region of space in which it can be “fired”, nothing will happen upon operating the firearm. Therefore, every single firearm will ALWAYS have at least one position in which a cartridge MUST be placed for operation. Exceptions to these rules are quite annoying – for example, a flamethrower would perhaps not be considered a firearm, although many games do consider it as such. Perhaps the presence of fuel in the flamethrower tank can be compared to the presence of a cartridge in a firearm chamber.
Let us leave alone the acceleration matter for the moment, since it is a great source of issues. Certainly, it is the more complicated part as SAO/DAO, bolt action/break action arguments stem entirely from this matter of accelerating the projectile.
Finally, directing a projectile towards a target is generally performed by constricting movement of the projectile during acceleration. In fact, unrestricted movement would result in completely different phenomena than those often associated to firearms. Exploding fireworks are a case of unrestricted movement upon projectile acceleration. If your firearms acted like a piece of fireworks, their projectiles might easily fly into your direction rather than in the direction of a target.
On the other hand, some rockets direct themselves towards a target not through firearm-specific restriction but through projectile-specific control. Firing such a heat-seeking missile from a firearm would perhaps not fit the paradigm of a firearm. Not to mention that such a rocket would not need to be accelerated at all, since it would accelerate itself in flight. The only use for a firearm in this case would be as a “Launcher”, to set off and to direct the missile.
Updating these definitions, a firearm can be defined as:
A cartridge (which turns into a projectile)
A chamber (to put the cartridge in)
A way to accelerate the projectile OR a way to set off the cartridge OR a form of ignition
(Optional) A restriction to projectile movement
Perhaps it is fair to test these definition from a different perspective. Namely, in a video-game, the definition of a firearm is more related to:
Interaction (LMB “fires”)
Control (mouse “aims”)
(Optional) Resource management (R “reloads”)
From this gameplay-directed approach, Interaction IS setting off a cartridge/igniting/accelerating the projectile. Control is quite unrelated to the firearm, as it is determined by player movement. Resource management instead relies on a chamber (the allocation of resources) and a cartridge (the resource). While some games use infinite resources, the presence of a cartridge and its effects on projectile properties is considered. Perhaps the most basic interaction with a video-game firearm can therefore be reduced to the following:
Loading a cartridge resource
Preparing a chamber through interactions
Firing a projectile
Some firearms will not require R to be pressed often, in which case they have a sort-of feed system. Some require different button presses to prepare the chamber, but some do not require any additional presses. Therefore, the most basic form of firearm is this:
Press R – reload chamber. Press LMB – fire projectile
Event loops
Let us extend this idea somewhat by considering the Control — PreAction — Action — PostAction (C-pA-A-PA) loop we found in a single instance of Receiver’s game code. The control handling for reloading a chamber just requires [in pseudo-code]: OnKeyDown(R) => if (PreAction()) { Action(); PostAction(); }. Control can be infinitely expanded to handle lighting a fuse with OnKeyDown(LMB) => if (fuse == unlit) { LightFuse(); countDownTimer = 1f; }.
What is now of utmost importance is the following consideration. Every single component in a gun will change the C-pA-A-PA loop. A hammer is cocked (A) by pressing F (C), unless the cylinder is out (pA) or the hammer is already cocked (pA), and doing so cycles the cylinder (PA). Some of these steps rely on the presence/absence of components. For example, if the firearm has no cylinder, the first pA and the last PA are irrelevant. If the gun has no hammer, the entire loop shouldn’t exist, and perhaps a different loop should exist. If the gun has both a hammer and a bolt, controlling both with F might get complicated — although this is a too complex problem to handle right now. Let’s first consider how the C-pA-A-PA loop should be implemented.
Controls are ideally changeable during run-time by the player of the game, this is a desired feature. Furthermore, it should be possible to assign the same control to multiple actions – which would be separable based on their pA and A outcomes. Thus, pA and A must return a boolean outcome as to whether or not their action has been performed. PA doesn’t need a return type.
More often than not, controls will not change, such that the control ideally only calls the rest of the loop when a change has occurred in button presses. Thus, the loop should rely on keypresses – a sort of k-C-pA-A-PA loop. If no change in keypress occurs, C returns early. If a change occurs, C checks whether any pA-A-PA loops are subscribed to the keypress. Furthermore, pA-A-PA loops could be called without a keypress, for example the post-fire action might recock the hammer, requiring a hammer cocking pA-A-PA loop.
Some A do not require any PA. Some A do not require any pA. An ideal system is therefore able to handle variable numbers of each.
Then, the ideal number of pA-A-PA loops is as few as possible such that less code must be rewritten per gun. On the other hand, the amount of such loops relies entirely on the code surrounding these loops. Do we consider the hammer-like safety on some bolt action rifles to be a hammer? Then, many hammer-specific pA must be ignored if we do this on a bolt action. Is the slide action of many semi-autos similar enough to a hammer? In that case, what do we do with semi-autos having both a slide action and a hammer? If the gun has a striker, is this similar enough to a hammer to share keys? Do the conditions in pA mutually exclude each other, or could a keypress be ambiguous? In all cases, we can not simply lay back and write a few lines of code — lest some interaction with another type of gun is inaccurate to real life.
So.. what structure lends itself best to gun handling?
In my current opinion, an event-based handling of inputs will result in much less cluttered gun handling classes than Receiver. If your game is going to feature a huge array of guns across all eras, consider that a gun in essence is an object which you press LMB and it fires a damaging projectile. Sometimes they must be reloaded with R. Sometimes it must be cocked, but sometimes it is bolt-operated or any other form of operation. Most modern guns have dozens of buttons, slides, triggers, hammers, slides, safeties, firemode selectors, attachments, attachment rails and further, all of which could require a button press to be interacted with. Players are unlikely to want to interact with all of these systems at the same time — allowing simple logic statements to distinguish button presses from one another.
More importantly, each of the firearm’s mechanical systems COULD interact with any other system, and they could do this in various ways. For each additional system, keep enums of ways in which they could work — unless the system is as universal as a trigger or hammer. Exploit the fact that the pA conditions are chained AND’s, meaning you could provide a very large amount of conditions at once. Attach all pA’s, A’s and PA’s in a Start() method and keep track which gun parts added them, such that removing one gun part doesn’t remove the action of another gun part. Have a Remove() method or similar to remove only the relevant parts of a component.
The minimal ideal way to implement pA-A-PA loops would be through Dictionary<string, List<System.Action>> for PA and Dictionary<string, List<Func<bool>>> for pA and A. However, sometimes it may be preferred to have PA called with additional parameters, in which case something like a parameter interface could help, forming System.Action<IParameters> or Func<IParameters, bool> lists in a string dictionary. The dictionaries should be stored on the gun.
Downsides of complex gun handling
Obvious downsides are the fact that enemy AI will struggle to interact with gun complexity. Ideally, AI would handle guns in similar fashion to the player, but this would require significant AI scripting efforts. Time efforts to fire a gun once could also be estimated, perhaps per-component to provide more accurate time values. The easiest fix is of course to follow Receiver’s example of making enemies follow entirely different gun handling mechanics to the player – their turret guns are fully automatic and the turrets present an additional handling challenge of aiming for the vital turret parts. In the end, more stupid AI helps to increase the frustration of difficult gun handling.
In my own game, the issues related to difficult gun handling might be alleviated through the use of a progression system – only presenting the player with difficult to use guns further into the game and as challenges to conquer rather than as hurdles to overcome.
Subtitle: the value of questioning nothing for the type that questions everything.
Several blogs and books, along with personal experience as INTP, have convinced me that a typical struggle of this MBTI type is the search for meaning. Similarly, time spent on the INTP subreddit provided similar voices in this general direction. The following sections aim to provide a hopefully compelling argument towards religiously following something arbitrary for the purpose of providing meaning.
What is love, but blasphemous?
Love is a form of deep meaning to the INTP. When head over heels in love, it is the constant reassurance of feelings through logic that make a certain person irresistable. Sleepless nights and over the top emotions. Anything related to their presence. The bible as well as the Quran tell us not to idolize anyone but God, or Allah. Idolization of a mortal is blasphemous towards such religions. In a similar vein, Jordan Peterson discusses in detail the narrative purpose of every single bible story – as a way to provide future generations with a manner in which to deal with the world.
While this is a heap of information to take in, and I would personally be unable to combine these ideas coherently from the start, I am somewhat fond of the following idea: Meaning is the absence of logical thought. In this sense, meaning can be intuition, perhaps emotion, even sensation. As long as there is a lack of thought, there is a possibility for meaning.
Assuming that Peterson is right to say that religious text aims to serve its followers, not indoctrinate them into a dysfunctional system of narrative but into a functional one, we can further clarify meaning. Meaning by this definition may be to follow religion blindly, to idolize a certain entity, a being, a deity, without any regard to logic and/or reason. Otherwise, if Peterson is wrong, we may be wrongly assigning positive intent to religion. Yet, in my personal experience, it is highly rewarding in terms of meaning to follow someone or something religiously. A meaningful relationship is perhaps one where you are blinded by love. A meaningful religion (which is to say, every surviving religion) blinds you to practically all alternatives.
Devotion as a modernconcept
What causes the sensation of meaning is then perhaps a certain release of chemicals in the brain (after all, people are a bag of chemicals). This would be associable to both feelings of deep adoration, early stages of a relationship, deep religious following. To follow a certain, almighty, unquestioned way in which to interact with the world. No amount of self-help could save you unless you were to devote your existence to them.
Then.. what could a simple person, living in current times do to solve this dilemma of the INTP (meaning is inherently lacking logic)? What nameless entity should you bring into your conscious to devote your life to?
On the one hand, it is easy to say to devote your life to “other people”, to help them emotionally or intellectually, to teach or to design. What this would entail is to truly believe in the good of mankind, to follow a certain gospel of those with similar feelings towards life. Many religions preach some variation on this set of values. Many people devote their lives in one way or another to helping other people. Farms feed, hospitals heal, trucks transport, factories produce, water treatment purifies, power plants energyze. In this sense, society is full of meaning as long as you are willing to religiously believe in the purposes these activities are serving.
But perhaps, and I personally am entertaining this idea for the moment, perhaps there is a greater meaning than each of these. Sure, mankind is nice, but helping mankind is a closed loop. From an ethics perspective, if everyone helped eachother, nobody would drive mankind forward. INTP may not be the kind to help others in this way, instead devoting their lives to furthering mankind’s understanding or progress in the universe or similar abstract, large-scale thought. Science discovers, space agencies design, governments steer, politics keep peace, law maintains a level of justice. To devote your life to such a big deal is more reminiscent of thinking in terms of groups of people. In all cases, you will misrepresent people. You will make errors on an individual level. But, on average you are making a positive impact.
Perhaps extending a helping hand towards animals is a greater calling, not for keeping them as test animals and attractions, but to maintain a self-sustainable ecosystem. To keep mankind as well as its auxiliary organisms alive. Plants for breathing and a vast amount of animals for the breakdown and feeding of these plants. After all, earth is a big blob of molten steel with an extremely thin crust – a large-scale mass separation of gases and solids on which hydrocarbon forms of life emerged in an attempt to leach off of the energy provided by sunlight. Modern humanity is a sort of self-limiting cancer, one which goes against the grain of survival of the fittest, destroying the ecosystems driving its livelihood and perhaps destroying that mold of carbon-material on earth’s surface which we call life. Devoting your life to the environment is definitely worthwhile in the long run.
Whichever path you choose is ultimately irrelevant. Religions are narrative tools meant to simplify life, while often pushing a hidden agenda. There is good reason for christianity as well as Islam to demonize other religions – not for the (often similar) teachings they provide their appraiser, but for their alternative agendas. It is not comfortable to think of religion in such negative light, and I would just like to point it out for consideration. In similar vein, being head over heels in love with a certain person means that your “religion” condemns all other romantic options to a much lower level. Following the cult of veganism for an ideally unspoken reason similarly condemns all alternatives. Ideally, your religious following of whatever entity you choose is ridden with hidden agendas of your own. A devotion to sharing knowledge is a great religious facade towards the hidden agenda of a succesful academic writing career, and your inevitable gained skills would make you highly competent in this area of work – yet, only the religous part is of actual importance. Without this devotion, you would perhaps write to make money and enter a burnout in no-time.
Some parallels with other theories
I can not help but form parallels with WaitButWhy’s blog posts regarding these topics, of religious thought and of “giants” – groups of humans working in tandem towards a seemingly shared goal, much like ant colonies. The INTP archetype, perhaps including many of its followers, is often said to condemn non-scientific thinking, to dislike group-think, to question almost everything. Listening to songs about INTP (by Greg McLeod), we keep ourselves out of the struggle of daily lives by building castles in the sky without setting a foot on earth – what if our calculations are wrong?
And this compells the argument towards religious thinking greatly. We somehow hate reality, we do not enjoy setting foot in this hellhole that does not resemble our ideal thoughts as much as we might want it to. We love building a castle in our minds and attaching every single thought and emotion we can to it, lest we forget its positive feelings it gave us at some point. We cling strongly to relationships. We switch from aethist to strongly religious and back at the drop of a hat. Castles in the sky are our forté.
Therefore, build a sky castle! Come up with the most exciting damn thing your combined thought and feeling can find. Build an unnamed, impossible ideal and cherish it as you idolize a crush. Think about it when you can and feel great doing it. If the castle turns out to be fake or impossible to reach eventually, we can manage as we have managed every other abandoned castle in our life. There’s always new castles to come up with, and every castle is as exciting as the depths to which we allow ourselves to drop reasoning.
As always, let me know your ideas or thoughts on the matter!
Starting from the question why are most games on Steam absolutely never going to be a hit?, I came across Mechanics-Dynamics-Aesthetics (MDA) theory. In this article, I attempt to combine it with MBTI theory and I suggest approaches to make your game design more appealing to people.
Mechanics-Dynamics-Aesthetics (MDA) theory
Based on MDA theory, games can be split up into three interconnected phases: mechanics, dynamics and aesthetics. Programmers create the mechanics of a game, while players initially are drawn towards a game’s aesthetics and interact with dynamics. Therefore, the two groups interact with games in fundamentally different ways, and game design is more about designing the player-interface rather than the programmer-interface of the game.
Furthermore, aesthetics of a game can be subdivided into roughly eight different types, namely sensation and narrative, expression and submission, fellowship and fantasy, discovery and challenge. A good, audiovisual primer on MDA theory is by ExtraCredits, “Aesthetics of Play“. ExtraCredits also suggest competition as an aesthetic, which we ignore for the following hypothesis.
Meyers-Briggs Type Indicator theory
Introducing MBTI, the Meyers-Briggs Type Indicator. MBTI is a theory which poses that people’s character generally follows one of about sixteen archetypes. The original conceptual theory by Carl Jung notes that people can be split up into Sensation (S) and Intuition (N), Feeling (F) and Thinking (T) types at any one time. Meyers and Briggs expanded this theory to one introducing four additional types, namely Extraversion (E) and Introversion (I), Judging (J) and Perceiving (P). While some may suggest that the theory is highly scientifically grounded, others note that the expansion from Jung’s model to Meyers-Briggs archetypes was performed without rigorous scientific background. Nowadays, expansions of MBTI exist, although to my knowledge, no model of character archetypes is sufficiently scientifically proven or provable and not all studies on MBTI result in statistical differences between vastly differing character types.
Due to dubious nature if both theories, caution must be taken to use either as absolute principles in game design. Yet, MDA theory has been applied in game design and is arguably successful. Intuitively, use of MDA theory provides an indication as to why a certain game on Steam looks incoherent. Some games which describe themselves as zombie shooters with survival elements (thus falling broadly within sensation/challenge) lack the visual aesthetics to handle the sensation aspect by using the aesthetic of an “Asset Flip” or they do not seem challenging.
Similarly, MBTI theory has been applied in general for writing believable characters for novels and other narratives such as those found in videogames. Several story writers have been known to use MBTI or MBTI-like theories for writing characters, perhaps in the absence of a sufficiently good understanding of the person they are writing their story about. Traditionally, character writing would perhaps require immense directive input from someone with that particular character in case the writer had insufficient knowledge or experience with that character.
MBTI Functions
MBTI furthermore introduces the concept of functions. Each function is one of the original Jungian archetypes S and N, F and T (sensing and intuition, feeling and thinking) along with a small letter “i” or “e”, standing for internal or external. The function Ne would be external intuition, otherwise known as exploration or discovery. The specific subject which is being explored with this type depends on other functions within a person’s function stack. The function stack can be calculated from someone’s MBTI-type and represents an ordering of functions by their most common use. My MBTI type is “INTP”, which corresponds to the function stack of Ti-Ne-Si-Fe. Having Ti (internal thinking) functionality, my Ne (external intuition) would attempt to explore the world in search of things to think about internally – meaning, I would be rather good at keeping focus during university-level lectures, something which I would say I am. And, when surveying INTPs, statistically significant amounts are found enjoying university.
MBTI-MDA Correlation Hypothesis
The novelty of this hypothesis is to match MBTI functions with the MDA aesthetics. MDA aesthetic names are somewhat open to interpretation. Providing their equivalence through MBTI allows people versed in MBTI theory to narrow down on specific aesthetics for game design rather than on their interpretation of one aesthetic. From personal experience, these two concepts can be mapped as follows:
INTP View on Correlation
Aesthetic
Function
Description
Sensation (sense-pleasing)
Fe (inf.)
Attempt to create sensation in games, e.g through realistic bullet models and ammo explosions. Enjoying sensational narratives such as in Disgaea
Narrative (drama)
Fi
Finding more seriously narrative-driven games such as Mass Effect unappealing, having a general lack of appreciation for lore or backstory
Expression (self-discovery)
Te
I do not see expression as a form of self-discovery, as the aesthetic suggests, instead expression would be a way to organize already-discovered thought
Submission (pastime)
Ti (1st)
I enjoy Runescape, Recettear and Pokémon, games requiring immense repetition of the same monotonous tasks to complete
Fellowship (social framework)
Se
Playing MMORPGs as a single-player experience, not caring for social features in video games and seeing other players more as a novelty
Fantasy (make-believe)
Si (3rd)
Enjoying Lord of the Rings, Harry Potter and other fantasy books, or modding Skyrim to turn it into a hunting simulation
Discovery (uncharted territory)
Ne (2nd)
Enjoying cave exploration in Fallout: New Vegas
Challenge (obstacle course)
Ni
Dislike of (multiplayer) competition in video games or arbitrarily difficult games like QWOP, AoE 2
Functions with brackets are part of INTP function stack, thus 1st-3rd estimates are more accurate and inf. estimate is less accurate.
Multiplayer, online games, party games, social games
Narrative
Fi (1st)
Games with strong emotional content and storylines, like JRPGs
Expression/ Submission?
Te (inf.)
Games with responsibilities and a schedule
Challenge/ Submission?
Ti
Puzzles
Sensation
Se (2nd)
Thrilling games with great visuals, such as FPS, fighting, hack ‘n slash
Challenge?
Si
Not sure, perhaps strategy
Discovery
Ne
Sandbox games, experimental games, games with many options and endings
Fantasy?
Ni (3rd)
Note sure, perhaps heavy use of symbolism
Functions with brackets are part of ISFP function stack, thus 1st-3rd estimates are more accurate and inf. estimate is less accurate. Underlined aesthetics match between INTP and ISFP estimate. Italics aesthetics are guessed by INTP based on description.
RPGs, text adventures, abstract puzzle games, ability to perform levels to perfection
Sensation/ Challenge?
Se
Rhytm games, shooters, fast paced platformers
?
Si (3rd)
Orderly complexity as in turn based strategy, or memorization as in Pokémon
Discovery
Ne (2nd)
Sandbox, multiple endings and side quests, games with many easter eggs
?
Ni
Puzzle adventure games like Myst, Riven, Silent Hill, out-of-sequence metroidvanias
Surprisingly, the ISFP estimates for Se and Fe are opposite the INTP estimates. Narrative and discovery match between estimates. Perhaps it is less intuitive to match functions with aesthetics due to the format of the estimates given by WoodPeckerNo1, as they are only descriptions and they do not match the format of the INTP estimates. INFP estimates seem especially difficult to interpret and I would guess most aesthetics suggested are challenge.
Rudimentary Implications
While this hypothesis is obviously not time-tested, its implications for game design would be large. One example would be to use video games in a way entirely tailored towards a single character archetype to provide each and every major function of theirs, rather than relying on one or two well-polished functions. Such an approach would provide much more rewarding and satisfying games for a minority of people, while doing away with those mechanics and interactions not desired by such a group. Another example would be to cater to both the internal and the external form of a function and apply both aesthetics, forgoing the remaining aesthetics, as a way to provide a broadly satisfying MMORPG experience. Current-day MMORPGs may attempt to cater to every function to great lengths, diluting the player experience of any one particular player towards their character function stack. Combining the sensing functions related to both fantasy and fellowship tells us why a Dungeons & Dragons type of environment is so rewarding to a broader range of players – whether you are there for immersion into a fantasy (Si) or for having a fun time with friends (Se), all function stacks can participate. Those leaning towards fantasy can zone out of social interaction and imagine the fantasy world, while those leaning towards fellowship can ignore fantasy and value the experience for its friendship-strengthening aspects.
Knowing the intricacies of the feeling functions would allow lore-writers to create lore rich in both sensation and narrative, providing an experience enjoyable to both sides of the feeling-type players. From personal experience, I have no desire to read Skyrim lore books due to their bland delivery or their absence of impact on gameplay. Books are a bloating addition to Skyrim which dilutes my experience of looting in the game, something which I otherwise find enjoyable. On the other hand, watching playthroughs of Skyrim and reading the comments, several players enjoy lore books while also enjoying the looting aspect, such that this part of the game is enhanced for their function stack.
This hypothesis warns us against combining fellowship with sensation, or fantasy with narrative. Such an approach, combining different types of functions, means that at best a quarter of your players (with those specific functions in their stack) is satisfied while the remaining 75% are either completely unsatisfied or have an experience requiring them to take notice of only half of the content’s reach, ignoring the latter part as their function stack can not understand it. Taking the alternative, combining fellowship with fantasy, means that the same piece of content, if shaped in a particularly satisfying way for both of these character functions A and B, will completely satisfy those with function A while people with that function remain oblivious to the way in which it can satisfy function B as they do not have that function. Therefore, combining functions with opposite internal/external subscripts guarantees optimal satisfaction to 100% of players.
Conclusion
While testing this hypothesis would be best performed with more rigorous scientific methods, I can’t help but notice the Reddit post “What are some video games which go well with the 8 cognitive functions?“. Immediately, I can tell that I haven’t enjoyed those games suggested for Ni, Se, Te or Fi. I’m on the fence about the Ti suggestions, which I can’t recall specific games for, and I haven’t played many of the Fe games although they seem appealing. I do enjoy Ne games, but Si game suggestions are a major group of games I enjoy.
Let me know what you think, does this hypothesis hold true in your books?
Why exactly isn’t there a multidimensional variant of minesweeper? Let me explain.
For a while I’ve had a copy of 4D MineSweeper (4DMS) on my desktop, a free Steam game supplied by the ever-crafty Julian Schlüntz. Beside chuckling at the immediate absurdity of having such an icon on my desktop every once in a few days, I have played a few rounds of 4DMS and they have felt almost exactly as those first rounds I spent in Microsoft’s MineSweeper. 4DMS reminds me of learning an entirely new system of thinking during countless hours on holidays with a crappy Windows laptop.
I wondered why Schülz picked 4D rather than the always popular 3D or perhaps something as exotic as 5D. I started my quest by creating an n-dimensional MineSweeper clone in Unity. The entire GUI is handled by the UnityEditor for setting number of mines, size of the dimensions and number of dimensions, alongside a 2D texture for visualization.
As occurred to me after spending two days programming the generation of a new playing field, pattern matching is a rather fun game mechanic in multiple dimensions. The representation of n dimensions into n/2 along the x-axis and the rest along the y-axis felt intuitive.
The major problem is that mine laying algorithms have run-time proportional to 2^n up to 3^n. Namely, for each additional dimension, you need to check one or two additional copies of all previously iterated dimensions. It is entirely possible to perform these calculations during play, but ideally the number of tiles you click is more than the number of mines in the field so it is beneficial to perform all neighbour-calculations on game reset.
While this is a major issue on startup, perhaps it doesn’t extend to playing the game, right? Wrong. During play, to mimick the original minesweeper, 3^n-1 tiles must be checked for every tile that is uncovered. This means that in the worst case scenario, your algorithm runtime is proportional to d^n * (3^n-1), with d the (iso) maximum length along each dimension. In lower dimensions, this is very computationally manageable (n=2: 8 calls per tile) and it is the reason why MineSweeper could probably be played on a toaster. In higher dimensions (n=10: 59,048 calls per tile), calculation speeds are crippled by checking the same tile very many times. Even in 4D, each tile would be checked 80 times at worst. Incidentally, playing 4DMS I would often experience lag when clearing a sizeable area of tiles.
Therefore, the real culprit is the floodfill algorithm. At best, a floodfill algorithm can be a scanline or a recursively growing area, resulting in runtime operation proportional to n to 2n. Namely, a 1D floodfill requires checking of the left and right neighbour, unless either position is a wall. A 2D floodfill must check four neighbours per tile, unless walls are involved in which case at least two neighbours must be checked. An nD floodfill checks 2n tiles. This way, n=10 floodfills “only” check each tile 20 times. The issue with such algorithms is that while faster, they ignore the diagonal check found in original MineSweeper and in 4DMS. Furthermore, coming up with any other algorithms which sound differe t from this one, e.g those with 1 per tile calculations, must always be expanded into at least 4n because this is the complexity required by the problem. We want to know the direct neighbours of each tile, therefore we must check the direct neighbours every step along the way. Rewriting between a recursive and a stack-based algorithm doesn’t change this.
Perhaps calculations could be baked into the game on reset by considering which groups of tiles are connected, such that on runtime all tiles with the same group could reveal their numbers at once without floodfill. I thought long and hard about this case. Although, the most general implementation possible would require either rather advanced algorithms for “edge detection” (I don’t know what Im talking about here), a much simplified version which could determine how many groups could be formed based on mine positions (e.g in 1D, you can split a single group of undiscovered tiles into 2 by placing a mine in the middle. Extending to nD, each next dimension could provide a “backdoor”, allowing certain tiles to still be accessed by both groups and merging the groups again). Some mine positions warrant simple checks and handling, but there are too many exceptions possible. Calculations are also unlikely to be faster. The more general case would for example iterate all mines to check if some combination of neighbours could enclose a certain tile. Then, runtime calculation times are proportional to much more than (3^n-1)^2 per mine. Why? Because the full periphery of a tile two-apart from a mine must be checked in n dimensions to see whether it could be enclosed. Now, there are (3^n-1) of such two-tile apart neighbours on each mine, each of these tiles needs to check its neighbouring tiles again.
So, we either must content with runtime proportional to 4n or come up with a much better algorithm which magically solves everything but which relies on a method proportional to the number of mines.
Fairly interestingly, it seems that to obtain a “floodfill group” in n dimensions, those tiles must at least be “floodfill groups” within the 2D representation. An optimization could initially group all empty tiles based on runtime proportional to 4 calculations per tile (n=2) followed by more rigorous testing of each and every group other than the majority group. Such an approach could easily cut down on calculations, although it must be noted that 4 calculations for initial group guesses followed by 4n calculations results in more calculations when very few groups can be found in the game – e.g when there are very many mines. Although, such a case would also present a situation where this slower runtime would not be detrimental since there would only be very few empty spaces, without mines, anyways.
Tl:dr
MineSweeper uses computationally expensive n-dimensional floodfill methods. I haven’t found any methods which would allow similar neighbour-clearing behaviour as seen in the original game but at a reasonable number of calculations.
4D MineSweeper represents a maximum viable use of the (3^n-1) algorithm which evaluates each tile 80 times(!!).
The last few days I’ve gone through a list of Mark Manson, Nerd Fitness and Joshua Spodek articles. First, Mark told me to be more vulnerable. Thanks Mark. The advice didn’t mesh with me quite well. I do like the idea but have no clue how to implement it. Luckily, Mark has more articles. Another mentioned habits. Now, as a programmer and chemist, I do mesh with the idea of auto-pilot and I could recognize all the habit lingo easily in daily life.
So, I decided to come up with a good habit.
I iterated each of my current bad habits. I especially thought about what their cues were, of which most could easily be grouped under feeling stress or feeling bored. Some were associated with not having any activity to do – nail biting.
A habit I would gladly change is my habit to aimlessly browse the internet when bored. I sit at the computer for days on end. I get bored about every hour. Browsing the internet does get you somewhere in solving boredom, but not off the chair and outside, where the sun shines and wind blows. Not even immersion mods in Skyrim help.
So I decided to, from now on, take a 5 minute walk whenever I felt bored. The reward for surfing the internet I reckon was to “have something to do”. And I think I was right..
I’m very surprised with the results. Today, in 10 waking hours, I have spent a grand total of THREE hours walking. Normally, I walk three hours in a month, if I’m pushed by someone else. I had no idea I could force myself to walk this much without anyone to do it for, except for myself. But maybe more importantly, I had no idea I was this bored!
My legs are also quite tired and my feet hurt a bit. I am doubting it’s a good idea to keep the habit as-is, because this is a huge time investment. I am thinking to instead make the cue something like having had food (breakfast, dinner). I do already walk during lunch, when at university.
So my experiment in willpower seems to be going well, but needs some adjustment to prevent me from walking 24/7. Perhaps I should write a blog post whenever I start to feel bored. Perhaps I should work on my videogame whenever I feel bored. Those habits come next month.
Tomorrow, I’ll think about the habit some more. Otherwise, I’m amazed at the results for this single day.
When working on a mod for a while, you know exactly what goes wrong in a bug report. The stacktrace is your friend. The errors tell you where to look, what to do, what tests to perform. Easy-peasy. Bug fixing can be boring, but it can also be very wholesome and exciting as you improve others’ lives. A fixed bug is a rewarding experience – many will thank you, many tell you your mod is great and that you should keep it up! It’s quite like volunteering.
Some bug reports are awful. You can hear their authors scream at their monitor, flailing their arms on the keyboard. Ignoring bug report protocols. These are the kinds of bugs they want fixed ASAP, but if you fix them too quickly, you will get nothing worthwhile. It is the author’s divine right to scream and flail. Better put it in the issue tracker… and fix it in a year or so.
But some reports, some are the best. Those written by someone who 1) doesn’t understand the English language perfectly, or 2) explains the bug to you in the form of a novel. Today, we look at a category two bug report, the story of a pawn named Spud, holding an explosive sac:
This error happens 10 times when a pawn named "spud" trying to attack me with an explosive sac.
He has a knife in his inventory.
Every this error happens, a Molotov cocktail is thrown out from his hand. So 10 Molotov cocktails fly out from his hand in a line to my base.
Then a 10 jobs in 10 ticks error thrown.
Then again another 10 errors thrown.
Then another 10 jobs in 10 ticks error.
......
Finally this "spud" just "fires" infinite Molotov cocktails like firing a machine gun, and he never ends until anything in his sight has been destroyed.
temple_wing has taken it upon himself to describe the event in all its colours, including small details every step of the way, repeating sentences for increased infect. We had a good chuckle at this masterpiece annex dumpster fire of a report in the Discord.
[8:49 PM] 1: spud destroyer of worlds
[8:54 PM] 2: spud, destroyer of reality
[9:30 PM] 3: Beware the power of the explosive sac
[9:33 PM] 2: the sac that explodes with the power of ten thousand suns
[9:48 PM] 4: and he never ends until anything in his sight has been destroyed
Suffice it to say I haven’t bothered fixing the bug.
Today I woke up and decided to spend the day deriving an equation for bullet ricochet, as you do, and hit a few roadblocks on the way.
Solution
αcr = 2 * acot((sqrt(a^2 + 1) - 1) / a) - pi/4
βcr = 2 * atan((sqrt(a^2 + 1) - 1) / a)
a = 0.874 / b - b / 2.62
b = (sqrt(12 + 81 x^2) - 9 x)^(1/3)
x = (8 m v0^2) / (π Rt D^3)
The Story
I’ve recently tried solving the Wijk Ricochet Criterion (Koene, 2016) in WolframAlpha. The formula looks like below, and should be converted to the form y = f(x). Here, y [deg] is the maximum oblique angle of impact which allows a projectile to ricochet off of a surface, while x is a combination of constants which are easily found for any projectile and surface.
tan(y)^3 + tan(y) = x
Naturally, we want to calculate y in one step rather than by guessing its value. WolframAlpha tells me that this can also be rewritten into something simpler, like the following, but I still couldn’t tell you how to solve it:
x = sec(y)^2 * tan(y)
Let’s see what WolframAlpha makes of it. There’s got to be some solution, at least, right?
Thanks but no thanks. It seems that there’s a few instances of the imaginary I there, suggesting it’s being solved for complex numbers. Now, neither y nor x should be a complex number. Let’s get rid of that.
Solve[{Sec[y]^2 Tan[y] == x}, y, Reals]
Solve[{Sec[y]^2 Tan[y] == x}, y, PositiveReals]
Solve[{Sec[y]^2 Tan[y] == x}, y, NegativeReals]
Solve: This system cannot be solved with the methods available to Solve.
Solve: This system cannot be solved with the methods available to Solve.
Solve: This system cannot be solved with the methods available to Solve.
No dice.
Now, looking at the graph I can see some immediate issues. The function is periodic, meaning we’ll get tons of answers we don’t care about. Maybe we can limit the solutions to 0 >= y >= 90 and x >= 0.
Solve[{Sec[y]^2 Tan[y] == x,x>=0,pi/2>=y>=0}, y]
Solve: This system cannot be solved with the methods available to Solve.
Right. Let’s try it again with a different method:
What happened here? We set stronger boundaries, but now the solution is worse! What’s happening is that there’s this little piece of formula within the solution, and sometimes its sibling:
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 + x #1^6 &, 2]]
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 + x #1^6 &, 1]]
Here, the first- and second solution C of a sextic equation -x + 2 C + 3 x C^2 + 4 C^3 - 3 x C^4 + 2 C^5 + x C^6 == 0 are being calculated. Now, this calculation has a whole number of simplifications. While a quadratic equation can be solved quite easily, cubic equations take more work and have several simplifications, quartic equations are riddled with simplifications…
…and this trend continues to quintics and sextics… which also have a tendency to have no solutions at all. There’s a whole area of mathematics devoted to solving polynomials of degree p > 4, and I spent about two hours looking for anything I could a) understand and b) implement, with no results. So, since I don’t have five years to get a MSc in mathematics, we’ll return to Wolfram.
It seems that Wolfram Language decides to write out the simplifying cases right into the answer, making it unwieldy. In fact, because 0 >= y >= 90, these simplifications are possible. To get a more workable answer, we can instead set fewer limits:
Reduce[{Sec[y]^2 Tan[y] == x, y >= 0, x >= 0}, y, Reals]
(C[1] \[Element]
Integers && ((C[1] >= 0 &&
x == 0 && (y == \[Pi] + 2 \[Pi] C[1] ||
y == 2 \[Pi] C[1])) || (ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 1]] \[Element] Reals && C[1] >= 1 && x > 0 &&
y == 2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 1]] + 2 \[Pi] C[1]) || (ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 2]] \[Element] Reals && C[1] >= 1 && x > 0 &&
y == 2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 2]] + 2 \[Pi] C[1]))) || (ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 2]] \[Element] Reals && x != 0 && x > 0 &&
y == 2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 2]])
And even fewer:
Reduce[{Sec[y]^2 Tan[y] == x}, y, Reals]
(C[1] \[Element] Integers && x == 0 &&
y == \[Pi] + 2 \[Pi] C[1]) || (C[1] \[Element] Integers &&
x < 0 && (y ==
2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 1]] + 2 \[Pi] C[1] ||
y == 2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 2]] + 2 \[Pi] C[1])) || (C[1] \[Element]
Integers && x == 0 &&
y == 2 \[Pi] C[1]) || (C[1] \[Element] Integers &&
x > 0 && (y ==
2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 1]] + 2 \[Pi] C[1] ||
y == 2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 2]] + 2 \[Pi] C[1]))
However, if we have too few limits, the solution becomes longer again:
Reduce[{Sec[y]^2 Tan[y] == x}, y]
(C[1] \[Element] Integers &&
x == 0 && (y == 2 \[Pi] C[1] || y == \[Pi] + 2 \[Pi] C[1])) || (C[
1] \[Element] Integers &&
x != 0 && (y ==
2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 1]] + 2 \[Pi] C[1] ||
y == 2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 2]] + 2 \[Pi] C[1] ||
y == 2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 3]] + 2 \[Pi] C[1] ||
y == 2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 4]] + 2 \[Pi] C[1] ||
y == 2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 5]] + 2 \[Pi] C[1] ||
y == 2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 6]] + 2 \[Pi] C[1]))
The apparent solution
Let’s take our shortest solution to date:
(C[1] \[Element] Integers && x == 0 &&
y == \[Pi] + 2 \[Pi] C[1]) || (C[1] \[Element] Integers &&
x < 0 && (y ==
2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 1]] + 2 \[Pi] C[1] ||
y == 2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 2]] + 2 \[Pi] C[1])) || (C[1] \[Element]
Integers && x == 0 &&
y == 2 \[Pi] C[1]) || (C[1] \[Element] Integers &&
x > 0 && (y ==
2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 1]] + 2 \[Pi] C[1] ||
y == 2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 2]] + 2 \[Pi] C[1]))
You can notice that there’s a case x < 0. Now, x = (8 m v0^2) / (3 π Yt D^3) with all of these variables positive, real values. So, the case x < 0 is nonsense. Then, the solution for x == 0 is given twice, so let’s remove these two solutions:
(C[1] \[Element] Integers && x == 0 &&
y == 2 \[Pi] C[1]) || (C[1] \[Element] Integers &&
x > 0 && (y ==
2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 1]] + 2 \[Pi] C[1] ||
y == 2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 2]] + 2 \[Pi] C[1]))
Now, the value of C[1] is always an integer. Yet, for the value of y we expect 0 <= y <= π / 2, so C[1] seems to be present in the solution just to “normalize” it to stay within these bounds. A better option would be to use a modulus, which would also ensure 0 <= y <= π / 2. Let’s keep the value of this modulus open using a second constant C[2].
(y == Mod[
2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 1]], C[2] ||
y == Mod[
2 ArcTan[
Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 +
x #1^6 &, 2]], C[2]])
Now we’re getting somewhere. It’s strange that the solution is fine with either the first or the second root of the equation – this suggests that the roots are the same, or some multiple of 2 π. In either case, the Mod[] function should already solve this issue. Then, our solution consists of:
y = Mod[2 ArcTan[Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 + x #1^6 &, 2]], C[2]]
When plotting both equations, it is evident that C[2] was indeed there for normalization:
Plot[{2 ArcTan[Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 + x #1^6 &, 1]],2 ArcTan[Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 + x #1^6 &, 2]]},{x,0,10}]
In fact, the first root presents the bottom curve while the second root gives the top curve. Setting C[2] = π makes the two graphs overlap, but one could also just use the second root.
Plot[{Mod[2 ArcTan[Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 + x #1^6 &, 1]],Pi],2 ArcTan[Root[-x + 2 #1 + 3 x #1^2 + 4 #1^3 - 3 x #1^4 + 2 #1^5 + x #1^6 &, 2]]},{x,0,1}]
Now how the heck do we convert this to an analytical solution? Let’s write the equation down and see what we can find:
y = 2 ArcTan[Root[-x + 2 C + 3 x C^2 + 4 C^3 - 3 x C^4 + 2 C^5 + x C^6, 2]]
z = -x + 2 C + 3 x C^2 + 4 C^3 - 3 x C^4 + 2 C^5 + x C^6
z = x (C^6 - 3 C^4 + 3 C^2 - 1) + 2 C (C^4 + 2 C^2 + 1)
z = x (C + 1)^3 (C - 1)^3 + 2 C (C + i)^2 (C - i)^2
Obviously we’ll want to solve z(x, C). It can be rewritten a bunch of times, but it doesn’t help the function much, or at least I don’t see it helping. What I did is instead use Wolfram Language’s NRoots[] which numerically solves the equation. I’m not sure why I did it, but when solving for z == 0 /.{x -> 1}, the following happened:
NRoots[-x + 2 C + 3 x C^2 + 4 C^3 - 3 x C^4 + 2 C^5 + x C^6 == 0/.{x -> 1}, C]
C1 == -2.41421 || C2 == -0.566121 - 0.458821 I || C3 == -0.566121 + 0.458821 I
|| C4 == 0.414214 || C5 == 1.06612 - 0.864054 I || C6 == 1.06612 + 0.864054 I
Now.. I somehow noticed that adding the two C’s yields a difference of exactly 2. The imaginary C’s also add up to exactly 0.5. This can’t be coincidence. I noted this down, and looked for another value with this property. It seemed to happen again at x = 10. The next occurrence took longer, but it happened at x = 68. Then, 520. Something seemed familiar about the numbers, but I couldn’t quite tell.
Now, I was interested in C4, not in C1+C4, so I instead used Solve[] to get symbolic values of the different data points I had found, and some kind of pattern appeared:
x
C1+C4
Re(C2+C5)
C4
“a“
2
-2
0.5
Sqrt(2) – 1
1
10
-1
0.4
1/2 (Sqrt(5) – 1)
2
68
-0.5
..
1/4 (Sqrt(17) – 1)
4
520
-0.25
..
1/8 (Sqrt(65) – 1)
8
It seems that C4 ~= 1 / a * (sqrt(x / a) - 1), but I didn’t quite know what the relation between a and x was. Continuing the table by finding Solve[] values:
x
C1+C4
Re(C2+C5)
C4
“a”
4112
-0.125
..
1/16 (Sqrt(257) – 1)
16
It became obvious that the value in the square-root is one higher than a power of 2. In fact, it could be described as a^2 + 1. Now, this meant that the value of x could be estimated: if x / a = a^2 + 1, the phenomenon occurs whenever x = a^3 + a. So, the next iteration at a = 32 would be at x = 32768 + 32 = 32800, and yes, it was:
x
C1+C4
Re(C2+C5)
C4
“a”
32800
-0.0625
..
1/32 (Sqrt(1025) – 1)
32
This was enough to suppose that C4 = 1/a * (sqrt(a^2 + 1) - 1), x = a^3 + a. To get C4(x), we might simply be able to do Solve[x==a^3 + a, a], taking the only real solution:
{{a -> (2/3)^(1/3)/(-9 x + Sqrt[3] Sqrt[4 + 27 x^2])^(
1/3) - (-9 x + Sqrt[3] Sqrt[4 + 27 x^2])^(1/3)/(
2^(1/3) 3^(2/3))},
{a -> -((1 + I Sqrt[3])/(
2^(2/3) 3^(1/3) (-9 x + Sqrt[3] Sqrt[4 + 27 x^2])^(
1/3))) + ((1 - I Sqrt[3]) (-9 x + Sqrt[3] Sqrt[4 + 27 x^2])^(
1/3))/(2 2^(1/3) 3^(2/3))},
{a -> -((1 - I Sqrt[3])/(
2^(2/3) 3^(1/3) (-9 x + Sqrt[3] Sqrt[4 + 27 x^2])^(
1/3))) + ((1 + I Sqrt[3]) (-9 x + Sqrt[3] Sqrt[4 + 27 x^2])^(
1/3))/(2 2^(1/3) 3^(2/3))}}
Now this is a lot of square-roots and cubic roots, but it’s better than no solution at all. The formula for a could be added either to C4(a) or C4(a,x), although the solution for C4(a,x) doesn’t require squaring the formula. If we plot it:
c = 1/a*(Sqrt[x/a]-1) /.Solve[x==a^3+a,a][[1]];
Plot[{c,Root[-x + 2 #1 + 3 x #1^2 + 4 #1 ^3 - 3 x #1 ^4 + 2 #1^5 + x #1^6&,2]},{x,0,1000},PlotStyle->{Thick, Dashed}]
They overlap! So, the derivation seems to have worked!
Thus I present the formula for the Wijk’s Ricochet Criterion in the form βcr = f(x):
βcr = 2 * atan(a * (sqrt(x * a) - 1))
a = 1 / (0.874 / b - b / 2.62)
b = (sqrt(12 + 81 x^2) - 9 x)^(1/3)
x = (8 m v0^2) / (π Rt D^3)
With βcr [rad] the critical ricochet angle w.r.t the surface normal, m [kg] the weight of the projectile, v0 [m/s] the incident velocity, Rt [Pa] the target resistance which is often set to Rt = 3 Yt, with Yt [Pa] the yield strength of the target material, and finally D [m] the diameter of the bullet.
Naturally the equation can be rewritten to give you αcr [rad], the critical ricochet angle w.r.t the surface instead of w.r.t to the surface normal:
αcr = 2 * acot(a * (sqrt(x * a) - 1)) - pi/4
Stability of the equation
We’ve asked ourselves earlier whether the a^2 + 1 (red) or the x/a (green) route would provide less computationally advanced answers. However, when plotting the two of them in the form 1 - (Tan[βcr]^3 + Tan[βcr])/x, e.g filling in our solution βcr in the initial formula to see if it yields x, then dividing by x so it yields 1 and subtracting 1 from it, we should see the stability of the solution. The solution for a^2 + 1 (red) seems to have an error of about 1e-9, while x/a (green) has 2e-8 error for most values. This suggests that the a^2 + 1 route is better, either it takes less time so more decimals can be calculated or the error is inherently lower. Similar figures are seen when iterating from x = 0 -> 1 instead of x = 0 -> 1000.
This suggests that an improved version of the equation is the following:
αcr = 2 * acot(a * (sqrt(a^2 + 1) - 1)) - pi/4
βcr = 2 * atan(a * (sqrt(a^2 + 1) - 1))
a = 0.874 / b - b / 2.62
b = (sqrt(12 + 81 x^2) - 9 x)^(1/3)
x = (8 m v0^2) / (π Rt D^3)
Addendum
I realized that the initial answer (first code block) already contained an answer:
What’s a huge problem with this formula is the presence of x^8, x^6, x^4, which take more time to calculate than the answer I presented earlier. Furthermore, I haven’t exactly managed to tame the equation yet – it doesn’t seem to give the same results as the other answer.
Nothing quite says “I’m bored” like selecting every option in Microsoft Excel or Google Sheets just to see what they look like. However, some of the most beautiful graphs come from it. This post is an ode to graphs, to randomly pressing all the options and seeing what the computer blurts out.
Let us consider the data set I am currently working with. That sounds like I am some sort of data set analyst, but really I’m a simple student with a hobby in projectile physics and a propensity towards using Google Sheets for testing out new formulae. I’ve been simulating projectile penetration for a while (as can be seen in my previous post about the subject) and decided to see what the penetration depth is for different initial velocities at different time points. While I would’ve preferred the data plotted with time on the x-axis and velocities the different series, Google Sheets dislikes selecting rows and prefers it when you select columns for graph series.
Initially the graph was set to a scatter plot:
Quite a boring sight which also didn’t really tell me anything about the data. Interestingly, some of the penetration depths are below zero, which is an issue with the data set – I decided to hide data points below zero using conditional formatting, meaning the data points were technically still there for graphing purposes. This fact is better illustrated with the line plots (left). Interestingly, the smoothed line plot seems to show some squiggly lines which make it look like the graph is a sort of drill (right).
Then I stumbled upon a mosaic work of art. The 100% stacked stepped area chart. What a beauty.
While this mosaic may have some resemblance of data, the following mosaic looks even stranger, almost like a sedimentary layer map or something that belongs in an art museum. This is truly the peak of Google Sheets content.
Finally, the gauge chart with too many gauges – a 20×37 grid with two missing, so that’s 738 gauges. I don’t know why anyone would use this many gauges, but Google Sheets certainly allows for it: