Loading the eight carried-over mods in the game (not only the probe) showed
two limits real packs reach. Block textures past 2048 layers are another PR;
this one handles block states and measures a pack.
- Chunk sections store a block state in 16 bits. Vanilla 26.3 has 35,723
states; Macaw's four mods add 36,154, the eight 41,389. Past 65,536 ids
wrapped onto other blocks (a trapdoor became a hanging sign).
mod_content::build now refuses a set of mods past MAX_STATES and names the
states per mod; the game then starts without content mods, as for any
refused content.
- tools/mod_port_harness.py probes each mod on its own against vanilla, as a
player adding it would load it.
- tools/perf_bench.py --game neoforge: Minecraft 1.21.1 on NeoForge 21.1.259
with --mod-jars and --deps, driven by a dev-only NeoForge port of the bench
mod (mods/horizon/src/neoforge-bench); --port-work loads a work folder's
carried-over mods into Horizon. tools/neoforge_oracle.py installs the
client once under .local/neoforge-oracle (the installer, then the vanilla
libraries it inherits, sha1-checked) and builds the driver. A NeoForge
start that crashes before the driver loads (FML reading fml.toml while
creating it) is retried once.
Measured (docs/modpacks.md): six ported mods in Horizon against the same
mods on NeoForge, Mac mini M4 Pro, 2 games per row on a loaded machine. The
world is ready 3.1x sooner at 8 and 16 chunks, the flights run 1.7x and
3.6x the frames, and Horizon's RSS is not lower (+1.1 GB with the mods).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`report <work>` writes coverage.json and coverage.md: per mod ported,
partial, not yet or not needed with the reason, and the totals by class,
tier and licence family. It reads the inventory, each mod's carry report
and its harness result: ported is a green harness with nothing left out
(blocks with a class of their own are counted and named); partial lists
the failing checks, the blocks left out and what the templates miss; not
yet names the API tier or the dependency it waits for; not needed covers
libraries, performance mods and pack tooling. Unclassified mods are not
yet, never not needed.
`import` also reads Crash Assistant's modlist.json, the list ATM10
publishes in its repository (mod ids, names, versions, Modrinth hashes,
the loader entry), and the inventory uses its mod ids and names when
there is no jar.
ATM10 (496 files, the eight mods we downloaded carried and checked):
ported 1, partial 7, not yet 367, not needed 121.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The harness the modpack design calls for, for data-tier mods:
- tools/neoforge_oracle.py installs a NeoForge 21.1.259 server (ATM10's)
under .local/neoforge-oracle, builds the exporter in
mods/horizon/src/neoforge-oracle (our code, never shipped), runs it
headless with the user's jars and writes every block (class chain,
states, per-state hardness, light, tool need, map colour, sounds,
outline and collision; drops for hand, diamond tools, shears and silk
touch; placement; BlockSetType and WoodType), item, recipe and tag of
the mods' namespaces, plus the vanilla blocks for templates. About 30 s.
- examples/mod_port_probe.rs builds the content root from the carried
mods and dumps what Horizon's loaders make of the same things.
- tools/mod_port_harness.py compares them per mod and writes
<work>/mods/<id>/harness/result.json. 1.21.1 sound events map to 26.3's
through vanilla blocks both have, and vanilla items newer than 1.21.1
are not counted against a recipe.
On ATM10's own jars (MIT): Macaw's Trapdoors passes everything; Doors,
Fences, Paths, Glassential, Factory Blocks and Cloud Glass pass every
drop, recipe, tag and placement and fail only on what API 0.2 cannot say
(shapes and sound events of their own, blocks of classes no vanilla block
shares). Jars, facts and results stay in the work folder.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Carrying over seven real ATM10 mods (Cloud Glass, Factory Blocks,
Glassential, Macaw's Doors, Fences, Paths and Trapdoors) showed what the
guesses missed:
- Loot tables: 1.21.1's `conditions` lists, `functions` and
`condition`/`function` kinds are now 26.3's single `condition`,
`modifier` and `type`; silk-touch and shears tool matches become
`minecraft:tool/can_silk_touch` and `can_shear`. Before, every drop
condition was ignored (glass dropped itself by hand, upper door halves
dropped doors, double slabs dropped one).
- With the oracle's facts (<work>/oracle/facts.json, from
tools/neoforge_oracle.py in the next PR), every registered block gets
the vanilla template of the same vanilla class and states that matches
its shapes, sounds, BlockSetType and WoodType best, and the mod's own
hardness, blast resistance, light, tool need, friction, map colour and
sounds. Items get stack size and rarity. The carry report lists what a
template does not reproduce and why a block was left out.
- Convention tags a recipe uses (c:dyes/red) are written from the
oracle's tags, vanilla and own members only; the jar's own c tags stay
inside the mod.
- Without the oracle, blockstates that omit properties their models
ignore (powered, waterlogged) still find their template, and names pick
between fences and panes, doors and trapdoors.
- `version = "${file.jarVersion}"` is read from MANIFEST.MF.
- `--resources <prepared>` limits templates to blocks Horizon has.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
carry <work> [<mod id>...] turns each content mod with a jar into a folder
content mod (API 0.2) in <work>/mods/<id>/carry/, for local use:
- assets/<id>/, data/<id>/ and data/minecraft/tags/ are copied; other
namespaces and vanilla data go to outside/ for review; no class file is
copied.
- Vanilla-type recipes get 26.3 ingredients ("id", "#tag", lists);
custom types and NeoForge conditions are kept and listed.
- Item models get the items/ client definitions 1.21.4 introduced.
- Each block is declared from a vanilla template guessed from its
blockstate properties; unknown property sets are listed. Items
without a block are declared from minecraft:stick.
The test builds a small NeoForge jar, carries it over, and reads the
result with horizon-mods' own Content reader.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A CLI for the first steps of docs/modpacks.md's pipeline:
- import: a Modrinth .mrpack, a CurseForge zip or a Prism / MultiMC
instance (or a folder of jars) to pack.json: Minecraft and loader
versions, each file with its hash and sides, and the overrides.
- inventory: Modrinth metadata by SHA-1 (cached in modrinth.json, so
reruns work offline), and the jars when the user's instance has them
(neoforge.mods.toml / mods.toml / fabric.mod.json and a file census,
without decompiling). Each mod gets a class and API tier from the
reviewed ATM10 table, then rules on categories, sides and the census,
and a dependency layer. Writes inventory.json and inventory.csv.
Only metadata is downloaded; jars are never fetched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/modpacks.md: how any pack (ATM10 down to small Fabric packs) gets to
Horizon: import (.mrpack, CurseForge zip or instance, Prism, packwiz),
inventory and classification, dependency order, mechanical carry-over of
data and assets, one agent per mod for code with a per-mod harness against
a dev-only NeoForge/Fabric oracle, and a coverage report. Also the mod API
gaps against 0.1, the registries 0.2 design (template blocks through the
overlay root), licence handling, how "10x" is measured against NeoForge,
and the scope for 2026-10-23.
docs/modpacks/atm10-mods.csv: ATM10's 495 jars from the pack's own
crash_assistant modlist (2026-10-15), with Modrinth metadata looked up by
hash: class, API tier, licence, sides. Metadata only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>