{"article":{"slug":"solving-factorio-quality","title":"Solving Factorio Quality","subtitle":null,"summary":"Simon Sapin models Factorio Space Age quality upcycling with transition matrices and equilibrium equations, then builds an interactive TypeScript calculator for gambling, washing, upcycling, and asteroid reprocessing loops.","content_type":"tutorial","language":"en","canonical_url":"https://exyr.org/2026/solving-factorio-quality/","author":{"name":"Simon Sapin","url":"https://exyr.org/","person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"Exyr.org","url":"https://exyr.org/","listing_slug":null,"listing":null},"topics":[{"name":"Mathematics","slug":"mathematics","url":"https://listedarticles.com/topics/mathematics"},{"name":"Programming","slug":"programming","url":"https://listedarticles.com/topics/programming"},{"name":"Tutorials","slug":"tutorials","url":"https://listedarticles.com/topics/tutorials"},{"name":"Games","slug":"games","url":"https://listedarticles.com/topics/games"},{"name":"Engineering","slug":"engineering","url":"https://listedarticles.com/topics/engineering"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":3372,"reading_minutes":15,"published_at":"2026-02-15T00:00:00.000Z","added_at":"2026-09-30T09:12:45.895Z","updated_at":"2026-09-30T09:12:45.895Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/solving-factorio-quality","markdown_url":"https://listedarticles.com/articles/solving-factorio-quality.md","example":false,"citation":"Simon Sapin, Exyr.org. \"Solving Factorio Quality.\" 15 Feb 2026. https://exyr.org/2026/solving-factorio-quality/ (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://exyr.org/2026/solving-factorio-quality/"},"body_markdown":"## Solving Factorio Quality\n\nSimon Sapin,\n\nI play Factorio the normal way: by writing matrix math code to plan the factory.\n\nBut we’ll get to that.\nOr, *TL;DR*, go to my new [online calculator tool](https://factoqual.grebedoc.dev/).\n\n### Intro to Factorio and Quality\n\n[Factorio](https://factorio.com/) pretty much founded the factory video game genre:\nyou play a character who harvests resources and combines them to craft increasingly complex items,\nwhich in turn enable more sophisticated crafting.\nSo far this sounds a lot like Minecraft and many other survival games,\nbut what sets factory games apart is the focus on automation:\nsoon enough, most of the crafting is done not “by hand” by the character\nbut by increasingly many machines,\nwith various forms of logistics like conveyor belts to move items between machines\nor wherever they need to go.\nSome factory game go further and remove the player character altogether.\n\nAs “technologies” are unlocked in-game, Factorio offers many mechanisms to improve production. One of them is modules: crafting machines have a (limited) number of slots to accept different kinds of modules that affect their stats: speed modules make the machine run faster at the cost of more energy consumption, productivity modules increase yield from the same ingredients at the cost of speed and energy, etc.\n\nReleased in 2024, the Space Age extension adds new game mechanics including Quality:\nevery item and recipe now come in five quality tiers:\n⚀ normal, ⚁ uncommon, ⚂ rare, ⚃ epic, and ⚄ legendary.\nDepending on the item, each tier improves stats such as making crafting machines faster\nor making productivity modules more productive.\nHigh-quality items can be crafted directly from ingredients of the same quality,\nbut the only way to *increase* quality is through the new quality modules.\n\nModules affect the probability of any quality increase. For a given craft, each quality tier increase after the first is another 10% chance. We can build a table of the probabilities of output quality depending on input quality:\n\nFor example, the maximum possible quality chance in a machine with four module slots is 24.8%:\n\nSome players dislike the introduction of randomness\nto a game that was [mostly](https://wiki.factorio.com/Uranium_processing) deterministic, but\n[with enough repetitions probabilities become ratios](https://en.wikipedia.org/wiki/Law_of_large_numbers).\n\nThe probabilities are balanced so that even with multiple crafting steps (each a potential quality jumps), getting high-quality items unavoidably involves also crafting many unwanted low-quality ones.\n\nTo avoid the factory grinding to a halt when storage eventually gets full, Space Age also introduces the recycler: a new machine that destroys any item and (usually) returns 25% of its ingredients. This enables players to design “upcycling” contraptions that craft and recycle in a loop with quality modules until items reach the desired quality, at the cost of consuming many more ingredients:\n\n### Factory planning tools\n\nSome video games are partly “played” outside of the game itself.\n[Blue Prince](https://www.blueprincegame.com/) fully expects its players\nto keep extensive notes of everything they see,\nbut doesn’t provide an in-game notepad or similar tool.\nFactory games lend themselves\nto building [large spreadsheets](https://youtu.be/8PzhHwnX9ts?t=71) for resource accounting,\nbut a select few players decide that spreadsheets are not powerful enough for factory planning\nand spent countless hours programming [dedicated tools](https://factoriolab.github.io/)\nthat reproduce much of the game’s math to accurately model a production chain.\n\nThis is all optional in factory games, it’s perfectly viable to play it by ear and just build more when seeing something lacking.\n\nBut I do like to plan in advance: how many machines of each kind do I need? How much yield can I expect? Where are the bottlenecks? The looping nature of quality upcycling makes this particularly challenging either to guess, or to calculate with existing tools.\n\n### Matrix math\n\nLet’s imagine:\n\n- Some ingredients (for example iron plates) that come from an arbitrary production chain that may involve quality modules. Any given ingredient has a probability to be in each quality tier: ⚀ normal, ⚁ uncommon, ⚂ rare, ⚃ epic, and ⚄ legendary.\n- Enough assembling machines with each recipe tier to craft all ingredients into some product (for example pipes). These machines have quality modules so that the a quality chance is 10%.\n\nLet’s track the possible fates of one item:\n\nA product of a given tier can come from ingredients of the same tier or lower. The total probability for this outcome is the sum of (independent) probabilites of different ways to get it. In turn, those are the product of the percentage chance of a specific quality jump times the probability of having the corresponding ingredient tier in the first place:\n\n(The percent sign can be thought of as implicit division by 100, so that “percentage of” is the same as multiplication.)\n\nHere the percentage coefficients look [transposed](https://en.wikipedia.org/wiki/Transpose)\nacross the diagonal compared to the quality jump probability table from the wiki,\nbut that’s only because we’ve arranged product tiers vertically.\nInstead let’s group the probabilities of different tiers of the same item into row vectors:\n\nNow our [system of linear equations](https://en.wikipedia.org/wiki/System_of_linear_equations)\ncan be written as a single equation where a vector\nis [multiplied](https://en.wikipedia.org/wiki/Matrix_multiplication) by\na [transition matrix](https://en.wikipedia.org/wiki/State-transition_matrix)\nthat matches the wiki’s table:\n\nAs an edge case, zero quality chance means no tier transformation.\nThe corresponding transition matrix is\nthe [identity matrix](https://en.wikipedia.org/wiki/Identity_matrix):\n\nThis may not seem like much progress, but now a multi-step process can be computed through successive matrix multiplication. For example mining iron ore with 7.5% quality chance, then smelting it into iron plates with 5% quality chance, then crafting pipes with 10% chance. With no productivity bonus, we get these probabilities of end-products:\n\n### Quality strategies\n\nWhile it is possible to use quality modules as much as possible and deal with\n[Every tier Everywhere All at Once](https://youtu.be/l5NA-5e0LbI?t=310),\nhere we’ll focus on smaller self-contained systems.\n\n#### “Gambling”: opportunistic quality without recycling\n\nThe easiest but also least effective is to craft from normal-quality ingredients, with quality modules, and not recycle anything. This can be done before unlocking the recycler but is only viable for a small number of items, such as crafting a few hundred asteroid collectors to hope to get a dozen uncommon or rare ones for an early space ship.\n\nThis is improved when the factory has another use for normal-quality items. For example if placing thousands of normal-quality solar panels on the ground, crafting them with quality modules gives a better yield of higher-quality ones for space ships before the output buffers fill up.\n\nWith a single step and no loop, this is simplest to calculate: the expected product tier distribution is the first row of the transition matrix or of the probability table found on the wiki.\n\n#### “Washing”: pure recycling loop\n\nFor most items, the recycler reverses the main crafting recipe and returns 25% of the ingredients. But some items don’t have a crafting recipe (like ore) or it is considered irreversible (typically smelting and chemical processes). In that case the recycler produces either nothing or, 25% of the time, the same item. This process can improve quality if the recycler has quality modules. Repeating it in a loop, eventually all items will be either destroyed or improved until they reach any desired quality tier. Self-recycling items is arguably not the common case but let’s start here since the math is simpler.\n\nLet’s consider one item injected into the system, in this case from mining, and call the 5-component row vector of probabilities of each quality tier.\n\nAfter we transform that vector, the new probabilities may add up to less than one. The implicit remaining case is not having an item at all at a given place. For example, the recycler producing nothing 75% of the time can be represented by multiplying a vector of probabilities by .\n\nFiltering based on quality can also be represented with matrix multiplication. In the case of extracting rare or above:\n\nThe combined effect of one iteration through the loop is filtering to keep, then recycling with quality:\n\nNow let’s consider the possible ways an item can be extracted:\n\n- It originally had high enough quality to be extracted immediately: probability vector\n- It went through the recycling loop exactly once:\n- It went through recycling exactly twice:\n- etc.\n\nAny given item will eventually be extracted or destroyed, but there is no upper bound on how many loops that can take. The combination of all possible outcomes is an infinite sum:\n\nInfinite sums are rather inconvenient to calculate in finite time,\nalthough this one does converge to a finite value since the terms become exponentially smaller.\nWe could compute until the terms because small enough for an approximation,\nbut that would be unsatisfactory when an exact solution *is* possible.\n\n##### Long-term average throughput\n\nThe key insight is that\nit doesn’t matter how many times a given item has already been through the loop,\nonly what quality tier it has now.\nOn a short time scale this will vary because of the random effect of quality modules\nbut with enough repetitons\n[probabilities become ratios](https://en.wikipedia.org/wiki/Law_of_large_numbers).\n\nSo instead of probablities for a single item\nlet’s consider the **average rate of items over a long enough period of time**\ngoing through a given part of the system,\nagain as a 5-component vector for quality tiers.\nThese vectors can be multiplied by the same transition matrices as before.\nIn the case of a “washing” pure recycling loop, the relevant vectors are:\n\n- items injected into the system, with any quality distribution, in this example based on quality modules in miners\n- Items\n- All items going through the\n- Items based on their quality tier, in this example rare or above\n- Items with quality , those not extracted\n\nWe treat and as a fixed parameters and other vectors as unknowns we want to resolve.\n\nThe system converges to a dynamic equilibrium where the following equations hold for long-term averages:\n\nWe can rearrange and substitute:\n\nTranspose to get column vectors instead of row vectors and match the\n[classic convention](https://en.wikipedia.org/wiki/System_of_linear_equations#Matrix_equation):\n\nIntroduce some new names:\n\nNow we have equilibrium as a system of linear equations in the classic  form,\nthat we can solve for \nusing [Gaussian elimination](https://en.wikipedia.org/wiki/Gaussian_elimination).\nFrom  we can easily compute ,\nthen  (for the number of recyclers needed)\nand  (for the overall yield of the system).\n\nIf the example setup above was scaled to mine 100 ore per second, the parameters would be:\n\nAnd the solution with Gaussian Elimination:\n\nConverting from per second, the extracted rates are close to 90 per minute rare, 9 per minute epic, and 1 per minute legendary.\n\nFor another example:\n\n- Injecting only normal-quality fresh items, for now in some arbitrary unit:\n- Recycling with the best available quality modules:\n- Extracting only legendary-quality items\n\nSolving the equation gives:\n\nSo for items that recycle to themselves, “brute-force” washing consumes on average\n normal-quality inputs for every legendary output.\nThis matches the ratio that [others](#ack) have calculated.\nThe recycling capacity required is just shy of  of the fresh input rate.\n\n#### “Upcycling”: crafting + recycling loop\n\nFor items where recycling *does* return ingredients,\nwe can chain crafting machines with recyclers to form a loop:\n\nMost crafting recipes require multiple ingredients in various quantities, but here we’ll abstract over this and consider “the set of ingredients for one craft” as the base unit for our measurements. In this example, each machine crafting construction robots can consume 4 electronic circuits per second and 2 flying robot frames per second, but we’ll call that “2 ingredients per second”.\n\nSo we’re measuring long-term avarage rates of ingredients and products, each in five quality tiers, but they only appear in separate parts of the system so we’ll stick with 5-components vectors and don’t need to move to 10-dimensional math. (Wink wink foreshadowing)\n\nThe example setup has fresh normal-quality ingredients brought by robots into the blue requester chest,\nand extracts legendary products.\nIn the general case, we could imagine ingredients or products of any quality distribution\nbeing produced elsewhere and injected into the system.\nSimilarly, we could decide to extract products *or ingredients* (or both) of any quality tier.\n(Sometimes an ingredient may be more useful than a product to have in high quality,\nbut upcycling that specific product may have better yield than other methods.)\n\nSo the 5-component row vectors we’ll consider are, for ingredients:\n\n- injected into the system, with any quality distribution\n- Ingredients\n- based on their quality tier, in this example none\n- Ingredients new products from\n\nAnd for products:\n\n- injected into the system, with any quality distribution\n- Products\n- based on their quality tier, in this example lengendary\n- Products\n\nWe treat and as fixed parameters, and other vectors as unknown we want to resolve.\n\nAgain the transition matrices for filtering based on quality tier are complementary parts of the identity matrix. In this example:\n\nWe account for a crafting which is 0% in this example but could be greater with some crafting machines or with productivity modules. The quality chance may also be different for crafting v.s. recycling (for example when crafting with productivity modules, or based on the machine’s number of modules slots).\n\nFor short(er), let’s call:\n\nThe system converges to equilibrium where:\n\nWe can rearrange and substitute:\n\nLet’s introduce a couple more names:\n\nWe’ve again massaged our problem into the standard form with:\n\nSolving for gives us , and plugging that into the original equilibrium equations gives everything else.\n\nIn the example setup above, scaled for now to an arbitrary unit, the parameters are:\n\nAnd the solution with Gaussian Elimination:\n\nIn practice, the bottleneck of this system is the crafting speed of normal-quality products: 360 per minute. So let’s multiply everything by\n\nIn conclusion, this system produces on average almost 2 legendary construction robots per minute, and consumes about 309 sets of ingredients (309 frames + 618 circuits) per minute.\n\nFor a select few items in Space Age, the productivity bonus can be increased through repeatable research. The game enforces a hard cap of +300% bonus so that crafting then recycling returns at most the ingredients that we started with, never more.\n\nProductivity research levels get exponentially expensive so let’s assume that we use also productivity modules to reach the cap, meaning we don’t have quality modules in crafting machines. And this time, let’s inject fresh products instead of ingredients.\n\nOur model predicts:\n\nPerhaps surprisingly, the rates for intermediate quality tiers are identical.\n\nA maximal productivity bonus enables turning a normal quality product into a legendary one without any ressource loss, at the cost of many machines and modules to get significant throughput.\n\n#### [“Space casino”](https://youtu.be/jHrY1RtWHPw?t=1078): asteroid reprocessing loop\n\nIn Space Age, each space platform is a mini-factory. Instead of mining ore from the ground they collect asteroid chunks and crush them to get resources. Those can be used for a space ship’s own fuel and ammunition, or to feed a production chain in space whose end-product is sent to a planet’s ground.\n\nThree different types of chunks (metallic, carbonic, and oxide) yield different ressources and are more or less frequent in different regions of the solar system. Asteroid chunks can be “reprocessed” for a chance to get a different type (or nothing).\n\nAssuming enough crushers with each recipe, we can represent this process mathematically as multiplying a row vector by a square 3×3 transition matrix:\n\nReprocessing also accepts quality modules so it can be used in a loop much like “washing”. Crushing legendary chunks yields legendary versions of some base resources that can be used for crafting other legendary items.\n\nThis time we’ll have conveyor belts transporting asteroid chunks of any of three types,\neach in any of five quality tiers, for a total of 15 possible items.\nWe’ll represent this with 15-component row vectors and 15×15 transition matrices.\nWe build up the latter as [block matrices](https://en.wikipedia.org/wiki/Block_matrix)\nmade of nine 5×5 blocks:\n\nAside from the higher dimension, the math is the same as for washing and we end up with a system of linear equations to solve for , with:\n\nAsteroid crushers have two module slots, so the best possible quality chance for reprocessing is . Asteroid collectors don’t have any module slot, so freshly-collected chunks are always normal-quality. We build the 15-component vector from its 3 components for normal-quality rate of each asteroid type (metallic, carbonic, oxide). For example in Nauvis orbit:\n\nLet’s say that we only extract legendary oxide chunks and reprocess everything else. Solving the equation gives two complementary 15-components row vectors and that we can rearrange into 3×5 tables\n\nExample: in Nauvis orbit, extract legendary oxide chunks and reprocess everything else:\n\n| To reprocess |  |  |  |  |  | \n|---|---|---|---|---|---|\n|  | ⚀ | ⚁ | ⚂ | ⚃ | ⚄ | \n| Metallic | 1.31616 | 0.33791 | 0.13314 | 0.05286 | 0.0175 | \n| Carbonic | 1.11409 | 0.33244 | 0.13245 | 0.05277 | 0.01748 | \n| Oxide | 0.91202 | 0.32697 | 0.13175 | 0.05268 | 0 | \n| Extracted |  |  |  |  |  | \n|  | ⚀ | ⚁ | ⚂ | ⚃ | ⚄ | \n| Metallic | 0 | 0 | 0 | 0 | 0 | \n| Carbonic | 0 | 0 | 0 | 0 | 0 | \n| Oxide | 0 | 0 | 0 | 0 | 0.01398 | \n\nWe observe:\n\n- As the quality tier increases, the distribution of asteroid types quickly converges to an even each. The distribution of normal-quality fresh input has negligible impact on that of legendary-quality chunks going through the system.\n- When extracting a single type of legendary asteroids, the legendary yield is about of the total fresh input. This is much better than about for “washing” a.k.a. pure recycling, thanks to reprocessing destroying its input only 20% of the time v.s. 75% for recycling.\n\n#### Other strategies\n\nThere are other ways to get quality items that don’t neatly fit in the categories above\n(honorable mention to [“The LDS Shuffle”](https://www.youtube.com/watch?v=MO519YUYn0k))\nand I’m sure folks will come up with more.\nBut if a loop makes it tricky to calculate their behavior we can use the same ideas:\n\n- Represent throughput of multiple kinds of items as vectors\n- Represent linear transformations (crafting, recycling, …) as matrix multiplication\n- Represent dynamic equilibrium as a matrix equation\n- Solve the equation, using Gaussian Elimination if needed\n\n### Making an interactive calculator tool\n\nDoing matrix math by hand is obviously tedious and error-prone, let’s have computers do it for us.\n\nI started with Rust out of habit.\n[`nalgebra`](https://nalgebra.rs/) works out great for the matrix math we do here.\nIn includes multiple solvers for  systems of linear equations,\nbut those pretty much require the scalar type for matrix and vector components\nto be a floating point number `f32` or `f64`, whereas the base library is more generic.\n\nIn practice floating point would be perfectly adequate, but it is a fixed-precision approximation so every step of computation potentially introduces some error. Wouldn’t it be nice to get an exact result, just because we can? The matrix sizes and number of operations are fixed and relatively small, so we don’t need to optimize the code for speed.\n\nEvery operation we use is ultimately addition, substraction, multiplication, or division.\nSo if all of our parameters are [rational](https://en.wikipedia.org/wiki/Rational_number)\nthe result will be too.\n[`num_rational`](https://crates.io/crates/num_rational) represents rationals extactly\nas a pair of (generic) integers.\nI could reach for some infinite-precision `BigInt` library if needed,\nbut the built-in `i128` with\n[overflow checks](https://doc.rust-lang.org/cargo/reference/profiles.html#overflow-checks)\nturns out to be sufficient for 15×15 martices.\n(`i64` is sufficient for 5×5.)\n\nAt this point I had a functional Rust library doing all of the math above,\nbut editing source code to tweak parameters isn’t a nice user experience.\nI’d much prefer something like [Factoriolab](https://factoriolab.github.io/).\nAnd to be easy to use by other people it really should be on the web.\n\nMy Rust code can be compiled to WebAssembly but then I would need to either:\n\n- Build the entire GUI with Rust + wasm as well.\nIt’s possible but the tooling\n[isn’t great yet](https://fasterthanli.me/articles/does-dioxus-spark-joy)\n- Build the GUI in JavaScript or TypeScript (taking advantage of mature tools) and bridge into wasm for the math. But because of the many parameters the API surface is significant, and doing that much bridging isn’t fun\n\nSo I ended up rewriting the whole thing in TypeScript.\n[`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)\nis built-in,\nFactoriolab already has a good open-source\n[`rational` library](https://github.com/factoriolab/factoriolab/blob/e06e27bef0ab1f317ab9e19f9c5520cf7ff7dc4d/src/rational/rational.ts),\nand making a generic matrix library isn’t too hard.\n\nAs to building an interactive GUI in the browser, last time I did much of it jQuery was the hot new thing. I didn’t feel like learning React so I settled on:\n\n- [VanJS](https://vanjs.org/) for minimal reactive goodness\n- [Vite](https://vite.dev/) for TypeScript wrangling and a reload-on-save dev server\n- [Grebedoc](https://grebedoc.dev/) for static file hosting\n\nAll together, [factoqual.grebedoc.dev](https://factoqual.grebedoc.dev/) now provides\nan interactive GUI for planning Quality upcyclers.\nIts source code is published at\n[codeberg.org/SimonSapin/factoqual](https://codeberg.org/SimonSapin/factoqual).\n\n### \n\nI was heavily inspired by Daniel Monteiro’s\n[blog posts](https://dfamonteiro.com/tags/factorio-quality/) on quality math.\nOthers have done similar work,\nincluding [Konage](https://forums.factorio.com/viewtopic.php?t=124772)\nand [Factorio wiki contributors](https://wiki.factorio.com/Tutorial:Quality_upcycling_math).\nBut I believe the exact solution from solving equilibrium equation as shown here is new,\nas opposed to iterative approximation an infinite sum.","body_html":"<h2 id=\"solving-factorio-quality\">Solving Factorio Quality</h2>\n<p>Simon Sapin,</p>\n<p>I play Factorio the normal way: by writing matrix math code to plan the factory.</p>\n<p>But we’ll get to that.\nOr, <em>TL;DR</em>, go to my new <a href=\"https://factoqual.grebedoc.dev/\" rel=\"nofollow ugc noopener\">online calculator tool</a>.</p>\n<h3 id=\"intro-to-factorio-and-quality\">Intro to Factorio and Quality</h3>\n<p><a href=\"https://factorio.com/\" rel=\"nofollow ugc noopener\">Factorio</a> pretty much founded the factory video game genre:\nyou play a character who harvests resources and combines them to craft increasingly complex items,\nwhich in turn enable more sophisticated crafting.\nSo far this sounds a lot like Minecraft and many other survival games,\nbut what sets factory games apart is the focus on automation:\nsoon enough, most of the crafting is done not “by hand” by the character\nbut by increasingly many machines,\nwith various forms of logistics like conveyor belts to move items between machines\nor wherever they need to go.\nSome factory game go further and remove the player character altogether.</p>\n<p>As “technologies” are unlocked in-game, Factorio offers many mechanisms to improve production. One of them is modules: crafting machines have a (limited) number of slots to accept different kinds of modules that affect their stats: speed modules make the machine run faster at the cost of more energy consumption, productivity modules increase yield from the same ingredients at the cost of speed and energy, etc.</p>\n<p>Released in 2024, the Space Age extension adds new game mechanics including Quality:\nevery item and recipe now come in five quality tiers:\n⚀ normal, ⚁ uncommon, ⚂ rare, ⚃ epic, and ⚄ legendary.\nDepending on the item, each tier improves stats such as making crafting machines faster\nor making productivity modules more productive.\nHigh-quality items can be crafted directly from ingredients of the same quality,\nbut the only way to <em>increase</em> quality is through the new quality modules.</p>\n<p>Modules affect the probability of any quality increase. For a given craft, each quality tier increase after the first is another 10% chance. We can build a table of the probabilities of output quality depending on input quality:</p>\n<p>For example, the maximum possible quality chance in a machine with four module slots is 24.8%:</p>\n<p>Some players dislike the introduction of randomness\nto a game that was <a href=\"https://wiki.factorio.com/Uranium_processing\" rel=\"nofollow ugc noopener\">mostly</a> deterministic, but\n<a href=\"https://en.wikipedia.org/wiki/Law_of_large_numbers\" rel=\"nofollow ugc noopener\">with enough repetitions probabilities become ratios</a>.</p>\n<p>The probabilities are balanced so that even with multiple crafting steps (each a potential quality jumps), getting high-quality items unavoidably involves also crafting many unwanted low-quality ones.</p>\n<p>To avoid the factory grinding to a halt when storage eventually gets full, Space Age also introduces the recycler: a new machine that destroys any item and (usually) returns 25% of its ingredients. This enables players to design “upcycling” contraptions that craft and recycle in a loop with quality modules until items reach the desired quality, at the cost of consuming many more ingredients:</p>\n<h3 id=\"factory-planning-tools\">Factory planning tools</h3>\n<p>Some video games are partly “played” outside of the game itself.\n<a href=\"https://www.blueprincegame.com/\" rel=\"nofollow ugc noopener\">Blue Prince</a> fully expects its players\nto keep extensive notes of everything they see,\nbut doesn’t provide an in-game notepad or similar tool.\nFactory games lend themselves\nto building <a href=\"https://youtu.be/8PzhHwnX9ts?t=71\" rel=\"nofollow ugc noopener\">large spreadsheets</a> for resource accounting,\nbut a select few players decide that spreadsheets are not powerful enough for factory planning\nand spent countless hours programming <a href=\"https://factoriolab.github.io/\" rel=\"nofollow ugc noopener\">dedicated tools</a>\nthat reproduce much of the game’s math to accurately model a production chain.</p>\n<p>This is all optional in factory games, it’s perfectly viable to play it by ear and just build more when seeing something lacking.</p>\n<p>But I do like to plan in advance: how many machines of each kind do I need? How much yield can I expect? Where are the bottlenecks? The looping nature of quality upcycling makes this particularly challenging either to guess, or to calculate with existing tools.</p>\n<h3 id=\"matrix-math\">Matrix math</h3>\n<p>Let’s imagine:</p>\n<ul><li>Some ingredients (for example iron plates) that come from an arbitrary production chain that may involve quality modules. Any given ingredient has a probability to be in each quality tier: ⚀ normal, ⚁ uncommon, ⚂ rare, ⚃ epic, and ⚄ legendary.</li><li>Enough assembling machines with each recipe tier to craft all ingredients into some product (for example pipes). These machines have quality modules so that the a quality chance is 10%.</li></ul>\n<p>Let’s track the possible fates of one item:</p>\n<p>A product of a given tier can come from ingredients of the same tier or lower. The total probability for this outcome is the sum of (independent) probabilites of different ways to get it. In turn, those are the product of the percentage chance of a specific quality jump times the probability of having the corresponding ingredient tier in the first place:</p>\n<p>(The percent sign can be thought of as implicit division by 100, so that “percentage of” is the same as multiplication.)</p>\n<p>Here the percentage coefficients look <a href=\"https://en.wikipedia.org/wiki/Transpose\" rel=\"nofollow ugc noopener\">transposed</a>\nacross the diagonal compared to the quality jump probability table from the wiki,\nbut that’s only because we’ve arranged product tiers vertically.\nInstead let’s group the probabilities of different tiers of the same item into row vectors:</p>\n<p>Now our <a href=\"https://en.wikipedia.org/wiki/System_of_linear_equations\" rel=\"nofollow ugc noopener\">system of linear equations</a>\ncan be written as a single equation where a vector\nis <a href=\"https://en.wikipedia.org/wiki/Matrix_multiplication\" rel=\"nofollow ugc noopener\">multiplied</a> by\na <a href=\"https://en.wikipedia.org/wiki/State-transition_matrix\" rel=\"nofollow ugc noopener\">transition matrix</a>\nthat matches the wiki’s table:</p>\n<p>As an edge case, zero quality chance means no tier transformation.\nThe corresponding transition matrix is\nthe <a href=\"https://en.wikipedia.org/wiki/Identity_matrix\" rel=\"nofollow ugc noopener\">identity matrix</a>:</p>\n<p>This may not seem like much progress, but now a multi-step process can be computed through successive matrix multiplication. For example mining iron ore with 7.5% quality chance, then smelting it into iron plates with 5% quality chance, then crafting pipes with 10% chance. With no productivity bonus, we get these probabilities of end-products:</p>\n<h3 id=\"quality-strategies\">Quality strategies</h3>\n<p>While it is possible to use quality modules as much as possible and deal with\n<a href=\"https://youtu.be/l5NA-5e0LbI?t=310\" rel=\"nofollow ugc noopener\">Every tier Everywhere All at Once</a>,\nhere we’ll focus on smaller self-contained systems.</p>\n<h4 id=\"gambling-opportunistic-quality-without-recycling\">“Gambling”: opportunistic quality without recycling</h4>\n<p>The easiest but also least effective is to craft from normal-quality ingredients, with quality modules, and not recycle anything. This can be done before unlocking the recycler but is only viable for a small number of items, such as crafting a few hundred asteroid collectors to hope to get a dozen uncommon or rare ones for an early space ship.</p>\n<p>This is improved when the factory has another use for normal-quality items. For example if placing thousands of normal-quality solar panels on the ground, crafting them with quality modules gives a better yield of higher-quality ones for space ships before the output buffers fill up.</p>\n<p>With a single step and no loop, this is simplest to calculate: the expected product tier distribution is the first row of the transition matrix or of the probability table found on the wiki.</p>\n<h4 id=\"washing-pure-recycling-loop\">“Washing”: pure recycling loop</h4>\n<p>For most items, the recycler reverses the main crafting recipe and returns 25% of the ingredients. But some items don’t have a crafting recipe (like ore) or it is considered irreversible (typically smelting and chemical processes). In that case the recycler produces either nothing or, 25% of the time, the same item. This process can improve quality if the recycler has quality modules. Repeating it in a loop, eventually all items will be either destroyed or improved until they reach any desired quality tier. Self-recycling items is arguably not the common case but let’s start here since the math is simpler.</p>\n<p>Let’s consider one item injected into the system, in this case from mining, and call the 5-component row vector of probabilities of each quality tier.</p>\n<p>After we transform that vector, the new probabilities may add up to less than one. The implicit remaining case is not having an item at all at a given place. For example, the recycler producing nothing 75% of the time can be represented by multiplying a vector of probabilities by .</p>\n<p>Filtering based on quality can also be represented with matrix multiplication. In the case of extracting rare or above:</p>\n<p>The combined effect of one iteration through the loop is filtering to keep, then recycling with quality:</p>\n<p>Now let’s consider the possible ways an item can be extracted:</p>\n<ul><li>It originally had high enough quality to be extracted immediately: probability vector</li><li>It went through the recycling loop exactly once:</li><li>It went through recycling exactly twice:</li><li>etc.</li></ul>\n<p>Any given item will eventually be extracted or destroyed, but there is no upper bound on how many loops that can take. The combination of all possible outcomes is an infinite sum:</p>\n<p>Infinite sums are rather inconvenient to calculate in finite time,\nalthough this one does converge to a finite value since the terms become exponentially smaller.\nWe could compute until the terms because small enough for an approximation,\nbut that would be unsatisfactory when an exact solution <em>is</em> possible.</p>\n<h5 id=\"long-term-average-throughput\">Long-term average throughput</h5>\n<p>The key insight is that\nit doesn’t matter how many times a given item has already been through the loop,\nonly what quality tier it has now.\nOn a short time scale this will vary because of the random effect of quality modules\nbut with enough repetitons\n<a href=\"https://en.wikipedia.org/wiki/Law_of_large_numbers\" rel=\"nofollow ugc noopener\">probabilities become ratios</a>.</p>\n<p>So instead of probablities for a single item\nlet’s consider the <strong>average rate of items over a long enough period of time</strong>\ngoing through a given part of the system,\nagain as a 5-component vector for quality tiers.\nThese vectors can be multiplied by the same transition matrices as before.\nIn the case of a “washing” pure recycling loop, the relevant vectors are:</p>\n<ul><li>items injected into the system, with any quality distribution, in this example based on quality modules in miners</li><li>Items</li><li>All items going through the</li><li>Items based on their quality tier, in this example rare or above</li><li>Items with quality , those not extracted</li></ul>\n<p>We treat and as a fixed parameters and other vectors as unknowns we want to resolve.</p>\n<p>The system converges to a dynamic equilibrium where the following equations hold for long-term averages:</p>\n<p>We can rearrange and substitute:</p>\n<p>Transpose to get column vectors instead of row vectors and match the\n<a href=\"https://en.wikipedia.org/wiki/System_of_linear_equations#Matrix_equation\" rel=\"nofollow ugc noopener\">classic convention</a>:</p>\n<p>Introduce some new names:</p>\n<p>Now we have equilibrium as a system of linear equations in the classic  form,\nthat we can solve for \nusing <a href=\"https://en.wikipedia.org/wiki/Gaussian_elimination\" rel=\"nofollow ugc noopener\">Gaussian elimination</a>.\nFrom  we can easily compute ,\nthen  (for the number of recyclers needed)\nand  (for the overall yield of the system).</p>\n<p>If the example setup above was scaled to mine 100 ore per second, the parameters would be:</p>\n<p>And the solution with Gaussian Elimination:</p>\n<p>Converting from per second, the extracted rates are close to 90 per minute rare, 9 per minute epic, and 1 per minute legendary.</p>\n<p>For another example:</p>\n<ul><li>Injecting only normal-quality fresh items, for now in some arbitrary unit:</li><li>Recycling with the best available quality modules:</li><li>Extracting only legendary-quality items</li></ul>\n<p>Solving the equation gives:</p>\n<p>So for items that recycle to themselves, “brute-force” washing consumes on average\n normal-quality inputs for every legendary output.\nThis matches the ratio that <a href=\"#ack\">others</a> have calculated.\nThe recycling capacity required is just shy of  of the fresh input rate.</p>\n<h4 id=\"upcycling-crafting-recycling-loop\">“Upcycling”: crafting + recycling loop</h4>\n<p>For items where recycling <em>does</em> return ingredients,\nwe can chain crafting machines with recyclers to form a loop:</p>\n<p>Most crafting recipes require multiple ingredients in various quantities, but here we’ll abstract over this and consider “the set of ingredients for one craft” as the base unit for our measurements. In this example, each machine crafting construction robots can consume 4 electronic circuits per second and 2 flying robot frames per second, but we’ll call that “2 ingredients per second”.</p>\n<p>So we’re measuring long-term avarage rates of ingredients and products, each in five quality tiers, but they only appear in separate parts of the system so we’ll stick with 5-components vectors and don’t need to move to 10-dimensional math. (Wink wink foreshadowing)</p>\n<p>The example setup has fresh normal-quality ingredients brought by robots into the blue requester chest,\nand extracts legendary products.\nIn the general case, we could imagine ingredients or products of any quality distribution\nbeing produced elsewhere and injected into the system.\nSimilarly, we could decide to extract products <em>or ingredients</em> (or both) of any quality tier.\n(Sometimes an ingredient may be more useful than a product to have in high quality,\nbut upcycling that specific product may have better yield than other methods.)</p>\n<p>So the 5-component row vectors we’ll consider are, for ingredients:</p>\n<ul><li>injected into the system, with any quality distribution</li><li>Ingredients</li><li>based on their quality tier, in this example none</li><li>Ingredients new products from</li></ul>\n<p>And for products:</p>\n<ul><li>injected into the system, with any quality distribution</li><li>Products</li><li>based on their quality tier, in this example lengendary</li><li>Products</li></ul>\n<p>We treat and as fixed parameters, and other vectors as unknown we want to resolve.</p>\n<p>Again the transition matrices for filtering based on quality tier are complementary parts of the identity matrix. In this example:</p>\n<p>We account for a crafting which is 0% in this example but could be greater with some crafting machines or with productivity modules. The quality chance may also be different for crafting v.s. recycling (for example when crafting with productivity modules, or based on the machine’s number of modules slots).</p>\n<p>For short(er), let’s call:</p>\n<p>The system converges to equilibrium where:</p>\n<p>We can rearrange and substitute:</p>\n<p>Let’s introduce a couple more names:</p>\n<p>We’ve again massaged our problem into the standard form with:</p>\n<p>Solving for gives us , and plugging that into the original equilibrium equations gives everything else.</p>\n<p>In the example setup above, scaled for now to an arbitrary unit, the parameters are:</p>\n<p>And the solution with Gaussian Elimination:</p>\n<p>In practice, the bottleneck of this system is the crafting speed of normal-quality products: 360 per minute. So let’s multiply everything by</p>\n<p>In conclusion, this system produces on average almost 2 legendary construction robots per minute, and consumes about 309 sets of ingredients (309 frames + 618 circuits) per minute.</p>\n<p>For a select few items in Space Age, the productivity bonus can be increased through repeatable research. The game enforces a hard cap of +300% bonus so that crafting then recycling returns at most the ingredients that we started with, never more.</p>\n<p>Productivity research levels get exponentially expensive so let’s assume that we use also productivity modules to reach the cap, meaning we don’t have quality modules in crafting machines. And this time, let’s inject fresh products instead of ingredients.</p>\n<p>Our model predicts:</p>\n<p>Perhaps surprisingly, the rates for intermediate quality tiers are identical.</p>\n<p>A maximal productivity bonus enables turning a normal quality product into a legendary one without any ressource loss, at the cost of many machines and modules to get significant throughput.</p>\n<h4 id=\"space-casino-asteroid-reprocessing-loop\"><a href=\"https://youtu.be/jHrY1RtWHPw?t=1078\" rel=\"nofollow ugc noopener\">“Space casino”</a>: asteroid reprocessing loop</h4>\n<p>In Space Age, each space platform is a mini-factory. Instead of mining ore from the ground they collect asteroid chunks and crush them to get resources. Those can be used for a space ship’s own fuel and ammunition, or to feed a production chain in space whose end-product is sent to a planet’s ground.</p>\n<p>Three different types of chunks (metallic, carbonic, and oxide) yield different ressources and are more or less frequent in different regions of the solar system. Asteroid chunks can be “reprocessed” for a chance to get a different type (or nothing).</p>\n<p>Assuming enough crushers with each recipe, we can represent this process mathematically as multiplying a row vector by a square 3×3 transition matrix:</p>\n<p>Reprocessing also accepts quality modules so it can be used in a loop much like “washing”. Crushing legendary chunks yields legendary versions of some base resources that can be used for crafting other legendary items.</p>\n<p>This time we’ll have conveyor belts transporting asteroid chunks of any of three types,\neach in any of five quality tiers, for a total of 15 possible items.\nWe’ll represent this with 15-component row vectors and 15×15 transition matrices.\nWe build up the latter as <a href=\"https://en.wikipedia.org/wiki/Block_matrix\" rel=\"nofollow ugc noopener\">block matrices</a>\nmade of nine 5×5 blocks:</p>\n<p>Aside from the higher dimension, the math is the same as for washing and we end up with a system of linear equations to solve for , with:</p>\n<p>Asteroid crushers have two module slots, so the best possible quality chance for reprocessing is . Asteroid collectors don’t have any module slot, so freshly-collected chunks are always normal-quality. We build the 15-component vector from its 3 components for normal-quality rate of each asteroid type (metallic, carbonic, oxide). For example in Nauvis orbit:</p>\n<p>Let’s say that we only extract legendary oxide chunks and reprocess everything else. Solving the equation gives two complementary 15-components row vectors and that we can rearrange into 3×5 tables</p>\n<p>Example: in Nauvis orbit, extract legendary oxide chunks and reprocess everything else:</p>\n<div class=\"table-wrap\"><table><thead><tr><th>To reprocess</th><th></th><th></th><th></th><th></th><th></th></tr></thead><tbody><tr><td></td><td>⚀</td><td>⚁</td><td>⚂</td><td>⚃</td><td>⚄</td></tr><tr><td>Metallic</td><td>1.31616</td><td>0.33791</td><td>0.13314</td><td>0.05286</td><td>0.0175</td></tr><tr><td>Carbonic</td><td>1.11409</td><td>0.33244</td><td>0.13245</td><td>0.05277</td><td>0.01748</td></tr><tr><td>Oxide</td><td>0.91202</td><td>0.32697</td><td>0.13175</td><td>0.05268</td><td>0</td></tr><tr><td>Extracted</td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td>⚀</td><td>⚁</td><td>⚂</td><td>⚃</td><td>⚄</td></tr><tr><td>Metallic</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr><tr><td>Carbonic</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr><tr><td>Oxide</td><td>0</td><td>0</td><td>0</td><td>0</td><td>0.01398</td></tr></tbody></table></div>\n<p>We observe:</p>\n<ul><li>As the quality tier increases, the distribution of asteroid types quickly converges to an even each. The distribution of normal-quality fresh input has negligible impact on that of legendary-quality chunks going through the system.</li><li>When extracting a single type of legendary asteroids, the legendary yield is about of the total fresh input. This is much better than about for “washing” a.k.a. pure recycling, thanks to reprocessing destroying its input only 20% of the time v.s. 75% for recycling.</li></ul>\n<h4 id=\"other-strategies\">Other strategies</h4>\n<p>There are other ways to get quality items that don’t neatly fit in the categories above\n(honorable mention to <a href=\"https://www.youtube.com/watch?v=MO519YUYn0k\" rel=\"nofollow ugc noopener\">“The LDS Shuffle”</a>)\nand I’m sure folks will come up with more.\nBut if a loop makes it tricky to calculate their behavior we can use the same ideas:</p>\n<ul><li>Represent throughput of multiple kinds of items as vectors</li><li>Represent linear transformations (crafting, recycling, …) as matrix multiplication</li><li>Represent dynamic equilibrium as a matrix equation</li><li>Solve the equation, using Gaussian Elimination if needed</li></ul>\n<h3 id=\"making-an-interactive-calculator-tool\">Making an interactive calculator tool</h3>\n<p>Doing matrix math by hand is obviously tedious and error-prone, let’s have computers do it for us.</p>\n<p>I started with Rust out of habit.\n<a href=\"https://nalgebra.rs/\" rel=\"nofollow ugc noopener\"><code>nalgebra</code></a> works out great for the matrix math we do here.\nIn includes multiple solvers for  systems of linear equations,\nbut those pretty much require the scalar type for matrix and vector components\nto be a floating point number <code>f32</code> or <code>f64</code>, whereas the base library is more generic.</p>\n<p>In practice floating point would be perfectly adequate, but it is a fixed-precision approximation so every step of computation potentially introduces some error. Wouldn’t it be nice to get an exact result, just because we can? The matrix sizes and number of operations are fixed and relatively small, so we don’t need to optimize the code for speed.</p>\n<p>Every operation we use is ultimately addition, substraction, multiplication, or division.\nSo if all of our parameters are <a href=\"https://en.wikipedia.org/wiki/Rational_number\" rel=\"nofollow ugc noopener\">rational</a>\nthe result will be too.\n<a href=\"https://crates.io/crates/num_rational\" rel=\"nofollow ugc noopener\"><code>num_rational</code></a> represents rationals extactly\nas a pair of (generic) integers.\nI could reach for some infinite-precision <code>BigInt</code> library if needed,\nbut the built-in <code>i128</code> with\n<a href=\"https://doc.rust-lang.org/cargo/reference/profiles.html#overflow-checks\" rel=\"nofollow ugc noopener\">overflow checks</a>\nturns out to be sufficient for 15×15 martices.\n(<code>i64</code> is sufficient for 5×5.)</p>\n<p>At this point I had a functional Rust library doing all of the math above,\nbut editing source code to tweak parameters isn’t a nice user experience.\nI’d much prefer something like <a href=\"https://factoriolab.github.io/\" rel=\"nofollow ugc noopener\">Factoriolab</a>.\nAnd to be easy to use by other people it really should be on the web.</p>\n<p>My Rust code can be compiled to WebAssembly but then I would need to either:</p>\n<ul><li><p>Build the entire GUI with Rust + wasm as well.</p><p>It’s possible but the tooling\n<a href=\"https://fasterthanli.me/articles/does-dioxus-spark-joy\" rel=\"nofollow ugc noopener\">isn’t great yet</a></p></li><li>Build the GUI in JavaScript or TypeScript (taking advantage of mature tools) and bridge into wasm for the math. But because of the many parameters the API surface is significant, and doing that much bridging isn’t fun</li></ul>\n<p>So I ended up rewriting the whole thing in TypeScript.\n<a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt\" rel=\"nofollow ugc noopener\"><code>BigInt</code></a>\nis built-in,\nFactoriolab already has a good open-source\n<a href=\"https://github.com/factoriolab/factoriolab/blob/e06e27bef0ab1f317ab9e19f9c5520cf7ff7dc4d/src/rational/rational.ts\" rel=\"nofollow ugc noopener\"><code>rational</code> library</a>,\nand making a generic matrix library isn’t too hard.</p>\n<p>As to building an interactive GUI in the browser, last time I did much of it jQuery was the hot new thing. I didn’t feel like learning React so I settled on:</p>\n<ul><li><a href=\"https://vanjs.org/\" rel=\"nofollow ugc noopener\">VanJS</a> for minimal reactive goodness</li><li><a href=\"https://vite.dev/\" rel=\"nofollow ugc noopener\">Vite</a> for TypeScript wrangling and a reload-on-save dev server</li><li><a href=\"https://grebedoc.dev/\" rel=\"nofollow ugc noopener\">Grebedoc</a> for static file hosting</li></ul>\n<p>All together, <a href=\"https://factoqual.grebedoc.dev/\" rel=\"nofollow ugc noopener\">factoqual.grebedoc.dev</a> now provides\nan interactive GUI for planning Quality upcyclers.\nIts source code is published at\n<a href=\"https://codeberg.org/SimonSapin/factoqual\" rel=\"nofollow ugc noopener\">codeberg.org/SimonSapin/factoqual</a>.</p>\n<p>### </p>\n<p>I was heavily inspired by Daniel Monteiro’s\n<a href=\"https://dfamonteiro.com/tags/factorio-quality/\" rel=\"nofollow ugc noopener\">blog posts</a> on quality math.\nOthers have done similar work,\nincluding <a href=\"https://forums.factorio.com/viewtopic.php?t=124772\" rel=\"nofollow ugc noopener\">Konage</a>\nand <a href=\"https://wiki.factorio.com/Tutorial:Quality_upcycling_math\" rel=\"nofollow ugc noopener\">Factorio wiki contributors</a>.\nBut I believe the exact solution from solving equilibrium equation as shown here is new,\nas opposed to iterative approximation an infinite sum.</p>","headings":[{"level":2,"text":"Solving Factorio Quality","id":"solving-factorio-quality"},{"level":3,"text":"Intro to Factorio and Quality","id":"intro-to-factorio-and-quality"},{"level":3,"text":"Factory planning tools","id":"factory-planning-tools"},{"level":3,"text":"Matrix math","id":"matrix-math"},{"level":3,"text":"Quality strategies","id":"quality-strategies"},{"level":3,"text":"Making an interactive calculator tool","id":"making-an-interactive-calculator-tool"}]}}