[data] full codex catalog + data validator
Transcribe the §2-§6 codex into machine-readable canon and add the validator that keeps five parallel lanes from drowning in dangling ids. - validator (28 tests): recipes point back at their machine, every item/tech/ commission reference resolves, ids unique + kebab-case, colors hex, footprints >=1x1, plus a reachability pass proving MELT is craftable from raw ore. - items 9->36, machines 7->21, recipes 4->35, tech 0->17, commissions 1->10. - mosh rewired to canon (gop-crate -> melt + reclaimed anchor slab); all seed recipe ids preserved so nothing dangles mid-round. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
1a769e6cbd
commit
f015a66283
@ -1,4 +1,41 @@
|
||||
[
|
||||
{ "id": "first-taste", "flavor": "One (1) unit of MELT. We hear you make it fresh. No questions will be answered.",
|
||||
"wants": { "melt": 1 }, "rewardBandwidth": 50 }
|
||||
{ "id": "first-taste",
|
||||
"flavor": "One (1) unit of MELT. We hear you make it fresh. No questions will be answered.",
|
||||
"wants": { "melt": 1 }, "rewardBandwidth": 50 },
|
||||
|
||||
{ "id": "dust-allowance",
|
||||
"flavor": "12 STATIC CANISTERS. Brine grade, not display grade. Our previous supplier shipped display grade and we no longer speak of them.",
|
||||
"wants": { "static-canister": 12 }, "rewardBandwidth": 120 },
|
||||
|
||||
{ "id": "patio-resurfacing",
|
||||
"flavor": "48 MACROBLOCK BRICKS for a patio. Client has been advised the patio will not be flat. Client insists the patio has never been flat. Proceed.",
|
||||
"wants": { "macroblock-bricks": 48 }, "rewardBandwidth": 220 },
|
||||
|
||||
{ "id": "haunted-doorway",
|
||||
"flavor": "9 RINGING HALOS, hung not stacked. They are for a doorway. The doorway is for nothing. Do not ask what comes through it; it is already through.",
|
||||
"wants": { "ringing-halos": 9, "gamut-paint-rec709": 2 }, "rewardBandwidth": 300 },
|
||||
|
||||
{ "id": "house-gray-audit",
|
||||
"flavor": "30 LUMA BARS and 4 GAMUT PAINT: REC709. Deliver to Compliance. This is a legitimate order from a legitimate buyer and we are legally required to state that we are not The Correction.",
|
||||
"wants": { "luma-bars": 30, "gamut-paint-rec709": 4 }, "rewardBandwidth": 260 },
|
||||
|
||||
{ "id": "wrong-on-purpose",
|
||||
"flavor": "16 JUDDER CAMS installed in 4 BLOOM CONCENTRATE canisters. We want the movement to arrive slightly after the thing that is moving. Yes. That. Exactly that.",
|
||||
"wants": { "judder-cams": 16, "bloom-concentrate": 4 }, "rewardBandwidth": 480 },
|
||||
|
||||
{ "id": "vaporwave-sunset",
|
||||
"flavor": "1 MELTED ANCHOR SLAB + 3 MOSQUITO SWARMS (live) + 40 bbl CHROMA SLURRY (4:2:0) = 1 VAPORWAVE SUNSET. Deliver before the beat drops.",
|
||||
"wants": { "melt": 1, "anchor-slab": 1, "mosquito-swarm": 3, "chroma-420": 40 }, "rewardBandwidth": 700 },
|
||||
|
||||
{ "id": "gibbs-wraith-willingly",
|
||||
"flavor": "One (1) GIBBS WRAITH, willingly.",
|
||||
"wants": { "gibbs-wraith": 1 }, "rewardBandwidth": 900 },
|
||||
|
||||
{ "id": "sky-blue-mislabeled",
|
||||
"flavor": "600 MACROBLOCK BRICKS, sky-blue, MISLABELED REC2020. Purpose: none of your business.",
|
||||
"wants": { "macroblock-bricks": 600, "gamut-paint-rec2020": 6 }, "rewardBandwidth": 2400 },
|
||||
|
||||
{ "id": "quarterly-sins",
|
||||
"flavor": "Quarterly order: 200 MELT, 40 EXPLOSIVE HITS, 12 MOIRE CRYSTAL. Ship the HITS last and ship them running. Our loading dock is a formality and our insurer knows this.",
|
||||
"wants": { "melt": 200, "explosive-hits": 40, "moire-crystal": 12 }, "rewardBandwidth": 3200 }
|
||||
]
|
||||
|
||||
@ -1,11 +1,43 @@
|
||||
[
|
||||
{ "id": "mdat-ore", "name": "MDAT ORE", "codex": "2:mdat-ore", "tier": 0, "color": "#1a1a22" },
|
||||
{ "id": "bayer-grit", "name": "BAYER GRIT", "codex": "2:bayer-grit", "tier": 0, "color": "#3fbf5f" },
|
||||
{ "id": "field-splinters", "name": "FIELD SPLINTERS", "codex": "2:field-splinters", "tier": 0, "color": "#6ef58a" },
|
||||
{ "id": "silver-frames", "name": "SILVER FRAMES", "codex": "2:silver-frames", "tier": 0, "color": "#b0803a" },
|
||||
|
||||
{ "id": "luma-bars", "name": "LUMA BARS", "codex": "2:luma-bars", "tier": 1, "color": "#c0c0c0" },
|
||||
{ "id": "chroma-slurry", "name": "CHROMA SLURRY", "codex": "2:chroma-slurry", "tier": 1, "color": "#ff3fd4" },
|
||||
{ "id": "chroma-422", "name": "CHROMA SLURRY (4:2:2)", "codex": "2:chroma-slurry", "tier": 1, "color": "#e03fc0" },
|
||||
{ "id": "chroma-420", "name": "CHROMA SLURRY (4:2:0)", "codex": "2:chroma-slurry", "tier": 1, "color": "#b83fa8" },
|
||||
{ "id": "color-plate", "name": "COLOR PLATE", "codex": "4:demosaicer", "tier": 1, "color": "#d8b0ff" },
|
||||
{ "id": "coefficient-pack", "name": "COEFFICIENT PACK", "codex": "2:coefficient-packs", "tier": 1, "color": "#3fffe0" },
|
||||
{ "id": "hf-dust", "name": "HF DUST", "codex": "2:hf-dust", "tier": 1, "color": "#e8e0ff" },
|
||||
{ "id": "mv-flux", "name": "MOTION VECTOR FLUID", "codex": "2:motion-vector-fluid", "tier": 1, "color": "#3fd8ff" },
|
||||
{ "id": "v-hold-roll", "name": "V-HOLD ROLL", "codex": "1:the-broadcast-shelf", "tier": 1, "color": "#8fdc7a" },
|
||||
|
||||
{ "id": "anchor-slab", "name": "I-FRAME (ANCHOR SLAB)", "codex": "2:i-frame", "tier": 1, "color": "#fff2a0" },
|
||||
{ "id": "delta-wafer", "name": "P-FRAME (DELTA WAFER)", "codex": "2:p-frame", "tier": 1, "color": "#7fb0ff" },
|
||||
{ "id": "janus-wafer", "name": "B-FRAME (JANUS WAFER)", "codex": "2:b-frame", "tier": 1, "color": "#b08fff" },
|
||||
{ "id": "gop-crate", "name": "GOP CRATE", "codex": "2:gop-crate", "tier": 1, "color": "#a07840" },
|
||||
{ "id": "melt", "name": "MELT", "codex": "3:melt", "tier": 2, "color": "#ff7a3f" }
|
||||
|
||||
{ "id": "melt", "name": "MELT", "codex": "3:melt", "tier": 2, "color": "#ff7a3f" },
|
||||
{ "id": "bloom-concentrate", "name": "BLOOM CONCENTRATE", "codex": "3:bloom-concentrate", "tier": 2, "color": "#ffb0e0" },
|
||||
{ "id": "macroblock-bricks", "name": "MACROBLOCK BRICKS", "codex": "3:macroblock-bricks", "tier": 2, "color": "#6f8fa0" },
|
||||
{ "id": "ringing-halos", "name": "RINGING HALOS", "codex": "3:ringing-halos", "tier": 2, "color": "#cfe8ff" },
|
||||
{ "id": "static-canister", "name": "STATIC CANISTER", "codex": "3:static-canisters", "tier": 2, "color": "#d8d8d8" },
|
||||
{ "id": "moire-crystal", "name": "MOIRE CRYSTAL", "codex": "3:moire-crystal", "tier": 2, "color": "#7fffd4" },
|
||||
{ "id": "false-color-speckle", "name": "FALSE-COLOR SPECKLE", "codex": "3:false-color-speckle", "tier": 2, "color": "#d940ff" },
|
||||
{ "id": "judder-cams", "name": "JUDDER CAMS", "codex": "3:judder-cams", "tier": 2, "color": "#9aa0a8" },
|
||||
{ "id": "explosive-hits", "name": "EXPLOSIVE HITS", "codex": "3:explosive-hits", "tier": 2, "color": "#ff6a00" },
|
||||
{ "id": "gamut-paint-rec709", "name": "GAMUT PAINT: REC709", "codex": "3:gamut-paints", "tier": 2, "color": "#8a8f96" },
|
||||
{ "id": "gamut-paint-dci-p3", "name": "GAMUT PAINT: DCI-P3", "codex": "3:gamut-paints", "tier": 2, "color": "#ff2f6a" },
|
||||
{ "id": "gamut-paint-rec2020", "name": "GAMUT PAINT: REC2020", "codex": "3:gamut-paints", "tier": 2, "color": "#00ff5a" },
|
||||
|
||||
{ "id": "analog-pack", "name": "ANALOG PACK", "codex": "2:science-packs", "tier": 2, "color": "#57d96b" },
|
||||
{ "id": "spatial-pack", "name": "SPATIAL PACK", "codex": "2:science-packs", "tier": 2, "color": "#4fa8ff" },
|
||||
{ "id": "temporal-pack", "name": "TEMPORAL PACK", "codex": "2:science-packs", "tier": 2, "color": "#ffb43f" },
|
||||
{ "id": "container-pack", "name": "CONTAINER PACK", "codex": "2:science-packs", "tier": 2, "color": "#c07fff" },
|
||||
{ "id": "cinema-pack", "name": "CINEMA PACK", "codex": "2:science-packs", "tier": 3, "color": "#fdfdff" },
|
||||
|
||||
{ "id": "mosquito-swarm", "name": "MOSQUITO SWARM (LIVE)", "codex": "5:mosquito-swarms", "tier": 2, "color": "#d0ffb0" },
|
||||
{ "id": "gibbs-wraith", "name": "GIBBS WRAITH", "codex": "5:gibbs-wraiths", "tier": 2, "color": "#bfe0ff" }
|
||||
]
|
||||
|
||||
@ -1,16 +1,60 @@
|
||||
[
|
||||
{ "id": "seam-extractor", "name": "SEAM EXTRACTOR", "codex": "4:seam-extractor", "kind": "extractor",
|
||||
"footprint": { "x": 2, "y": 2 }, "recipes": ["extract-mdat"], "powerDraw": 2, "asset": "seam-extractor" },
|
||||
"footprint": { "x": 2, "y": 2 },
|
||||
"recipes": ["extract-mdat", "extract-bayer-grit", "extract-field-splinters", "extract-silver-frames"],
|
||||
"powerDraw": 2, "asset": "seam-extractor" },
|
||||
|
||||
{ "id": "belt", "name": "STREAM BELT", "codex": "4:belts", "kind": "belt",
|
||||
"footprint": { "x": 1, "y": 1 }, "recipes": [], "powerDraw": 0, "beltSpeed": 2, "asset": "belt" },
|
||||
{ "id": "lane-splitter", "name": "LANE SPLITTER", "codex": "4:demuxer", "kind": "splitter",
|
||||
"footprint": { "x": 1, "y": 1 }, "recipes": [], "powerDraw": 1, "asset": "lane-splitter" },
|
||||
{ "id": "buffer-tank", "name": "BUFFER TANK", "codex": "2:buffer-charge", "kind": "buffer",
|
||||
"footprint": { "x": 2, "y": 2 }, "recipes": [], "powerDraw": 0, "asset": "buffer-tank" },
|
||||
|
||||
{ "id": "demuxer", "name": "DEMUXER", "codex": "4:demuxer", "kind": "crafter",
|
||||
"footprint": { "x": 2, "y": 2 }, "recipes": ["demux-ore"], "powerDraw": 3, "asset": "demuxer" },
|
||||
{ "id": "demosaicer", "name": "DEMOSAICER", "codex": "4:demosaicer", "kind": "crafter",
|
||||
"footprint": { "x": 3, "y": 2 }, "recipes": ["demosaic-rggb", "demosaic-vng4", "demosaic-stripes"],
|
||||
"powerDraw": 5, "asset": "demosaicer" },
|
||||
{ "id": "dct-press", "name": "DCT PRESS", "codex": "4:dct-press", "kind": "crafter",
|
||||
"footprint": { "x": 3, "y": 3 }, "recipes": ["press-color-plate", "press-anchor-slab"],
|
||||
"powerDraw": 8, "asset": "dct-press" },
|
||||
{ "id": "quantizer", "name": "QUANTIZER", "codex": "4:quantizer", "kind": "crafter",
|
||||
"footprint": { "x": 2, "y": 2 }, "recipes": ["quantize"], "powerDraw": 4, "asset": "quantizer" },
|
||||
"footprint": { "x": 2, "y": 2 }, "recipes": ["quantize", "quantize-crime", "skim-ringing"],
|
||||
"powerDraw": 4, "asset": "quantizer" },
|
||||
{ "id": "subsampler", "name": "SUBSAMPLER", "codex": "4:subsampler", "kind": "crafter",
|
||||
"footprint": { "x": 2, "y": 3 }, "recipes": ["subsample-422", "subsample-420", "pan-speckle"],
|
||||
"powerDraw": 4, "asset": "subsampler" },
|
||||
{ "id": "mv-extractor", "name": "MV EXTRACTOR", "codex": "4:mv-extractor-and-p-caster", "kind": "crafter",
|
||||
"footprint": { "x": 2, "y": 2 }, "recipes": ["extract-motion"], "powerDraw": 5, "asset": "mv-extractor" },
|
||||
{ "id": "p-caster", "name": "P-CASTER", "codex": "4:mv-extractor-and-p-caster", "kind": "crafter",
|
||||
"footprint": { "x": 2, "y": 2 }, "recipes": ["cast-delta-wafer", "cast-janus-wafer"],
|
||||
"powerDraw": 4, "asset": "p-caster" },
|
||||
{ "id": "gop-assembler", "name": "GOP ASSEMBLER", "codex": "4:gop-assembler", "kind": "crafter",
|
||||
"footprint": { "x": 3, "y": 2 }, "recipes": ["assemble-gop", "assemble-gop-ibbp", "assemble-gop-i-only"],
|
||||
"powerDraw": 6, "asset": "gop-assembler" },
|
||||
{ "id": "mosh-reactor", "name": "MOSH REACTOR", "codex": "4:mosh-reactor", "kind": "crafter",
|
||||
"footprint": { "x": 3, "y": 3 }, "recipes": ["mosh"], "powerDraw": 8, "asset": "mosh-reactor" },
|
||||
{ "id": "bloom-duplicator", "name": "P-FRAME DUPLICATOR", "codex": "4:bloom-loop", "kind": "crafter",
|
||||
"footprint": { "x": 1, "y": 1 }, "recipes": ["bloom-grade-3"], "powerDraw": 6, "asset": "bloom-duplicator" },
|
||||
{ "id": "field-loom", "name": "FIELD LOOM", "codex": "4:field-loom", "kind": "crafter",
|
||||
"footprint": { "x": 3, "y": 2 }, "recipes": ["weave-fields", "loom-vhold-roll"],
|
||||
"powerDraw": 5, "asset": "field-loom" },
|
||||
{ "id": "telecine-puller", "name": "TELECINE PULLER", "codex": "4:telecine-puller", "kind": "crafter",
|
||||
"footprint": { "x": 2, "y": 2 }, "recipes": ["mill-judder"], "powerDraw": 4, "asset": "telecine-puller" },
|
||||
{ "id": "hex-splicer", "name": "HEX SPLICER", "codex": "4:hex-splicer", "kind": "crafter",
|
||||
"footprint": { "x": 3, "y": 3 }, "recipes": ["splice-offsets"], "powerDraw": 12, "asset": "hex-splicer" },
|
||||
{ "id": "artifact-bottler", "name": "ARTIFACT BOTTLER", "codex": "2:science-packs", "kind": "crafter",
|
||||
"footprint": { "x": 2, "y": 2 },
|
||||
"recipes": ["can-static", "mix-rec709", "mix-dci-p3", "mix-rec2020",
|
||||
"bottle-analog-pack", "bottle-spatial-pack", "bottle-temporal-pack"],
|
||||
"powerDraw": 4, "asset": "artifact-bottler" },
|
||||
|
||||
{ "id": "decode-asic", "name": "DECODE ASIC", "codex": "4:decode-asic", "kind": "power",
|
||||
"footprint": { "x": 2, "y": 2 }, "recipes": [], "powerDraw": 0, "powerGen": 20, "asset": "decode-asic" },
|
||||
{ "id": "software-decoder", "name": "SOFTWARE DECODER", "codex": "4:software-decoder", "kind": "power",
|
||||
"footprint": { "x": 2, "y": 2 }, "recipes": [], "powerDraw": 0, "powerGen": 45, "asset": "software-decoder" },
|
||||
|
||||
{ "id": "shipper", "name": "SCREEN UPLINK", "codex": "7:the-muxer", "kind": "shipper",
|
||||
"footprint": { "x": 2, "y": 2 }, "recipes": [], "powerDraw": 5, "asset": "shipper" }
|
||||
]
|
||||
|
||||
@ -1,6 +1,87 @@
|
||||
[
|
||||
{ "id": "extract-mdat", "machine": "seam-extractor", "inputs": {}, "outputs": { "mdat-ore": 1 }, "ticks": 30, "bandwidth": 2 },
|
||||
{ "id": "demux-ore", "machine": "demuxer", "inputs": { "mdat-ore": 2 }, "outputs": { "luma-bars": 1, "chroma-slurry": 1 }, "ticks": 45, "bandwidth": 3 },
|
||||
{ "id": "quantize", "machine": "quantizer", "inputs": { "luma-bars": 1 }, "outputs": { "coefficient-pack": 1, "hf-dust": 1 }, "ticks": 30, "bandwidth": -6 },
|
||||
{ "id": "mosh", "machine": "mosh-reactor", "inputs": { "coefficient-pack": 2, "chroma-slurry": 1 }, "outputs": { "melt": 1, "anchor-slab": 1 }, "ticks": 90, "bandwidth": 10 }
|
||||
{ "id": "extract-bayer-grit", "machine": "seam-extractor", "inputs": {}, "outputs": { "bayer-grit": 1 }, "ticks": 45, "bandwidth": 3 },
|
||||
{ "id": "extract-field-splinters", "machine": "seam-extractor", "inputs": {}, "outputs": { "field-splinters": 1 }, "ticks": 40, "bandwidth": 3 },
|
||||
{ "id": "extract-silver-frames", "machine": "seam-extractor", "inputs": {}, "outputs": { "silver-frames": 1 }, "ticks": 60, "bandwidth": 4 },
|
||||
|
||||
{ "id": "demux-ore", "machine": "demuxer", "inputs": { "mdat-ore": 3 },
|
||||
"outputs": { "luma-bars": 2, "chroma-slurry": 1, "mv-flux": 1 }, "ticks": 45, "bandwidth": 3 },
|
||||
|
||||
{ "id": "demosaic-rggb", "machine": "demosaicer", "inputs": { "bayer-grit": 4 },
|
||||
"outputs": { "color-plate": 1 }, "ticks": 60, "bandwidth": 4 },
|
||||
{ "id": "demosaic-vng4", "machine": "demosaicer", "inputs": { "bayer-grit": 6 },
|
||||
"outputs": { "color-plate": 1, "false-color-speckle": 2 }, "ticks": 75, "bandwidth": 5 },
|
||||
{ "id": "demosaic-stripes", "machine": "demosaicer", "inputs": { "bayer-grit": 4, "field-splinters": 1 },
|
||||
"outputs": { "moire-crystal": 1 }, "ticks": 90, "bandwidth": 6 },
|
||||
|
||||
{ "id": "press-color-plate", "machine": "dct-press", "inputs": { "color-plate": 1 },
|
||||
"outputs": { "coefficient-pack": 3 }, "ticks": 45, "bandwidth": 4 },
|
||||
{ "id": "press-anchor-slab", "machine": "dct-press", "inputs": { "coefficient-pack": 6, "chroma-420": 1 },
|
||||
"outputs": { "anchor-slab": 1 }, "ticks": 120, "bandwidth": 12 },
|
||||
|
||||
{ "id": "quantize", "machine": "quantizer", "inputs": { "luma-bars": 1 },
|
||||
"outputs": { "coefficient-pack": 1, "hf-dust": 1 }, "ticks": 30, "bandwidth": -6 },
|
||||
{ "id": "quantize-crime", "machine": "quantizer", "inputs": { "coefficient-pack": 1 },
|
||||
"outputs": { "macroblock-bricks": 2, "hf-dust": 2 }, "ticks": 20, "bandwidth": -18 },
|
||||
{ "id": "skim-ringing", "machine": "quantizer", "inputs": { "coefficient-pack": 3 },
|
||||
"outputs": { "ringing-halos": 1, "hf-dust": 1 }, "ticks": 40, "bandwidth": -4 },
|
||||
|
||||
{ "id": "subsample-422", "machine": "subsampler", "inputs": { "chroma-slurry": 2 },
|
||||
"outputs": { "chroma-422": 1 }, "ticks": 24, "bandwidth": -4 },
|
||||
{ "id": "subsample-420", "machine": "subsampler", "inputs": { "chroma-422": 2 },
|
||||
"outputs": { "chroma-420": 1 }, "ticks": 24, "bandwidth": -8 },
|
||||
{ "id": "pan-speckle", "machine": "subsampler", "inputs": { "hf-dust": 40 },
|
||||
"outputs": { "false-color-speckle": 1 }, "ticks": 200, "bandwidth": 8 },
|
||||
|
||||
{ "id": "extract-motion", "machine": "mv-extractor", "inputs": { "gop-crate": 1 },
|
||||
"outputs": { "mv-flux": 6, "hf-dust": 1 }, "ticks": 60, "bandwidth": 5 },
|
||||
{ "id": "cast-delta-wafer", "machine": "p-caster", "inputs": { "mv-flux": 2 },
|
||||
"outputs": { "delta-wafer": 1 }, "ticks": 15, "bandwidth": 2 },
|
||||
{ "id": "cast-janus-wafer", "machine": "p-caster", "inputs": { "mv-flux": 3, "delta-wafer": 1 },
|
||||
"outputs": { "janus-wafer": 1 }, "ticks": 30, "bandwidth": 4 },
|
||||
|
||||
{ "id": "assemble-gop", "machine": "gop-assembler", "inputs": { "anchor-slab": 1, "delta-wafer": 6 },
|
||||
"outputs": { "gop-crate": 1 }, "ticks": 60, "bandwidth": 6 },
|
||||
{ "id": "assemble-gop-ibbp", "machine": "gop-assembler",
|
||||
"inputs": { "anchor-slab": 1, "delta-wafer": 3, "janus-wafer": 2 },
|
||||
"outputs": { "gop-crate": 2 }, "ticks": 75, "bandwidth": 8 },
|
||||
{ "id": "assemble-gop-i-only", "machine": "gop-assembler", "inputs": { "anchor-slab": 8 },
|
||||
"outputs": { "gop-crate": 1 }, "ticks": 45, "bandwidth": 24 },
|
||||
|
||||
{ "id": "mosh", "machine": "mosh-reactor", "inputs": { "gop-crate": 1 },
|
||||
"outputs": { "melt": 3, "anchor-slab": 1 }, "ticks": 90, "bandwidth": 10 },
|
||||
|
||||
{ "id": "bloom-grade-3", "machine": "bloom-duplicator", "inputs": { "delta-wafer": 4, "mv-flux": 2 },
|
||||
"outputs": { "bloom-concentrate": 1 }, "ticks": 60, "bandwidth": 8 },
|
||||
|
||||
{ "id": "weave-fields", "machine": "field-loom", "inputs": { "field-splinters": 2 },
|
||||
"outputs": { "luma-bars": 1 }, "ticks": 30, "bandwidth": 3 },
|
||||
{ "id": "loom-vhold-roll", "machine": "field-loom", "inputs": { "field-splinters": 6 },
|
||||
"outputs": { "v-hold-roll": 1, "static-canister": 1 }, "ticks": 75, "bandwidth": 6 },
|
||||
|
||||
{ "id": "mill-judder", "machine": "telecine-puller", "inputs": { "silver-frames": 3 },
|
||||
"outputs": { "judder-cams": 1, "field-splinters": 2 }, "ticks": 60, "bandwidth": 5 },
|
||||
|
||||
{ "id": "splice-offsets", "machine": "hex-splicer", "inputs": { "gop-crate": 1 },
|
||||
"outputs": { "explosive-hits": 4, "hf-dust": 2 }, "ticks": 45, "bandwidth": 20 },
|
||||
|
||||
{ "id": "can-static", "machine": "artifact-bottler", "inputs": { "hf-dust": 4 },
|
||||
"outputs": { "static-canister": 1 }, "ticks": 30, "bandwidth": 3 },
|
||||
{ "id": "mix-rec709", "machine": "artifact-bottler", "inputs": { "chroma-slurry": 2, "luma-bars": 1 },
|
||||
"outputs": { "gamut-paint-rec709": 1 }, "ticks": 30, "bandwidth": 3 },
|
||||
{ "id": "mix-dci-p3", "machine": "artifact-bottler", "inputs": { "chroma-slurry": 3, "false-color-speckle": 1 },
|
||||
"outputs": { "gamut-paint-dci-p3": 1 }, "ticks": 45, "bandwidth": 5 },
|
||||
{ "id": "mix-rec2020", "machine": "artifact-bottler",
|
||||
"inputs": { "chroma-slurry": 4, "false-color-speckle": 3, "moire-crystal": 1 },
|
||||
"outputs": { "gamut-paint-rec2020": 1 }, "ticks": 90, "bandwidth": 12 },
|
||||
|
||||
{ "id": "bottle-analog-pack", "machine": "artifact-bottler",
|
||||
"inputs": { "field-splinters": 2, "v-hold-roll": 1, "static-canister": 1 },
|
||||
"outputs": { "analog-pack": 1 }, "ticks": 60, "bandwidth": 6 },
|
||||
{ "id": "bottle-spatial-pack", "machine": "artifact-bottler",
|
||||
"inputs": { "macroblock-bricks": 2, "ringing-halos": 1, "chroma-slurry": 2 },
|
||||
"outputs": { "spatial-pack": 1 }, "ticks": 60, "bandwidth": 6 },
|
||||
{ "id": "bottle-temporal-pack", "machine": "artifact-bottler",
|
||||
"inputs": { "melt": 2, "bloom-concentrate": 1, "judder-cams": 1 },
|
||||
"outputs": { "temporal-pack": 1 }, "ticks": 90, "bandwidth": 8 }
|
||||
]
|
||||
|
||||
@ -1 +1,37 @@
|
||||
[]
|
||||
[
|
||||
{ "id": "disc-artifact-bottling", "era": "disc", "cost": { "analog-pack": 10 },
|
||||
"unlocks": ["artifact-bottler", "can-static"] },
|
||||
{ "id": "disc-chroma-subsampling", "era": "disc", "cost": { "analog-pack": 10 },
|
||||
"unlocks": ["subsampler", "subsample-422"] },
|
||||
{ "id": "disc-macroblock-quarrying", "era": "disc", "cost": { "analog-pack": 15 },
|
||||
"unlocks": ["quantize-crime"] },
|
||||
{ "id": "disc-ringing-skim", "era": "disc", "cost": { "analog-pack": 10, "spatial-pack": 15 },
|
||||
"unlocks": ["skim-ringing"] },
|
||||
{ "id": "disc-subsampling-420", "era": "disc", "cost": { "analog-pack": 20, "spatial-pack": 15 },
|
||||
"unlocks": ["subsample-420", "press-anchor-slab"] },
|
||||
{ "id": "disc-mosh-reactor", "era": "disc", "cost": { "analog-pack": 20, "spatial-pack": 10 },
|
||||
"unlocks": ["mosh-reactor", "mosh"] },
|
||||
{ "id": "disc-gamut-paints", "era": "disc", "cost": { "spatial-pack": 20 },
|
||||
"unlocks": ["mix-rec709", "mix-dci-p3"] },
|
||||
{ "id": "disc-gop-cadence-ibbp", "era": "disc", "cost": { "spatial-pack": 25, "temporal-pack": 10 },
|
||||
"unlocks": ["cast-janus-wafer", "assemble-gop-ibbp"] },
|
||||
{ "id": "disc-i-only-cadence", "era": "disc", "cost": { "spatial-pack": 30, "temporal-pack": 20 },
|
||||
"unlocks": ["assemble-gop-i-only"] },
|
||||
|
||||
{ "id": "stream-field-loom", "era": "stream", "cost": { "analog-pack": 20 },
|
||||
"unlocks": ["field-loom", "weave-fields", "loom-vhold-roll"] },
|
||||
{ "id": "stream-telecine", "era": "stream", "cost": { "analog-pack": 25, "temporal-pack": 15 },
|
||||
"unlocks": ["telecine-puller", "mill-judder"] },
|
||||
{ "id": "stream-software-decoding", "era": "stream", "cost": { "analog-pack": 20, "temporal-pack": 20 },
|
||||
"unlocks": ["software-decoder"] },
|
||||
{ "id": "stream-demosaic-vng4", "era": "stream", "cost": { "spatial-pack": 30 },
|
||||
"unlocks": ["demosaic-vng4", "pan-speckle"] },
|
||||
{ "id": "stream-bloom-loops", "era": "stream", "cost": { "spatial-pack": 20, "temporal-pack": 30 },
|
||||
"unlocks": ["bloom-duplicator", "bloom-grade-3"] },
|
||||
{ "id": "stream-optical-flow", "era": "stream", "cost": { "temporal-pack": 25 },
|
||||
"unlocks": ["mv-extractor", "extract-motion"] },
|
||||
{ "id": "stream-laser-primaries", "era": "stream", "cost": { "spatial-pack": 40, "temporal-pack": 20 },
|
||||
"unlocks": ["mix-rec2020"] },
|
||||
{ "id": "stream-hex-splicing", "era": "stream", "cost": { "container-pack": 25, "temporal-pack": 25 },
|
||||
"unlocks": ["hex-splicer", "splice-offsets"] }
|
||||
]
|
||||
|
||||
@ -69,3 +69,72 @@ to a codex section. NOTES list what you invented beyond the codex.
|
||||
### Round N — YYYY-MM-DD — [your model]
|
||||
SHIPPED: / DECISIONS: / INVENTED: / BLOCKED/BROKEN: / CONTRACT REQUEST: / NEXT:
|
||||
```
|
||||
|
||||
### Round 1 — 2026-07-17 — Opus 4.8
|
||||
|
||||
SHIPPED:
|
||||
- Validator `src/sim/data-validate.test.ts`, 28 tests. Fault-injected a recipe with a
|
||||
dangling item + a machine that doesn't list it — both caught by id and named. It is
|
||||
not a test that only knows how to pass.
|
||||
- items 9 → 36, machines 7 → 21, recipes 4 → 35, tech 0 → 17, commissions 1 → 10.
|
||||
- Verified: `tsc` clean, `vitest run` 52/52 green (LANE-SCREEN's 24 included), and the
|
||||
live dev build boots with no console errors. All 21 machines appear in LANE-UI's
|
||||
build bar with zero code changes from them.
|
||||
|
||||
DECISIONS:
|
||||
- **All four seed recipe ids kept alive** (`extract-mdat`, `demux-ore`, `quantize`,
|
||||
`mosh`) so nothing dangles for a lane mid-round. `mosh` keeps its id but is rewired
|
||||
to canon: `gop-crate 1 → melt 3 + anchor-slab 1`. The winched-out I-frame is
|
||||
recovered (§4 mosh reactor: "recoverable — sell it!"), so slabs circulate and delta
|
||||
wafers are what actually burns. Net economy: 6 wafers → 3 melt.
|
||||
- `quantize` left at the seed's exact ratio so the M1 chain stays easy to build.
|
||||
- **M1 chain is longer than the masterplan says — please rule on this.** My orders
|
||||
spell out `slab + wafers → gop-crate` and `gop-crate → mosh`, but MASTERPLAN M1
|
||||
describes extractor → belts → demuxer → quantizer → mosh → shipper (seven machines).
|
||||
Those can't both be true: reaching a crate needs dct-press (slab), p-caster (wafers)
|
||||
and gop-assembler in between. I followed the orders since they're the more specific
|
||||
and more recent instruction, and all three run fine as generic crafters. If M1 must
|
||||
stay at seven machines, say so and I'll add a shortcut `mosh-raw` recipe next round
|
||||
rather than weaken the canon path.
|
||||
- Machine params that the codex describes as dials (quantizer Q, subsampler grade taps,
|
||||
GOP cadence templates) are modelled as **separate recipes per setting**, not as a
|
||||
per-entity parameter. Cheapest thing that works against the current contract; if SIM
|
||||
ever wants a real dial, that's a contract change, not a data change.
|
||||
- Two absurd-but-honest ratios, as ordered: `assemble-gop-i-only` burns 8 premium slabs
|
||||
for 1 crate (I-only genuinely is the worst cadence — the joke balances itself, nobody
|
||||
sane builds it), and `pan-speckle` takes 40 HF DUST for 1 speckle (§3 says "panned
|
||||
like gold", so it's priced like gold panning). Both give hf-dust real sinks.
|
||||
|
||||
INVENTED (codex is silent; written in its voice — kill any of these if you disagree):
|
||||
- `press-anchor-slab` on the DCT PRESS — the press run in reverse is an IDCT, i.e. a
|
||||
complete picture. Consistent with the field loom and telecine puller, which the codex
|
||||
explicitly runs backwards. This is where I-frames come from.
|
||||
- Items `color-plate` (the demosaicer's "full-color sheet", §4) and `v-hold-roll`
|
||||
(bottled from the §1 Broadcast Shelf v-hold faults, so the ANALOG PACK's "combing +
|
||||
v-hold roll + static" triptych maps to three real items).
|
||||
- Machines `artifact-bottler` (§2 calls the packs "a bottled artifact triptych" but
|
||||
names no bottling plant) and `lane-splitter` (contract has `kind: 'splitter'` and
|
||||
belts need routing; the codex only ever calls the DEMUXER "the first splitter").
|
||||
|
||||
BLOCKED/BROKEN: nothing blocked. Known-incomplete, deliberately:
|
||||
- Bloom is one grade, not the graded 1-5 line §3 sells by intensity.
|
||||
- `container-pack` / `cinema-pack` / `mosquito-swarm` / `gibbs-wraith` exist as items
|
||||
with no recipe — forward canon, per orders. The validator's reachability test has an
|
||||
explicit FORWARD_CANON allowlist so these four can't hide a real dangling id; delete
|
||||
entries from it as recipes land.
|
||||
- `stream-hex-splicing` costs container-packs nothing currently produces. Intentional
|
||||
dead-end until the moov/container line exists (§6 heists).
|
||||
|
||||
CONTRACT REQUEST: `MachineDef.heatPerTick?: number` — the SOFTWARE DECODER is
|
||||
`kind: 'power'`, and power machines carry no recipes, but `RecipeDef.heat` is the only
|
||||
heat hook in the contract. So the one machine the codex defines *by* its heat (§4:
|
||||
"decodes ANYTHING, at any temperature") has no way to declare it. It's shipped as a
|
||||
plain 45-gen power machine and is currently strictly better than the ASIC, which is
|
||||
backwards. Alternative if you prefer no contract churn: let power machines run a
|
||||
recipe with empty outputs — but I'd rather not put `outputs: {}` in the data, and my
|
||||
validator rejects it today.
|
||||
|
||||
NEXT (Round 2 candidates, not self-assigned): bloom grades 1-5 as real items; the
|
||||
container line (moov shard / offset ribbon / sync key → CONTAINER PACK) which unblocks
|
||||
the hex splicer; mosquito traps so `vaporwave-sunset` becomes shippable; buffer-tank
|
||||
and muxer data; reel + broadcast era tech nodes.
|
||||
|
||||
287
fktry/src/sim/data-validate.test.ts
Normal file
287
fktry/src/sim/data-validate.test.ts
Normal file
@ -0,0 +1,287 @@
|
||||
/**
|
||||
* LANE-DATA territory (the one file this lane owns inside src/sim/).
|
||||
* Validates data/*.json against the contracts. It is the handshake that keeps five
|
||||
* parallel lanes from drowning in dangling ids: if a lane references a machine,
|
||||
* recipe, item or tech that data doesn't define, this test names it.
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { CommissionDef, ItemDef, MachineDef, MachineKind, RecipeDef, TechDef } from '../contracts';
|
||||
|
||||
import itemsJson from '../../data/items.json';
|
||||
import machinesJson from '../../data/machines.json';
|
||||
import recipesJson from '../../data/recipes.json';
|
||||
import techJson from '../../data/tech.json';
|
||||
import commissionsJson from '../../data/commissions.json';
|
||||
|
||||
const items = itemsJson as unknown as ItemDef[];
|
||||
const machines = machinesJson as unknown as MachineDef[];
|
||||
const recipes = recipesJson as unknown as RecipeDef[];
|
||||
const tech = techJson as unknown as TechDef[];
|
||||
const commissions = commissionsJson as unknown as CommissionDef[];
|
||||
|
||||
const KEBAB = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
||||
const CODEX_REF = /^[1-9]:[a-z0-9]+(-[a-z0-9]+)*$/;
|
||||
const HEX = /^#[0-9a-f]{6}$/;
|
||||
|
||||
const KINDS: MachineKind[] = ['extractor', 'crafter', 'belt', 'splitter', 'power', 'buffer', 'shipper'];
|
||||
const ERAS: TechDef['era'][] = ['reel', 'broadcast', 'disc', 'stream', 'fortress'];
|
||||
|
||||
const itemIds = new Set(items.map((i) => i.id));
|
||||
const machineIds = new Set(machines.map((m) => m.id));
|
||||
const recipeIds = new Set(recipes.map((r) => r.id));
|
||||
|
||||
/** Collect ids that appear more than once. */
|
||||
function duplicates(ids: string[]): string[] {
|
||||
const seen = new Set<string>();
|
||||
const dupes = new Set<string>();
|
||||
for (const id of ids) (seen.has(id) ? dupes : seen).add(id);
|
||||
return [...dupes];
|
||||
}
|
||||
|
||||
/** Item ids referenced by a count map that no item defines. */
|
||||
function unknownItems(counts: Record<string, number>): string[] {
|
||||
return Object.keys(counts).filter((id) => !itemIds.has(id));
|
||||
}
|
||||
|
||||
describe('ids and formatting', () => {
|
||||
it('every collection has unique ids', () => {
|
||||
expect(duplicates(items.map((i) => i.id))).toEqual([]);
|
||||
expect(duplicates(machines.map((m) => m.id))).toEqual([]);
|
||||
expect(duplicates(recipes.map((r) => r.id))).toEqual([]);
|
||||
expect(duplicates(tech.map((t) => t.id))).toEqual([]);
|
||||
expect(duplicates(commissions.map((c) => c.id))).toEqual([]);
|
||||
});
|
||||
|
||||
it('every id is kebab-case', () => {
|
||||
const bad = [...items, ...machines, ...recipes, ...tech, ...commissions]
|
||||
.map((d) => d.id)
|
||||
.filter((id) => !KEBAB.test(id));
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
|
||||
it('every item and machine points at a codex section', () => {
|
||||
const bad = [...items, ...machines]
|
||||
.filter((d) => !CODEX_REF.test(d.codex))
|
||||
.map((d) => `${d.id} -> "${d.codex}"`);
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('items', () => {
|
||||
it('every colour is a 6-digit lowercase hex', () => {
|
||||
const bad = items.filter((i) => !HEX.test(i.color)).map((i) => `${i.id} -> "${i.color}"`);
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
|
||||
it('every tier is 0..3', () => {
|
||||
const bad = items.filter((i) => !Number.isInteger(i.tier) || i.tier < 0 || i.tier > 3)
|
||||
.map((i) => `${i.id} -> ${i.tier}`);
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
|
||||
it('every item has a display name', () => {
|
||||
expect(items.filter((i) => !i.name.trim()).map((i) => i.id)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('machines', () => {
|
||||
it('every kind is a known MachineKind', () => {
|
||||
const bad = machines.filter((m) => !KINDS.includes(m.kind)).map((m) => `${m.id} -> "${m.kind}"`);
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
|
||||
it('every footprint is at least 1x1 in whole tiles', () => {
|
||||
const bad = machines.filter((m) => {
|
||||
const { x, y } = m.footprint ?? { x: 0, y: 0 };
|
||||
return !Number.isInteger(x) || !Number.isInteger(y) || x < 1 || y < 1;
|
||||
}).map((m) => m.id);
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
|
||||
it('every listed recipe exists and points back at the machine', () => {
|
||||
const missing: string[] = [];
|
||||
const mismatched: string[] = [];
|
||||
for (const m of machines) {
|
||||
for (const rid of m.recipes) {
|
||||
const r = recipes.find((x) => x.id === rid);
|
||||
if (!r) missing.push(`${m.id} lists unknown recipe "${rid}"`);
|
||||
else if (r.machine !== m.id) mismatched.push(`${m.id} lists "${rid}" but that recipe runs on "${r.machine}"`);
|
||||
}
|
||||
}
|
||||
expect(missing).toEqual([]);
|
||||
expect(mismatched).toEqual([]);
|
||||
});
|
||||
|
||||
it('belts declare a belt speed and power machines declare generation', () => {
|
||||
const bad = [
|
||||
...machines.filter((m) => m.kind === 'belt' && !(m.beltSpeed! > 0)).map((m) => `${m.id} has no beltSpeed`),
|
||||
...machines.filter((m) => m.kind === 'power' && !(m.powerGen! > 0)).map((m) => `${m.id} has no powerGen`),
|
||||
];
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
|
||||
it('every machine declares a non-negative idle draw and an asset key', () => {
|
||||
const bad = machines
|
||||
.filter((m) => !(m.powerDraw >= 0) || !KEBAB.test(m.asset))
|
||||
.map((m) => `${m.id} -> draw ${m.powerDraw}, asset "${m.asset}"`);
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('recipes', () => {
|
||||
it('every recipe runs on a machine that exists', () => {
|
||||
const bad = recipes.filter((r) => !machineIds.has(r.machine)).map((r) => `${r.id} -> "${r.machine}"`);
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
|
||||
it('every recipe is listed by the machine it runs on', () => {
|
||||
const orphans = recipes.filter((r) => {
|
||||
const m = machines.find((x) => x.id === r.machine);
|
||||
return m && !m.recipes.includes(r.id);
|
||||
}).map((r) => `${r.id} is not listed by ${r.machine}`);
|
||||
expect(orphans).toEqual([]);
|
||||
});
|
||||
|
||||
it('every input and output item exists', () => {
|
||||
const bad: string[] = [];
|
||||
for (const r of recipes) {
|
||||
for (const id of unknownItems(r.inputs)) bad.push(`${r.id} consumes unknown item "${id}"`);
|
||||
for (const id of unknownItems(r.outputs)) bad.push(`${r.id} produces unknown item "${id}"`);
|
||||
}
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
|
||||
it('every recipe produces something in whole positive counts', () => {
|
||||
const bad: string[] = [];
|
||||
for (const r of recipes) {
|
||||
if (Object.keys(r.outputs).length === 0) bad.push(`${r.id} has no outputs`);
|
||||
for (const [id, n] of [...Object.entries(r.inputs), ...Object.entries(r.outputs)]) {
|
||||
if (!Number.isInteger(n) || n < 1) bad.push(`${r.id} -> "${id}" count ${n}`);
|
||||
}
|
||||
}
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
|
||||
it('every recipe takes a whole positive number of ticks', () => {
|
||||
const bad = recipes.filter((r) => !Number.isInteger(r.ticks) || r.ticks < 1)
|
||||
.map((r) => `${r.id} -> ${r.ticks} ticks`);
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
|
||||
it('only extractors run input-free recipes', () => {
|
||||
const bad = recipes.filter((r) => Object.keys(r.inputs).length === 0)
|
||||
.filter((r) => machines.find((m) => m.id === r.machine)?.kind !== 'extractor')
|
||||
.map((r) => `${r.id} conjures items on non-extractor ${r.machine}`);
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
|
||||
it('every heat value is a 0..1 fraction per craft', () => {
|
||||
const bad = recipes.filter((r) => r.heat !== undefined && !(r.heat >= 0 && r.heat <= 1))
|
||||
.map((r) => `${r.id} -> heat ${r.heat}`);
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tech', () => {
|
||||
it('every era is a known era', () => {
|
||||
const bad = tech.filter((t) => !ERAS.includes(t.era)).map((t) => `${t.id} -> "${t.era}"`);
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
|
||||
it('every cost is paid in items that exist, in whole positive counts', () => {
|
||||
const bad: string[] = [];
|
||||
for (const t of tech) {
|
||||
for (const id of unknownItems(t.cost)) bad.push(`${t.id} costs unknown item "${id}"`);
|
||||
for (const [id, n] of Object.entries(t.cost)) {
|
||||
if (!Number.isInteger(n) || n < 1) bad.push(`${t.id} -> "${id}" count ${n}`);
|
||||
}
|
||||
if (Object.keys(t.cost).length === 0) bad.push(`${t.id} is free`);
|
||||
}
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
|
||||
it('every unlock names a machine or a recipe that exists', () => {
|
||||
const bad: string[] = [];
|
||||
for (const t of tech) {
|
||||
if (t.unlocks.length === 0) bad.push(`${t.id} unlocks nothing`);
|
||||
for (const u of t.unlocks) {
|
||||
if (!machineIds.has(u) && !recipeIds.has(u)) bad.push(`${t.id} unlocks unknown "${u}"`);
|
||||
}
|
||||
}
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
|
||||
it('no two techs unlock the same thing', () => {
|
||||
expect(duplicates(tech.flatMap((t) => t.unlocks))).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('commissions', () => {
|
||||
it('every want names an item that exists', () => {
|
||||
const bad: string[] = [];
|
||||
for (const c of commissions) {
|
||||
for (const id of unknownItems(c.wants)) bad.push(`${c.id} wants unknown item "${id}"`);
|
||||
}
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
|
||||
it('every commission wants a whole positive quantity of something', () => {
|
||||
const bad: string[] = [];
|
||||
for (const c of commissions) {
|
||||
if (Object.keys(c.wants).length === 0) bad.push(`${c.id} wants nothing`);
|
||||
for (const [id, n] of Object.entries(c.wants)) {
|
||||
if (!Number.isInteger(n) || n < 1) bad.push(`${c.id} -> "${id}" count ${n}`);
|
||||
}
|
||||
}
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
|
||||
it('every commission has fax text and a positive reward', () => {
|
||||
const bad = commissions
|
||||
.filter((c) => !c.flavor.trim() || !(c.rewardBandwidth > 0))
|
||||
.map((c) => `${c.id} -> reward ${c.rewardBandwidth}`);
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('economy reachability', () => {
|
||||
/** Items an extractor can pull, closed over every recipe that can then run. */
|
||||
function producible(): Set<string> {
|
||||
const have = new Set<string>();
|
||||
for (;;) {
|
||||
const before = have.size;
|
||||
for (const r of recipes) {
|
||||
if (Object.keys(r.inputs).every((i) => have.has(i))) {
|
||||
for (const o of Object.keys(r.outputs)) have.add(o);
|
||||
}
|
||||
}
|
||||
if (have.size === before) return have;
|
||||
}
|
||||
}
|
||||
|
||||
it('the M1 flow reaches MELT from raw ore', () => {
|
||||
expect([...producible()]).toContain('melt');
|
||||
});
|
||||
|
||||
it('every item is either producible or declared forward canon', () => {
|
||||
// Forward canon: named in the codex and shippable one day, but nothing makes them yet.
|
||||
// Keep this list short and delete entries as recipes land.
|
||||
const FORWARD_CANON = new Set([
|
||||
'mosquito-swarm', 'gibbs-wraith', 'container-pack', 'cinema-pack',
|
||||
]);
|
||||
const have = producible();
|
||||
const orphans = items
|
||||
.map((i) => i.id)
|
||||
.filter((id) => !have.has(id) && !FORWARD_CANON.has(id));
|
||||
expect(orphans).toEqual([]);
|
||||
});
|
||||
|
||||
it('no recipe is a free lunch: every craft that only recycles its own output loses something', () => {
|
||||
const bad = recipes.filter((r) => {
|
||||
const ids = Object.keys(r.outputs).filter((o) => r.inputs[o] !== undefined);
|
||||
return ids.length > 0 && ids.every((o) => r.outputs[o] > r.inputs[o]) &&
|
||||
Object.keys(r.inputs).every((i) => r.outputs[i] !== undefined);
|
||||
}).map((r) => `${r.id} multiplies its own inputs`);
|
||||
expect(bad).toEqual([]);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user