[balls_game_2] is a physics-based modpack with magic, engineering, and computers.
  • JavaScript 67.4%
  • Python 32.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
forgejo-actions a0bb10ad1e mods: bump 3 mods
BandwidthOptimizer: 1.21.1-5.10.30.134-neoforge -> 1.21.1-5.10.32.139-neoforge
OpenComputers II: MC1.21.1-neoforge-0.9.0 -> MC1.21.1-neoforge-0.9.1
Polytone: 1.21-5.0.0 -> 1.21-5.0.1

Not released. pack.toml is untouched and the jars
still want testing in game.
2026-10-08 06:23:51 +00:00
.forgejo/workflows Skip assets the release already has, and bound the upload 2026-10-01 23:36:28 -05:00
assets Add the front image and a real description to the README 2026-09-30 01:15:29 -05:00
overrides/configureddefaults Re-enable Async Logger, and stop my gates nerfing output counts 2026-10-02 01:57:08 -05:00
scripts Cut the dead scripts and the flags nobody sets 2026-10-02 01:31:21 -05:00
theme Fix the theme comment prefix and install the pack atomically 2026-10-01 05:01:26 -05:00
.gitignore Set up the pack as a git-tracked source of truth 2026-09-30 00:51:13 -05:00
CHANGELOG.md Re-enable Async Logger, and stop my gates nerfing output counts 2026-10-02 01:57:08 -05:00
LICENSE Initial commit 2026-09-30 05:39:53 +00:00
MODRINTH.md Re-enable Async Logger, and stop my gates nerfing output counts 2026-10-02 01:57:08 -05:00
modrinth.toml Stop attaching the instance skeleton to Modrinth 2026-10-01 06:14:26 -05:00
mods.lock.json mods: bump 3 mods 2026-10-08 06:23:51 +00:00
pack.toml Cut 2.0.0-beta6 2026-10-02 01:50:10 -05:00
README.md Re-enable Async Logger, and stop my gates nerfing output counts 2026-10-02 01:57:08 -05:00

[balls_game_2]

balls-game-2

[balls_game_2] is a private modpack made for my friends and I. Minecraft 1.21.1, NeoForge, 132 mods.

Built on the same FOSS-first philosophy as its predecessor, it blends industry, chemistry and physics-driven mechanics across a carefully selected mod list.

Major content

Create for complex mechanical automation, with addons spanning armed airships (Create Aeronautics, Create Big Cannons), rockets and two off-world dimensions (Create Cosmonautics), cooking (Central Kitchen) and enchanting (Enchantment Industry).

The Factory Must Grow, Chemica and Create: Power Grid are the industrial spine. Oil extraction and distillation, chemical vats, electrolysis, the acids, the Kroll titanium process, and circuit fabrication thirteen recipe layers deep.

Sable for fully physics-simulated vehicles with realistic momentum and collision, plus Drive-By-Sable for the controls.

Applied Energistics 2 for late-game digital storage and autocrafting, extended by ExtendedAE, MEGA Cells and ME Requester.

Computing in three eras that chain into each other. TIS-3D is assembly written by hand on four-port nodes. CC: Tweaked is Lua, and holds the actuators: vehicles through CC: Sable, cannons through CC:CBC, Create through the CC:C Bridge, the power grid through CCPowerGridCompat. OpenComputers II emulates RISC-V and Z80 and runs real Linux. A TIS-3D serial port reaches an OC2 machine at 300 baud, and OC2 presents itself to CC as a peripheral.

Eternal Starlight as a second dimension reached by rocket, and Re:Avaritia for the Infinity endgame that runs through it.

Starcatcher, Garnished and Farmer's Delight for fishing, cooking and a shop to sell it through. Exposure adds cameras and film developing.

Epic Terrain generates the world. Caxton provides custom font support and Sodium carries performance.

How this repo works

This repo is the source of truth for the pack. It holds no mod jars: mods.lock.json records which Modrinth version of each mod the pack pins, and the launcher fetches them at install time.

Layout

pack.toml            pack name, version, Minecraft and loader versions, Java hints
mods.lock.json       129 pinned mods: project id, version id, URL, sha1, sha512, env
modrinth.toml        Modrinth-only release settings
overrides/           everything that ships to players, under configureddefaults/
theme/               quest book theme, injected into the resource pack
CHANGELOG.md         release notes, one H2 section per version
scripts/sync.py           pull the live instance into this repo
scripts/update.py         check Modrinth for newer versions of the pinned mods
scripts/gen_ftbquests.py  regenerate the quest book from the advancement tree
scripts/apply_theme.py    write theme/ into the shipped resource pack
scripts/build.py          dist/*.mrpack and dist/*-instance.zip
scripts/publish.py        upload a version to Modrinth
scripts/check_ids.py      every item id the pack names, against the installed jars
scripts/tech_tree.py      item dependency graph, read out of the installed jars
scripts/resync_chemica_overrides.py   rebuild the chemica overrides from its jar
scripts/upload_jars.py    push the non-distributable jars to the package registry

Everything the pack ships lives under overrides/configureddefaults/, with no exceptions. The ConfiguredDefaults mod copies a file out only where the player does not already have one, so updating the pack can never overwrite someone's edited config.

That applies to the quest book too. It means a player who has already launched the pack keeps the book they have and will not receive a newer one, since the file is already there. Only a fresh install gets the current book. Anyone wanting the new one deletes config/ftbquests/quests/ and relaunches; progress lives in saves/<world>/ftbquests/ and is not affected.

Working on it

Edit the live instance, then pull the changes in:

scripts/sync.py            # mods and overrides
scripts/sync.py --files    # overrides only, no Modrinth calls

sync.py hashes every jar, resolves it through Modrinth's file-hash lookup, and rewrites the lockfile. A jar that is not on Modrinth is reported rather than silently dropped, because it cannot legally or mechanically go in the manifest.

Keeping mods current

scripts/update.py asks Modrinth's bulk update endpoint whether any pinned mod has a newer build that still matches the pack's loader and Minecraft version, so it can never suggest a jar that would not load.

scripts/update.py           # report; exit 10 if anything is stale, 0 if not
scripts/update.py --apply   # rewrite mods.lock.json
scripts/update.py --json    # machine-readable

bump-mods.yml runs it daily, applies what it finds, rebuilds to prove the lockfile still produces a valid manifest, commits, and posts to Discord. It never touches pack.toml: a mod bump is not a release, and the jars still want testing in game.

Hold a mod back by adding its slug to PINNED in scripts/update.py with the reason. Held mods are reported but never applied.

The quest book

scripts/gen_ftbquests.py builds all 188 quests, in 13 chapters, from the advancement tree under overrides/configureddefaults/kubejs/data/balls_game/advancement/balls, so the book and the advancements cannot drift apart. An inventory_changed criterion becomes an item task, changed_dimension becomes a dimension task, parent links become dependencies, and the advancement frame picks the quest shape and the xp reward.

Titles and descriptions go in lang/en_us.snbt keyed by id, not in the chapter files, which is how FTB Quests itself writes them.

scripts/gen_ftbquests.py   # rewrites overrides/configureddefaults/config/ftbquests/quests/
scripts/apply_theme.py     # theme/ into the shipped resource pack

Regenerating does not wipe anything

FTB Quests assigns a fresh random id to every quest and chapter it rewrites, so ids cannot be used to match a regenerated book against the one on disk. The generator reads the old book back first and matches on what FTB Quests leaves alone:

  • a quest, by the item its first task asks for
  • a chapter, by its filename

Anything matched keeps its id, its x and y, and a title that was edited by hand. So the book can be laid out and renamed in game, regenerated afterwards, and neither the positions nor player progress in saves/<world>/ftbquests/ is lost. New quests get ids and fall into free slots.

Editing the book in game is fine; sync.py --files pulls it back. Regenerate after syncing, not before, since the generator overwrites the whole directory and can only preserve what it finds there.

Theme

theme/ftb_quests_theme.txt is the pack's theme. FTB Quests loads ftbquests:ftb_quests_theme.txt through getResourceStack, so every resource pack that provides the path stacks over the mod's own default and the file only carries the properties that change. apply_theme.py validates it and writes it into overrides/configureddefaults/resourcepacks/[balls_game].zip as assets/ftbquests/ftb_quests_theme.txt.

It is kept outside overrides/ because anything under there also ships as a loose file, and a loose theme/ in the mrpack does nothing but confuse.

Overriding another mod's recipes

The pack overrides 138 of Chemica's recipes, because Chemica writes camelCase keys and names the vat type ids TFMG renamed in 1.3.1. An override freezes the upstream recipe at whatever version it was copied from, so a Chemica update silently reverts to the old numbers until the overrides are rebuilt.

scripts/resync_chemica_overrides.py

It takes Chemica's current file, applies only the two mechanical fixes, and keeps the pack's own file wherever there was a deliberate content decision, printing ADOPT or KEPT with the reason for each. Running it against a Chemica it has already been run against produces no diff, which is the check that it is still in sync.

scripts/tech_tree.py reads every recipe in every jar, including JarJar nests, plus the pack's own overrides, resolves tags and walks outward from what the world gives for free. Used to confirm nothing in the progression tree is unreachable.

scripts/tech_tree.py depth <item>    cheapest full route to one item
scripts/tech_tree.py tier            every item grouped by depth
scripts/tech_tree.py chain <ns>      depth-ordered milestones for one namespace

Releasing

Bump version in pack.toml, add a ## <version> section to CHANGELOG.md, then:

scripts/build.py
scripts/publish.py --dry-run
git tag 2.0.0-beta5 && git push origin 2.0.0-beta5

The tag triggers release.yml, which checks the tag against pack.toml, builds, uploads to Modrinth, creates the Forgejo release and attaches both artifacts, then posts a Discord embed.

Workflows

file trigger does
bump-mods.yml daily at 06:23, or manual check Modrinth, apply, rebuild, commit
release.yml tag push, or manual build, Modrinth upload, Forgejo release, notify

Both take optional secrets and skip cleanly when one is missing rather than failing:

  • MODRINTH_TOKEN: pat_ token with "Create versions"
  • DISCORD_WEBHOOK: release and bump notifications

FORGEJO_TOKEN is provided by the runner; nothing needs setting for the git push or the release API.

Artifacts

Some mods in the pack are All Rights Reserved and absent from Modrinth, so they cannot go in a Modrinth manifest: there is no allowlisted URL to point at, and no permission to bundle the jar. FTB Quests, FTB Library and FTB Teams are the current three. build.py therefore produces two packs rather than one.

file mods where it goes
<slug>-<version>.mrpack 124 referenced Modrinth. Omits the three.
<slug>-<version>-full.mrpack 124 referenced + 3 bundled Forgejo releases only. The complete pack.
<slug>-<version>-instance.zip none Configs over an existing install.

A jar joins the bundled set when sync.py cannot find it on Modrinth by file hash. It is recorded under unresolved in mods.lock.json, so nothing vanishes quietly, and build.py names which mods each variant drops or carries.

publish.py refuses outright to upload anything with -full in the name, and modrinth.toml does not glob for it either. Both guards, because the consequence Modrinth states for redistributing without permission is takedown plus account removal.

Build one variant at a time with scripts/build.py --variant modrinth|full|instance.

The instance skeleton never carries jars. Bundling all 127 would mean redistributing every one, which most of their licences do not grant.

Java

ZGC is worth setting; its pauses are sub-millisecond where G1's are not. Generational ZGC has been the default since JDK 23, so -XX:+UseZGC is the whole flag. -XX:+ZGenerational was removed in JDK 24 and will refuse to start.

-XX:+UseZGC -XX:+AlwaysPreTouch    with equal min and max heap

A modpack manifest cannot set JVM arguments, so this is on the player. The -instance.zip sets it for you.