{
    "archive_path": "archive/1790762673.828632",
    "base_url": "exyr.org/2026/solving-factorio-quality",
    "basename": "",
    "bookmarked_date": "2026-09-30 10:04",
    "canonical": {
        "archive_org_path": "https://web.archive.org/web/exyr.org/2026/solving-factorio-quality",
        "dom_path": "output.html",
        "favicon_path": "favicon.ico",
        "git_path": "git/",
        "google_favicon_path": "https://www.google.com/s2/favicons?domain=exyr.org",
        "headers_path": "headers.json",
        "htmltotext_path": "htmltotext.txt",
        "index_path": "index.html",
        "media_path": "media/",
        "mercury_path": "mercury/content.html",
        "pdf_path": "output.pdf",
        "readability_path": "readability/content.html",
        "screenshot_path": "screenshot.png",
        "singlefile_path": "singlefile.html",
        "warc_path": "warc/",
        "wget_path": "exyr.org/2026/solving-factorio-quality/index.html"
    },
    "domain": "exyr.org",
    "extension": "",
    "hash": "PTCKS69JBMS6NWE8N63P",
    "history": {
        "archive_org": [],
        "dom": [],
        "favicon": [
            {
                "cmd": [
                    "curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--max-time",
                    "120",
                    "--output",
                    "favicon.ico",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) curl/curl 8.14.1 (x86_64-pc-linux-gnu)",
                    "https://www.google.com/s2/favicons?domain=exyr.org"
                ],
                "cmd_version": "curl 8.14.1 (x86_64-pc-linux-gnu)",
                "end_ts": "2026-09-30T10:06:20.319434+00:00",
                "index_texts": null,
                "output": "favicon.ico",
                "pwd": "/data/archive/1790762673.828632",
                "schema": "ArchiveResult",
                "start_ts": "2026-09-30T10:06:19.777789+00:00",
                "status": "succeeded"
            }
        ],
        "git": [],
        "headers": [
            {
                "cmd": [
                    "curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--head",
                    "--max-time",
                    "120",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) curl/curl 8.14.1 (x86_64-pc-linux-gnu)",
                    "https://exyr.org/2026/solving-factorio-quality/"
                ],
                "cmd_version": "curl 8.14.1 (x86_64-pc-linux-gnu)",
                "end_ts": "2026-09-30T10:06:21.245248+00:00",
                "index_texts": null,
                "output": "headers.json",
                "pwd": "/data/archive/1790762673.828632",
                "schema": "ArchiveResult",
                "start_ts": "2026-09-30T10:06:20.325180+00:00",
                "status": "succeeded"
            }
        ],
        "htmltotext": [],
        "media": [],
        "mercury": [
            {
                "cmd": [
                    "/app/node_modules/@postlight/parser/cli.js",
                    "https://exyr.org/2026/solving-factorio-quality/"
                ],
                "cmd_version": "1.0.0",
                "end_ts": "2026-09-30T10:06:32.974064+00:00",
                "index_texts": null,
                "output": "mercury",
                "pwd": "/data/archive/1790762673.828632",
                "schema": "ArchiveResult",
                "start_ts": "2026-09-30T10:06:27.901917+00:00",
                "status": "succeeded"
            }
        ],
        "pdf": [],
        "readability": [
            {
                "cmd": [
                    "/app/node_modules/readability-extractor/readability-extractor",
                    "/tmp/tmp8z9b07lv",
                    "https://exyr.org/2026/solving-factorio-quality/"
                ],
                "cmd_version": "0.0.11",
                "end_ts": "2026-09-30T10:06:27.894603+00:00",
                "index_texts": [
                    "Solving Factorio Quality\n      \n        Simon Sapin,\n        2026-02-15\n      \n    \n    I play Factorio the normal way: by writing matrix math code to plan the factory.\nBut we\u2019ll get to that.\nOr, TL;DR, go to my new online calculator tool.\nIntro to Factorio and Quality\nFactorio 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 \u201cby hand\u201d 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.\nAs \u201ctechnologies\u201d are unlocked in-game, Factorio offers many mechanisms to improve production.\nOne of them is modules: crafting machines have a (limited) number of slots\nto accept different kinds of modules that affect their stats:\nspeed modules make the machine run faster at the cost of more energy consumption,\nproductivity modules increase yield from the same ingredients at the cost of speed and energy,\netc.\nReleased in 2024, the Space Age extension adds new game mechanics including Quality:\nevery item and recipe now come in five quality tiers:\n\u2680 normal, \u2681 uncommon, \u2682 rare, \u2683 epic, and \u2684 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\n  \n  \n    Quality modules can have quality too.\n    Source: Factorio wiki\n  \n\nModules affect the probability QQ of any quality increase.\nFor a given craft, each quality tier increase after the first is another 10% chance.\nWe can build a table of the probabilities of output quality depending on input quality:\n\n  \n  \n    Source: Factorio wiki\n  \n\nFor example, the maximum possible quality chance in a machine with four module slots is 24.8%:\n\n  \n  \n    Jumping from normal to legendary in one step is only a 0.0248% chance.\n    Source: Factorio wiki\n  \n\nSome players dislike the introduction of randomness\nto a game that was mostly deterministic, but\nwith enough repetitions probabilities become ratios.\nThe probabilities are balanced so that\neven with multiple crafting steps (each a potential quality jumps),\ngetting high-quality items unavoidably involves also crafting many unwanted low-quality ones.\nTo avoid the factory grinding to a halt when storage eventually gets full,\nSpace Age also introduces the recycler:\na new machine that destroys any item and (usually) returns 25% of its ingredients.\nThis enables players to design \u201cupcycling\u201d contraptions\nthat craft and recycle in a loop with quality modules\nuntil items reach the desired quality, at the cost of consuming many more ingredients:\n\n\n\n  \n      \n  \n  \n    Source: Factorio blog\n  \n\nFactory planning tools\nSome video games are partly \u201cplayed\u201d outside of the game itself.\nBlue\u00a0Prince fully expects its players\nto keep extensive notes of everything they see,\nbut doesn\u2019t provide an in-game notepad or similar tool.\nFactory games lend themselves\nto building large spreadsheets 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\nthat reproduce much of the game\u2019s math to accurately model a production chain.\n\n  \n  \n    Example production chain in Factoriolab\n  \n\nThis is all optional in factory games, it\u2019s perfectly viable to play it by ear\nand just build more when seeing something lacking.\nBut I do like to plan in advance:\nhow many machines of each kind do I need? How much yield can I expect? Where are the bottlenecks?\nThe looping nature of quality upcycling makes this particularly challenging\neither to guess, or to calculate with existing tools.\nMatrix math\nLet\u2019s imagine:\n\nSome ingredients (for example iron plates)\nthat come from an arbitrary production chain that may involve quality modules.\nAny given ingredient has a probability to be in each quality tier:\n\u2680 normal, \u2681 uncommon, \u2682 rare, \u2683 epic, and \u2684 legendary.\nEnough assembling machines with each recipe tier\nto craft all ingredients into some product (for example pipes).\nThese machines have quality modules so that the a quality chance is 10%.\n\nLet\u2019s track the possible fates of one item:\nA product of a given tier can come from ingredients of the same tier or lower.\nThe total probability for this outcome is the sum of (independent) probabilites\nof different ways to get it.\nIn turn, those are the product of the percentage chance of a specific quality jump\ntimes the probability of having the corresponding ingredient tier in the first place:\np\u2680=i\u2680\u22c590%p\u2681=i\u2680\u22c59%+i\u2681\u22c590%p\u2682=i\u2680\u22c50.9%+i\u2681\u22c59%+i\u2682\u22c590%p\u2683=i\u2680\u22c50.09%+i\u2681\u22c50.9%+i\u2682\u22c59%+i\u2683\u22c590%p\u2684=i\u2680\u22c50.01%+i\u2681\u22c50.1%+i\u2682\u22c51%+i\u2683\u22c510%+i\u2684\n\\begin{align*}\np_\u2680 &= i_\u2680 \u22c5 90\\% \\\\\np_\u2681 &= i_\u2680 \u22c5 9\\%    &+ &i_\u2681 \u22c5 90\\% \\\\\np_\u2682 &= i_\u2680 \u22c5 0.9\\%  &+ &i_\u2681 \u22c5 9\\%   &+ &i_\u2682 \u22c5 90\\% \\\\\np_\u2683 &= i_\u2680 \u22c5 0.09\\% &+ &i_\u2681 \u22c5 0.9\\% &+ &i_\u2682 \u22c5 9\\% &+ &i_\u2683 \u22c5 90\\% \\\\\np_\u2684 &= i_\u2680 \u22c5 0.01\\% &+ &i_\u2681 \u22c5 0.1\\% &+ &i_\u2682 \u22c5 1\\% &+ &i_\u2683 \u22c5 10\\% &+ i_\u2684\n\\end{align*}\n\n(The percent sign can be thought of as implicit division by 100,\nso that \u201cpercentage of\u201d is the same as multiplication.)\nHere the percentage coefficients look transposed\nacross the diagonal compared to the quality jump probability table from the wiki,\nbut that\u2019s only because we\u2019ve arranged product tiers vertically.\nInstead let\u2019s group the probabilities of different tiers of the same item into row vectors:\nproduct=(p\u2680p\u2681p\u2682p\u2683p\u2684)ingredient=(i\u2680i\u2681i\u2682i\u2683i\u2684)\n\\begin{align*}\nproduct    &= \\begin{pmatrix*} p_\u2680 & p_\u2681 & p_\u2682 & p_\u2683 & p_\u2684 \\end{pmatrix*} \\\\\ningredient &= \\begin{pmatrix*} i_\u2680 & i_\u2681 & i_\u2682 & i_\u2683 & i_\u2684 \\end{pmatrix*} \\\\\n\\end{align*}\n\nNow our system of linear equations\ncan be written as a single equation where a vector\nis multiplied by\na transition matrix\nthat matches the wiki\u2019s table:\nproducts=ingredients\u22c5Tquality(10%)Tquality(q)=(1\u2212q9q109q1009q1000q100001\u2212q9q109q100q100001\u2212q9q10q100001\u2212qq00001)\n\\begin{align*}\n{products} &= {ingredients} \u22c5 T_{quality}(10\\%) \\\\[1em]\nT_{quality}(q) &=\n\\begin{pmatrix*}\n  1-q & \\frac{9q}{10} & \\frac{9q}{100} & \\frac{9q}{1000} & \\frac{q}{1000} \\\\[0.3em]\n  0   & 1-q           & \\frac{9q}{10}  & \\frac{9q}{100}  & \\frac{q}{100}  \\\\[0.3em]\n  0   & 0             & 1-q            & \\frac{9q}{10}   & \\frac{q}{10}   \\\\[0.3em]\n  0   & 0             & 0              & 1-q             & q              \\\\[0.3em]\n  0   & 0             & 0              & 0               & 1\n\\end{pmatrix*}\n\\end{align*}\n\nAs an edge case, zero quality chance means no tier transformation.\nThe corresponding transition matrix is\nthe identity matrix:\nTquality(0%)=I5T_{quality}(0\\%) = I_5\nThis may not seem like much progress,\nbut now a multi-step process can be computed through successive matrix multiplication.\nFor example mining iron ore with 7.5% quality chance,\nthen smelting it into iron plates with 5% quality chance, then crafting pipes with 10% chance.\nWith no productivity bonus, we get these probabilities of end-products:\npipe=(10000)\u22c5Tquality(7.5%)\u22c5Tquality(5%)\u22c5Tquality(10%)=(0.9250.06750.006750.0006750.000075)\u22c5Tquality(5%)\u22c5Tquality(10%)\u2248(0.878750.105750.0136130.0016650.000223)\u22c5Tquality(10%)\u2248(0.7908750.1742630.0296780.0044670.000719)\n\\begin{align*}\npipe &= \\begin{pmatrix*} 1 & 0 & 0 & 0 & 0 \\end{pmatrix*} \u22c5\n    T_{quality}(7.5\\%) \u22c5\n    T_{quality}(5\\%) \u22c5\n    T_{quality}(10\\%) \\\\\n  &= \\begin{pmatrix*} 0.925 & 0.0675 & 0.00675 & 0.000675 & 0.000075 \\end{pmatrix*} \u22c5\n    T_{quality}(5\\%) \u22c5\n    T_{quality}(10\\%) \\\\\n  &\u2248 \\begin{pmatrix*} 0.87875 & 0.10575 & 0.013613 & 0.001665 & 0.000223 \\end{pmatrix*} \u22c5\n    T_{quality}(10\\%) \\\\\n  &\u2248 \\begin{pmatrix*} 0.790875 & 0.174263 & 0.029678 & 0.004467 & 0.000719 \\end{pmatrix*}\n\\end{align*}\n\nQuality strategies\nWhile it is possible to use quality modules as much as possible and deal with\nEvery tier Everywhere All at Once,\nhere we\u2019ll focus on smaller self-contained systems.\n\u201cGambling\u201d: opportunistic quality without recycling\nThe easiest but also least effective is to craft from normal-quality ingredients,\nwith quality modules, and not recycle anything.\nThis can be done before unlocking the recycler but is only viable for a small number of items,\nsuch as crafting a few hundred asteroid collectors to hope to get a dozen uncommon or rare ones\nfor an early space ship.\nThis is improved when the factory has another use for normal-quality items.\nFor example if placing thousands of normal-quality solar panels on the ground,\ncrafting them with quality modules gives a better yield of higher-quality ones for space ships\nbefore the output buffers fill up.\nWith a single step and no loop, this is simplest to calculate:\nthe expected product tier distribution is the first row of the Tquality(q)T_{quality}(q) transition matrix\nor of the probability table found on the wiki.\n\u201cWashing\u201d: pure recycling loop\nFor most items, the recycler reverses the main crafting recipe and returns 25% of the ingredients.\nBut some items don\u2019t have a crafting recipe (like ore)\nor it is considered irreversible (typically smelting and chemical processes).\nIn that case the recycler produces either nothing or, 25% of the time, the same item.\nThis process can improve quality if the recycler has quality modules.\nRepeating it in a loop, eventually all items will be either destroyed\nor improved until they reach any desired quality tier.\nSelf-recycling items is arguably not the common case but let\u2019s start here since the math is simpler.\n\n  \n  \n    Example setup mining ore (with quality modules),\n    and \u201cwashing\u201d it until rare or above.\n  \n\nLet\u2019s consider one item injected into the system, in this case from mining,\nand call freshunitfresh_{unit} the 5-component row vector of probabilities of each quality tier.\nAfter we transform that vector, the new probabilities may add up to less than one.\nThe implicit remaining case is not having an item at all at a given place.\nFor example, the recycler producing nothing 75% of the time can be represented\nby multiplying a vector of probabilities by 14\\frac{1}{4}.\nFiltering based on quality can also be represented with matrix multiplication.\nIn the case of extracting rare or above:\nTfilterKeep=(1000001000000000000000000)TfilterExtract=(0000000000001000001000001)=I5\u2212TfilterKeep\n\\begin{align*}\nT_{filterKeep} &=\n\\begin{pmatrix*}\n  1 & 0 & 0 & 0 & 0 \\\\\n  0 & 1 & 0 & 0 & 0 \\\\\n  0 & 0 & 0 & 0 & 0 \\\\\n  0 & 0 & 0 & 0 & 0 \\\\\n  0 & 0 & 0 & 0 & 0\n\\end{pmatrix*} \\\\\nT_{filterExtract} &=\n\\begin{pmatrix*}\n  0 & 0 & 0 & 0 & 0 \\\\\n  0 & 0 & 0 & 0 & 0 \\\\\n  0 & 0 & 1 & 0 & 0 \\\\\n  0 & 0 & 0 & 1 & 0 \\\\\n  0 & 0 & 0 & 0 & 1\n\\end{pmatrix*} = I_5 - T_{filterKeep}\n\\end{align*}\n\nThe combined effect of one iteration through the loop\nis filtering to keep, then recycling with quality:\nL=TfilterKeep\u00b714\u00b7Tquality(q)\n\\begin{align*}\nL &= T_{filterKeep} \u00b7 \\frac{1}{4} \u00b7 T_{quality}(q)\n\\end{align*}\n\nNow let\u2019s consider the possible ways an item can be extracted:\n\nIt originally had high enough quality to be extracted immediately: probability vector\nfreshunit\u00b7TfilterExtract=freshunit\u00b7L0\u00b7TfilterExtractfresh_{unit} \u00b7 T_{filterExtract} = fresh_{unit} \u00b7 L^0 \u00b7 T_{filterExtract}\nIt went through the recycling loop exactly once:\nfreshunit\u00b7TfilterKeep\u00b7L\u00b7TfilterExtract=freshunit\u00b7L1\u00b7TfilterExtractfresh_{unit} \u00b7 T_{filterKeep} \u00b7 L \u00b7 T_{filterExtract} = fresh_{unit} \u00b7 L^1 \u00b7 T_{filterExtract}\nIt went through recycling exactly twice:\nfreshunit\u00b7TfilterKeep\u00b7L\u00b7L\u00b7TfilterExtract=freshunit\u00b7L2\u00b7TfilterExtractfresh_{unit} \u00b7 T_{filterKeep} \u00b7 L \u00b7 L \u00b7 T_{filterExtract} = fresh_{unit} \u00b7 L^2 \u00b7 T_{filterExtract}\netc.\n\nAny given item will eventually be extracted or destroyed,\nbut there is no upper bound on how many loops that can take.\nThe combination of all possible outcomes is an infinite sum:\nextractedunit=freshunit\u00b7(\u2211i=0+\u221eLi)\u00b7TfilterExtract\nextracted_{unit} = fresh_{unit} \u00b7 \\bigg(\\sum_{i=0}^{+\u221e} L^i\\bigg) \u00b7 T_{filterExtract}\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.\nLong-term average throughput\nThe key insight is that\nit doesn\u2019t 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\nprobabilities become ratios.\nSo instead of probablities for a single item\nlet\u2019s 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 \u201cwashing\u201d pure recycling loop, the relevant vectors are:\n\nfreshfresh items injected into the system, with any quality distribution,\nin this example based on quality modules in miners\nItems fromRecyclingfromRecycling\nAll items going through the splittersplitter\nItems extractedextracted based on their quality tier, in this example rare or above\nItems toRecycletoRecycle with quality qq, those not extracted\n\nWe treat qq and freshfresh as a fixed parameters and other vectors as unknowns we want to resolve.\nThe system converges to a dynamic equilibrium\nwhere the following equations hold for long-term averages:\nsplitter=fresh+fromRecyclingsplitter=extracted+toRecycleextracted=splitter\u22c5TfilterExtracttoRecycle=splitter\u22c5TfilterKeepfromRecycling=toRecycle\u22c514Tquality(q)\n\\begin{align*}\nsplitter &= fresh + fromRecycling \\\\\nsplitter &= extracted + toRecycle \\\\\nextracted &= splitter \u22c5 T_{filterExtract} \\\\\ntoRecycle &= splitter \u22c5 T_{filterKeep} \\\\\nfromRecycling &= toRecycle \u22c5 \\frac{1}{4} T_{quality}(q)\n\\end{align*}\n\nWe can rearrange and substitute:\nsplitter=fresh+fromRecyclingsplitter\u2212fromRecycling=freshsplitter\u2212toRecycle\u22c514Tquality(q)=freshsplitter\u2212splitter\u22c5TfilterKeep\u22c514Tquality(q)=freshsplitter\u22c5(I5\u2212TfilterKeep\u22c514Tquality(q))=fresh\n\\begin{align*}\nsplitter = fresh + fromRecycling \\\\\nsplitter - fromRecycling = fresh \\\\\nsplitter - toRecycle \u22c5 \\frac{1}{4} T_{quality}(q) = fresh \\\\\nsplitter - splitter \u22c5 T_{filterKeep} \u22c5 \\frac{1}{4} T_{quality}(q) = fresh \\\\\nsplitter \u22c5 (I_5 - T_{filterKeep} \u22c5 \\frac{1}{4} T_{quality}(q)) = fresh \\\\\n\\end{align*}\n\nTranspose to get column vectors instead of row vectors and match the\nclassic convention:\n(splitter\u22c5(I5\u2212TfilterKeep\u22c514Tquality(q)))T=freshT(I5\u2212TfilterKeep\u22c514Tquality(q))T\u22c5splitterT=freshT\n\\begin{align*}\n(splitter \u22c5 (I_5 - T_{filterKeep} \u22c5 \\frac{1}{4} T_{quality}(q)))^T &= fresh^T \\\\\n(I_5 - T_{filterKeep} \u22c5 \\frac{1}{4} T_{quality}(q))^T \u22c5 splitter^T &= fresh^T\n\\end{align*}\n\nIntroduce some new names:\nA=(I5\u2212TfilterKeep\u22c514Tquality(q))Tx=splitterTb=freshT\n\\begin{align*}\nA &= (I_5 - T_{filterKeep} \u22c5 \\frac{1}{4} T_{quality}(q))^T \\\\\nx &= splitter^T \\\\\nb &= fresh^T\n\\end{align*}\n\nNow we have equilibrium as a system of linear equations in the classic A\u00b7x=bA\u00b7x = b form,\nthat we can solve for xx\nusing Gaussian elimination.\nFrom xx we can easily compute splittersplitter,\nthen toRecycletoRecycle (for the number of recyclers needed)\nand extractedextracted (for the overall yield of the system).\nIf the example setup above was scaled to mine 100 ore per second, the parameters would be:\nfresh=(9090.90.090.01)TfilterExtract=filter(\u2265rare)q=10%\n\\begin{align*}\nfresh &= \\begin{pmatrix*} 90 & 9 & 0.9 & 0.09 & 0.01 \\end{pmatrix*} \\\\\nT_{filterExtract} &= filter(\u2265 rare) \\\\\nq &= 10\\%\n\\end{align*}\n\nAnd the solution with Gaussian Elimination:\ntoRecycle\u2248(116.129114.9844000)extracted\u2248(001.49850.14990.0167)\n\\begin{align*}\ntoRecycle &\u2248 \\begin{pmatrix*} 116.1291 & 14.9844 & 0 & 0 & 0 \\end{pmatrix*} \\\\\nextracted &\u2248 \\begin{pmatrix*} 0 & 0 & 1.4985 & 0.1499 & 0.0167 \\end{pmatrix*} \\\\\n\\end{align*}\n\nConverting from per second, the extracted rates are close to 90 per minute rare,\n9 per minute epic, and 1 per minute legendary.\n\nFor another example:\n\nInjecting only normal-quality fresh items, for now in some arbitrary unit:\nfresh=(10000)fresh = \\begin{pmatrix*} 1 & 0 & 0 & 0 & 0 \\end{pmatrix*}\nRecycling with the best available quality modules: q=24.8%q = 24.8\\%\nExtracting only legendary-quality items\n\nSolving the equation gives:\ntoRecycle\u2248(1.2315280.084630.0142790.002410)extracted\u2248(00000.000366716)\n\\begin{align*}\ntoRecycle &\u2248 \\begin{pmatrix*} 1.231528 & 0.08463 & 0.014279 & 0.00241 & 0 \\end{pmatrix*} \\\\\nextracted &\u2248 \\begin{pmatrix*} 0 & 0 & 0 & 0 & 0.000366716 \\end{pmatrix*} \\\\\n\\end{align*}\n\nSo for items that recycle to themselves, \u201cbrute-force\u201d washing consumes on average\n1extracted\u2684\u22482726.91\\frac{1}{extracted_\u2684} \u2248 2726.91 normal-quality inputs for every legendary output.\nThis matches the ratio that others have calculated.\nThe recycling capacity required is just shy of 43\\frac{4}{3} of the fresh input rate.\n\u201cUpcycling\u201d: crafting + recycling loop\nFor items where recycling does return ingredients,\nwe can chain crafting machines with recyclers to form a loop:\n\n  \n  \n    Example setup upcycling construction robots until lengendary.\n  \n\nMost crafting recipes require multiple ingredients in various quantities,\nbut here we\u2019ll abstract over this and consider \u201cthe set of ingredients for one craft\u201d\nas the base unit for our measurements.\nIn this example, each machine crafting construction robots can consume\n4 electronic circuits per second and 2 flying robot frames per second,\nbut we\u2019ll call that \u201c2 ingredients per second\u201d.\nSo we\u2019re measuring long-term avarage rates of ingredients and products, each in five quality tiers,\nbut they only appear in separate parts of the system so we\u2019ll stick with 5-components vectors\nand don\u2019t need to move to 10-dimensional math.\n(Wink wink foreshadowing)\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.)\nSo the 5-component row vectors we\u2019ll consider are, for ingredients:\n\nfreshIfreshI injected into the system, with any quality distribution\nIngredients fromRecyclingfromRecycling\ntotalItotalI\nextractedIextractedI based on their quality tier, in this example none\nIngredients toCrafttoCraft new products from\n\nAnd for products:\n\nfreshPfreshP injected into the system, with any quality distribution\nProducts fromCraftingfromCrafting\ntotalPtotalP\nextractedPextractedP based on their quality tier, in this example lengendary\nProducts toRecycletoRecycle\n\nWe treat freshIfreshI and freshPfreshP as fixed parameters,\nand other vectors as unknown we want to resolve.\nAgain the transition matrices for filtering based on quality tier\nare complementary parts of the identity matrix.\nIn this example:\nTfilterKeepingredients=I5TfilterExtractingredients=05TfilterKeepproducts=(1000001000001000001000000)TfilterExtractproducts=(0000000000000000000000001)=I5\u2212TfilterKeepproducts\n\\begin{align*}\nT^{ingredients}_{filterKeep} &= I_5 \\\\\nT^{ingredients}_{filterExtract} &= 0_5 \\\\\nT^{products}_{filterKeep} &=\n\\begin{pmatrix*}\n  1 & 0 & 0 & 0 & 0 \\\\\n  0 & 1 & 0 & 0 & 0 \\\\\n  0 & 0 & 1 & 0 & 0 \\\\\n  0 & 0 & 0 & 1 & 0 \\\\\n  0 & 0 & 0 & 0 & 0\n\\end{pmatrix*} \\\\\nT^{products}_{filterExtract} &=\n\\begin{pmatrix*}\n  0 & 0 & 0 & 0 & 0 \\\\\n  0 & 0 & 0 & 0 & 0 \\\\\n  0 & 0 & 0 & 0 & 0 \\\\\n  0 & 0 & 0 & 0 & 0 \\\\\n  0 & 0 & 0 & 0 & 1\n\\end{pmatrix*} = I_5 - T^{products}_{filterKeep}\n\\end{align*}\n\nWe account for a crafting productivityBonusproductivityBonus which is 0% in this example\nbut could be greater with some crafting machines or with productivity modules.\nThe quality chance may also be different for crafting v.s. recycling\n(for example when crafting with productivity modules,\nor based on the machine\u2019s number of modules slots).\nFor short(er), let\u2019s call:\nTcraft=(1+productivityBonus)\u22c5Tquality(qcrafting)Trecycle=14Tquality(qrecycling)\n\\begin{align*}\nT_{craft} &= (1 + productivityBonus) \u22c5 T_{quality}(q_{crafting}) \\\\\nT_{recycle} &= \\frac{1}{4} T_{quality}(q_{recycling})\n\\end{align*}\n\nThe system converges to equilibrium where:\ntotalI=freshI+fromRecyclingtotalI=extractedI+toCraftextractedI=totalI\u22c5TfilterExtractingredientstoCraft=totalI\u22c5TfilterKeepingredientsfromCrafting=toCraft\u22c5TcrafttotalP=freshP+fromCraftingtotalP=extractedP+toRecycleextractedP=totalP\u22c5TfilterExtractproductstoRecycle=totalP\u22c5TfilterKeepproductsfromRecycling=toRecycle\u22c5Trecycle\n\\begin{align*}\ntotalI &= freshI + fromRecycling \\\\\ntotalI &= extractedI + toCraft \\\\\nextractedI &= totalI \u22c5 T^{ingredients}_{filterExtract} \\\\\ntoCraft &= totalI \u22c5 T^{ingredients}_{filterKeep} \\\\\nfromCrafting &= toCraft \u22c5 T_{craft} \\\\[2em]\ntotalP &= freshP + fromCrafting \\\\\ntotalP &= extractedP + toRecycle \\\\\nextractedP &= totalP \u22c5 T^{products}_{filterExtract} \\\\\ntoRecycle &= totalP \u22c5 T^{products}_{filterKeep} \\\\\nfromRecycling &= toRecycle \u22c5 T_{recycle}\n\\end{align*}\n\nWe can rearrange and substitute:\ntotalP=freshP+fromCraftingtotalP=freshP+toCraft\u22c5TcrafttotalP=freshP+totalI\u22c5TfilterKeepingredients\u22c5TcrafttotalP=freshP+(freshI+fromRecycling)\u22c5TfilterKeepingredients\u22c5TcrafttotalP=freshP+(freshI+toRecycle\u22c5Trecycle)\u22c5TfilterKeepingredients\u22c5TcrafttotalP=freshP+(freshI+totalP\u22c5TfilterKeepproducts\u22c5Trecycle)\u22c5TfilterKeepingredients\u22c5Tcraft\n\\begin{align*}\ntotalP &= freshP + fromCrafting \\\\\ntotalP &= freshP + toCraft \u22c5 T_{craft} \\\\\ntotalP &= freshP + totalI \u22c5 T^{ingredients}_{filterKeep} \u22c5 T_{craft} \\\\\ntotalP &= freshP + (freshI + fromRecycling) \u22c5 T^{ingredients}_{filterKeep} \u22c5 T_{craft} \\\\\ntotalP &= freshP + (freshI + toRecycle \u22c5 T_{recycle}) \u22c5 T^{ingredients}_{filterKeep} \u22c5 T_{craft} \\\\\ntotalP &= freshP + (freshI + totalP \u22c5 T^{products}_{filterKeep} \u22c5 T_{recycle}) \u22c5 T^{ingredients}_{filterKeep} \u22c5 T_{craft}\n\\end{align*}\n\nLet\u2019s introduce a couple more names:\nTfilterRecycle=TfilterKeepproducts\u22c5TrecycleTfilterCraft=TfilterKeepingredients\u22c5TcrafttotalP=freshP+(freshI+totalP\u22c5TfilterRecycle)\u22c5TfilterCrafttotalP\u2212totalP\u22c5TfilterRecycle\u22c5TfilterCraft=freshP+freshI\u22c5TfilterCrafttotalP\u22c5(I5\u2212TfilterRecycle\u22c5TfilterCraft)=freshP+freshI\u22c5TfilterCraft(I5\u2212TfilterRecycle\u22c5TfilterCraft)T\u22c5totalPT=(freshP+freshI\u22c5TfilterCraft)T\n\\begin{align*}\n\\begin{align*}\nT_{filterRecycle} &= T^{products}_{filterKeep} \u22c5 T_{recycle} \\\\\nT_{filterCraft} &= T^{ingredients}_{filterKeep} \u22c5 T_{craft} \\\\[1em]\ntotalP &= freshP + (freshI + totalP \u22c5 T_{filterRecycle}) \u22c5 T_{filterCraft} \\\\\n\\end{align*}\\\\\n\\begin{align*}\ntotalP - totalP \u22c5 T_{filterRecycle} \u22c5 T_{filterCraft} &= freshP + freshI \u22c5 T_{filterCraft} \\\\\ntotalP \u22c5 (I_5 - T_{filterRecycle} \u22c5 T_{filterCraft}) &= freshP + freshI \u22c5 T_{filterCraft} \\\\\n(I_5 - T_{filterRecycle} \u22c5 T_{filterCraft})^T \u22c5 totalP^T &= (freshP + freshI \u22c5 T_{filterCraft})^T\n\\end{align*}\n\\end{align*}\n\nWe\u2019ve again massaged our problem into the standard A\u00b7x=bA\u00b7x = b form with:\nA=(I5\u2212TfilterRecycle\u22c5TfilterCraft)Tx=totalPTb=(freshP+freshI\u22c5TfilterCraft)T\n\\begin{align*}\nA &= (I_5 - T_{filterRecycle} \u22c5 T_{filterCraft})^T \\\\\nx &= totalP^T \\\\\nb &= (freshP + freshI \u22c5 T_{filterCraft})^T\n\\end{align*}\n\nSolving for xx gives us totalPtotalP,\nand plugging that into the original equilibrium equations gives everything else.\nIn the example setup above, scaled for now to an arbitrary unit, the parameters are:\nfreshIunit=(10000)freshP=(00000)TfilterExtractingredients=filter(nothing)TfilterExtractproducts=filter(legendary)productivityBonus=0%qcrafting=24.8%qrecycling=24.8%\n\\begin{align*}\nfreshI_{unit} &= \\begin{pmatrix*} 1 & 0 & 0 & 0 & 0 \\end{pmatrix*} \\\\\nfreshP &= \\begin{pmatrix*} 0 & 0 & 0 & 0 & 0 \\end{pmatrix*} \\\\\nT^{ingredients}_{filterExtract} &= filter(nothing) \\\\\nT^{products}_{filterExtract} &= filter(legendary) \\\\\nproductivityBonus &= 0\\% \\\\\nq_{crafting} &= 24.8\\% \\\\\nq_{recycling} &= 24.8\\%\n\\end{align*}\n\nAnd the solution with Gaussian Elimination:\ntoCraftunit\u2248(1.1646550.1138360.0394040.0111330.002154)toRecycleunit\u2248(0.875820.3455550.0810350.0223070)extractedIunit=(00000)extractedPunit\u2248(00000.006464)\n\\begin{align*}\ntoCraft_{unit} &\u2248 \\begin{pmatrix*} 1.164655 & 0.113836 & 0.039404 & 0.011133 & 0.002154 \\end{pmatrix*} \\\\\ntoRecycle_{unit} &\u2248 \\begin{pmatrix*} 0.87582 & 0.345555 & 0.081035 & 0.022307 & 0 \\end{pmatrix*} \\\\\nextractedI_{unit} &= \\begin{pmatrix*} 0 & 0 & 0 & 0 & 0 \\end{pmatrix*} \\\\\nextractedP_{unit} &\u2248 \\begin{pmatrix*} 0 & 0 & 0 & 0 & 0.006464 \\end{pmatrix*}\n\\end{align*}\n\nIn practice, the bottleneck of this system is the crafting speed of normal-quality products:\n360 per minute.\nSo let\u2019s multiply everything by 360toCraftunit\u2680\\frac{360}{toCraft_{unit_\u2680}}\nfreshIscaled\u2248(309.100000)toCraftscaled\u2248(36035.1812.173.440.66)toRecyclescaled\u2248(270.72106.8125.046.890)extractedPscaled\u2248(00001.99)\n\\begin{align*}\nfreshI_{scaled} &\u2248 \\begin{pmatrix*} 309.10 & 0 & 0 & 0 & 0 \\end{pmatrix*} \\\\\ntoCraft_{scaled} &\u2248 \\begin{pmatrix*} 360 & 35.18 & 12.17 & 3.44 & 0.66 \\end{pmatrix*} \\\\\ntoRecycle_{scaled} &\u2248 \\begin{pmatrix*} 270.72 & 106.81 & 25.04 & 6.89 & 0 \\end{pmatrix*} \\\\\nextractedP_{scaled} &\u2248 \\begin{pmatrix*} 0 & 0 & 0 & 0 & 1.99 \\end{pmatrix*}\n\\end{align*}\n\nIn conclusion, this system produces on average almost 2\u00a0legendary construction robots per minute,\nand consumes about 309 sets of ingredients (309 frames + 618 circuits) per minute.\n\nFor a select few items in Space Age,\nthe productivity bonus can be increased through repeatable research.\nThe game enforces a hard cap of +300% bonus so that crafting then recycling\nreturns at most the ingredients that we started with, never more.\nProductivity research levels get exponentially expensive\nso let\u2019s assume that we use also productivity modules to reach the cap,\nmeaning we don\u2019t have quality modules in crafting machines.\nAnd this time, let\u2019s inject fresh products instead of ingredients.\nfreshI=(00000)freshPunit=(10000)TfilterExtractingredients=filter(nothing)TfilterExtractproducts=filter(legendary)productivityBonus=+300%qcrafting=0%qrecycling=24.8%\n\\begin{align*}\nfreshI &= \\begin{pmatrix*} 0 & 0 & 0 & 0 & 0 \\end{pmatrix*} \\\\\nfreshP_{unit} &= \\begin{pmatrix*} 1 & 0 & 0 & 0 & 0 \\end{pmatrix*} \\\\\nT^{ingredients}_{filterExtract} &= filter(nothing) \\\\\nT^{products}_{filterExtract} &= filter(legendary) \\\\\nproductivityBonus &= +300\\% \\\\\nq_{crafting} &= 0\\% \\\\\nq_{recycling} &= 24.8\\%\n\\end{align*}\n\nOur model predicts:\ntoCraftunit\u2248(0.7580.9070.9070.9070.25)toRecycleunit\u2248(4.0323.6293.6293.6290)extractedIunit=(00000)extractedPunit=(00001)\n\\begin{align*}\ntoCraft_{unit} &\u2248 \\begin{pmatrix*} 0.758 & 0.907 & 0.907 & 0.907 & 0.25 \\end{pmatrix*} \\\\\ntoRecycle_{unit} &\u2248 \\begin{pmatrix*} 4.032 & 3.629 & 3.629 & 3.629 & 0 \\end{pmatrix*} \\\\\nextractedI_{unit} &= \\begin{pmatrix*} 0 & 0 & 0 & 0 & 0 \\end{pmatrix*} \\\\\nextractedP_{unit} &= \\begin{pmatrix*} 0 & 0 & 0 & 0 & 1 \\end{pmatrix*}\n\\end{align*}\n\nPerhaps surprisingly, the rates for intermediate quality tiers are identical.\nA maximal productivity bonus enables\nturning a normal quality product into a legendary one without any ressource loss,\nat the cost of many machines and modules to get significant throughput.\n\u201cSpace casino\u201d: asteroid reprocessing loop\nIn Space Age, each space platform is a mini-factory.\nInstead of mining ore from the ground they collect asteroid chunks and crush them to get resources.\nThose can be used for a space ship\u2019s own fuel and ammunition,\nor to feed a production chain in space whose end-product is sent to a planet\u2019s ground.\nThree different types of chunks (metallic, carbonic, and oxide) yield different ressources\nand are more or less frequent in different regions of the solar system.\nAsteroid chunks can be \u201creprocessed\u201d for a chance to get a different type (or nothing).\n\n  \n  \n    Asteroid reprocessing recipes.\n    Source: Factorio wiki\n  \n\nAssuming enough crushers with each recipe,\nwe can represent this process mathematically\nas multiplying a row vector by a square 3\u00d73 transition matrix:\nfromReprocessing=toReprocess\u22c5TreprocessingTreprocesing=(40%20%20%20%40%20%20%20%40%)\n\\begin{align*}\nfromReprocessing &= toReprocess \u22c5 T_{reprocessing} \\\\[1em]\nT_{reprocesing} &=\n\\begin{pmatrix*}\n  40\\% & 20\\% & 20\\% \\\\\n  20\\% & 40\\% & 20\\% \\\\\n  20\\% & 20\\% & 40\\%\n\\end{pmatrix*}\n\\end{align*}\n\nReprocessing also accepts quality modules so it can be used in a loop much like \u201cwashing\u201d.\nCrushing legendary chunks yields legendary versions of some base resources\nthat can be used for crafting other legendary items.\nThis time we\u2019ll 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\u2019ll represent this with 15-component row vectors and 15\u00d715 transition matrices.\nWe build up the latter as block matrices\nmade of nine 5\u00d75 blocks:\nTreprocessing15=(40%\u22c5I520%\u22c5I520%\u22c5I520%\u22c5I540%\u22c5I520%\u22c5I520%\u22c5I520%\u22c5I540%\u22c5I5)Tquality15(q)=(Tquality(q)000Tquality(q)000Tquality(q))TfilterKeep15=(TfilterKeepMetallic000TfilterKeepCarbonic000TfilterKeepOxide)total=fresh+fromReprocessingfromReprocessing=toReprocess\u22c5Treprocessing15\u22c5Tquality15(q)toReprocess=total\u22c5TfilterKeep15extracted=total\u22c5TfilterExtract15=total\u22c5(I15\u2212TfilterKeep15)\n\\begin{align*}\nT^{15}_{reprocessing} &=\n\\begin{pmatrix*}\n  40\\% \u22c5 I_5 & 20\\% \u22c5 I_5 & 20\\% \u22c5 I_5 \\\\\n  20\\% \u22c5 I_5 & 40\\% \u22c5 I_5 & 20\\% \u22c5 I_5 \\\\\n  20\\% \u22c5 I_5 & 20\\% \u22c5 I_5 & 40\\% \u22c5 I_5\n\\end{pmatrix*} \\\\[2em]\nT^{15}_{quality}(q) &=\n\\begin{pmatrix*}\n  T_{quality}(q) & 0 & 0 \\\\\n  0 & T_{quality}(q) & 0 \\\\\n  0 & 0 & T_{quality}(q)\n\\end{pmatrix*} \\\\[2em]\nT^{15}_{filterKeep} &=\n\\begin{pmatrix*}\n  T_{filterKeepMetallic} & 0 & 0 \\\\\n  0 & T_{filterKeepCarbonic} & 0 \\\\\n  0 & 0 & T_{filterKeepOxide}\n\\end{pmatrix*} \\\\[2em]\ntotal &= fresh + fromReprocessing \\\\\nfromReprocessing &= toReprocess \u22c5 T^{15}_{reprocessing} \u22c5 T^{15}_{quality}(q) \\\\\ntoReprocess &= total \u22c5 T^{15}_{filterKeep} \\\\\nextracted &= total \u22c5 T^{15}_{filterExtract} = total \u22c5 (I_{15} - T^{15}_{filterKeep})\n\\end{align*}\n\n\nAside from the higher dimension,\nthe math is the same as for washing\nand we end up with a system of linear equations A\u00b7x=bA\u00b7x = b to solve for xx, with:\nA=(I15\u2212TfilterKeep15\u22c5Treprocessing15\u22c5Tquality15(q))Tx=totalTb=freshT\n\\begin{align*}\nA &= (I_{15} - T^{15}_{filterKeep} \u22c5 T^{15}_{reprocessing} \u22c5 T^{15}_{quality}(q))^T \\\\\nx &= total^T \\\\\nb &= fresh^T \\\\\n\\end{align*}\n\nAsteroid crushers have two module slots,\nso the best possible quality chance for reprocessing is q=12.4%q = 12.4\\%.\nAsteroid collectors don\u2019t have any module slot,\nso freshly-collected chunks are always normal-quality.\nWe build the 15-component freshfresh vector from its 3 components for normal-quality rate\nof each asteroid type (metallic, carbonic, oxide).\nFor example in Nauvis orbit:\nfresh\u2680=(362616)fresh_\u2680 = \\begin{pmatrix*} \\frac{3}{6} & \\frac{2}{6} & \\frac{1}{6} \\end{pmatrix*}\nLet\u2019s say that we only extract legendary oxide chunks and reprocess everything else.\nSolving the equation gives two complementary 15-components row vectors\ntoReprocesstoReprocess and extractedextracted that we can rearrange into 3\u00d75 tables\nExample: in Nauvis orbit, extract legendary oxide chunks and reprocess everything else:\nfresh\u2680=(362616)q=12.4%\n\\begin{align*}\nfresh_\u2680 &= \\begin{pmatrix*} \\frac{3}{6} & \\frac{2}{6} & \\frac{1}{6} \\end{pmatrix*} \\\\\nq &= 12.4\\%\n\\end{align*}\n\n\n  \n    To reprocess\n  \n  \n    \n      \u2680\u2681\u2682\u2683\u2684\n    \n    \n      Metallic\n      1.316160.337910.133140.052860.0175\n    \n    \n      Carbonic\n      1.114090.332440.132450.052770.01748\n    \n    \n      Oxide\n      0.912020.326970.131750.052680\n    \n  \n  \n    Extracted\n  \n  \n    \n      \u2680\u2681\u2682\u2683\u2684\n    \n    \n      Metallic\n      00000\n    \n    \n      Carbonic\n      00000\n    \n    \n      Oxide\n      00000.01398\n    \n  \n\nWe observe:\n\nAs the quality tier increases,\nthe distribution of asteroid types quickly converges to an even 13\\frac{1}{3} each.\nThe distribution of normal-quality fresh input has negligible impact\non that of legendary-quality chunks going through the system.\nWhen extracting a single type of legendary asteroids,\nthe legendary yield is about 171.5\u22481.4%\\frac{1}{71.5} \u2248 1.4\\% of the total fresh input.\nThis is much better than about 12726\\frac{1}{2726} for \u201cwashing\u201d a.k.a. pure recycling,\nthanks to reprocessing destroying its input only 20% of the time\nv.s. 75% for recycling.\n\nOther strategies\nThere are other ways to get quality items that don\u2019t neatly fit in the categories above\n(honorable mention to \u201cThe LDS Shuffle\u201d)\nand I\u2019m 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\nRepresent throughput of multiple kinds of items as vectors\nRepresent linear transformations (crafting, recycling, \u2026) as matrix multiplication\nRepresent dynamic equilibrium as a matrix equation\nSolve the equation, using Gaussian Elimination if needed\n\nMaking an interactive calculator tool\nDoing matrix math by hand is obviously tedious and error-prone, let\u2019s have computers do it for us.\nI started with Rust out of habit.\nnalgebra works out great for the matrix math we do here.\nIn includes multiple solvers for A\u00b7x=bA\u00b7x = b 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.\nIn practice floating point would be perfectly adequate,\nbut it is a fixed-precision approximation\nso every step of computation potentially introduces some error.\nWouldn\u2019t it be nice to get an exact result, just because we can?\nThe matrix sizes and number of operations are fixed and relatively small,\nso we don\u2019t need to optimize the code for speed.\nEvery operation we use is ultimately addition, substraction, multiplication, or division.\nSo if all of our parameters are rational\nthe result will be too.\nnum_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\noverflow checks\nturns out to be sufficient for 15\u00d715 martices.\n(i64 is sufficient for 5\u00d75.)\nAt this point I had a functional Rust library doing all of the math above,\nbut editing source code to tweak parameters isn\u2019t a nice user experience.\nI\u2019d much prefer something like Factoriolab.\nAnd to be easy to use by other people it really should be on the web.\nMy Rust code can be compiled to WebAssembly but then I would need to either:\n\nBuild the entire GUI with Rust + wasm as well.\nIt\u2019s possible but the tooling\nisn\u2019t great yet\nBuild the GUI in JavaScript or TypeScript (taking advantage of mature tools)\nand bridge into wasm for the math.\nBut because of the many parameters the API surface is significant,\nand doing that much bridging isn\u2019t fun\n\nSo I ended up rewriting the whole thing in TypeScript.\nBigInt\nis built-in,\nFactoriolab already has a good open-source\nrational library,\nand making a generic matrix library isn\u2019t too hard.\nAs to building an interactive GUI in the browser,\nlast time I did much of it jQuery was the hot new thing.\nI didn\u2019t feel like learning React so I settled on:\n\nVanJS for minimal reactive goodness\nVite for TypeScript wrangling and a reload-on-save dev server\nGrebedoc for static file hosting\n\nAll together, factoqual.grebedoc.dev now provides\nan interactive GUI for planning Quality upcyclers.\nIts source code is published at\ncodeberg.org/SimonSapin/factoqual.\n Acknowledgments\nI was heavily inspired by Daniel Monteiro\u2019s\nblog posts on quality math.\nOthers have done similar work,\nincluding Konage\nand Factorio wiki contributors.\nBut I believe the exact solution from solving equilibrium equation as shown here is new,\nas opposed to iterative approximation an infinite sum."
                ],
                "output": "readability",
                "pwd": "/data/archive/1790762673.828632",
                "schema": "ArchiveResult",
                "start_ts": "2026-09-30T10:06:24.330777+00:00",
                "status": "succeeded"
            }
        ],
        "screenshot": [],
        "singlefile": [],
        "title": [
            {
                "cmd": [
                    "curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--max-time",
                    "120",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) curl/curl 8.14.1 (x86_64-pc-linux-gnu)",
                    "https://exyr.org/2026/solving-factorio-quality/"
                ],
                "cmd_version": "curl 8.14.1 (x86_64-pc-linux-gnu)",
                "end_ts": "2026-09-30T10:06:24.324746+00:00",
                "index_texts": null,
                "output": "Solving Factorio Quality \u2212\n    Exyr.org",
                "pwd": "/data/archive/1790762673.828632",
                "schema": "ArchiveResult",
                "start_ts": "2026-09-30T10:06:24.189518+00:00",
                "status": "succeeded"
            }
        ],
        "wget": [
            {
                "cmd": [
                    "wget",
                    "--no-verbose",
                    "--adjust-extension",
                    "--convert-links",
                    "--force-directories",
                    "--span-hosts",
                    "--no-parent",
                    "-e",
                    "robots=off",
                    "--reject=js,mjs,woff,woff2,ttf,otf,eot,mp4,m4v,mov,webm,ogv,mkv,avi,flv",
                    "--timeout=120",
                    "--restrict-file-names=windows",
                    "--page-requisites",
                    "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) wget/GNU Wget 1.21.3",
                    "--compression=auto",
                    "--timestamping",
                    "https://exyr.org/2026/solving-factorio-quality/"
                ],
                "cmd_version": "GNU Wget 1.21.3",
                "end_ts": "2026-09-30T10:06:24.185116+00:00",
                "index_texts": null,
                "output": "exyr.org/2026/solving-factorio-quality/index.html",
                "pwd": "/data/archive/1790762673.828632",
                "schema": "ArchiveResult",
                "start_ts": "2026-09-30T10:06:21.254135+00:00",
                "status": "succeeded"
            }
        ]
    },
    "icons": null,
    "is_archived": true,
    "is_static": false,
    "latest": {
        "archive_org": null,
        "dom": null,
        "favicon": "favicon.ico",
        "git": null,
        "media": null,
        "pdf": null,
        "screenshot": null,
        "singlefile": null,
        "title": "Solving Factorio Quality \u2212\n    Exyr.org",
        "warc": null,
        "wget": "exyr.org/2026/solving-factorio-quality/index.html"
    },
    "link_dir": "/data/archive/1790762673.828632",
    "newest_archive_date": "2026-09-30T10:06:27.901917+00:00",
    "num_failures": 0,
    "num_outputs": 6,
    "oldest_archive_date": "2026-09-30T10:06:19.777789+00:00",
    "path": "/2026/solving-factorio-quality/",
    "schema": "Link",
    "scheme": "https",
    "snapshot_id": "65364222-aa96-45ad-bc40-25ef8003b83c",
    "sources": [
        "/data/sources/1790762673-import.txt"
    ],
    "tags": null,
    "tags_str": "",
    "timestamp": "1790762673.828632",
    "title": "Solving Factorio Quality \u2212\n    Exyr.org",
    "updated": "2026-09-30T10:06:19.777229+00:00",
    "updated_date": "2026-09-30 10:06",
    "url": "https://exyr.org/2026/solving-factorio-quality/"
}