Skip to content

Machine module stage 1: internals and recipe core - #81

Merged
dshadowwolf merged 21 commits into
master-2.0-1.21.1-neofrom
machine-module
Nov 2, 2026
Merged

dshadowwolf merged 21 commits into
master-2.0-1.21.1-neofrom
machine-module

Conversation

@faceofcat

@faceofcat faceofcat commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

What this is

Stage 1 of the machine module: a Java API under zone.moddev.mc.lib.machine, with no screens and no processor yet. Design and API docs for the module are kept outside this repo.

Internals. Parts sit in sockets and change a machine's stats.

  • InternalsDefinition sets up sockets, stats and swap rules.
  • Part files live in data/<ns>/mmdlib/machine_part/. They support material templates and $variable expressions.
  • Stats combine in a fixed order: add_value, then add_multiplied_base, then add_multiplied_total, then clamp.
  • Parts wear out, through a wear data component.
  • MachinePartsLoader reloads part files.

Phase 1 of the recipe core:

  • MachineIngredient: items, fluids and energy, with amount matching.
  • Slot groups and MachineInventory, changed through SnapshotTx transactions.
  • MachineProduct for item and fluid outputs.
  • RecipeMatcher, which checks, consumes and produces all or nothing.

MachineRecipe is only a data shape here. Registering it as a recipe type comes in the next PR. mmdlib:crushing is unchanged.

Import rule. Only machine.neoforge may import NeoForge. ./gradlew verifyMachineImports enforces this.

Docs. The existing docs get small updates for what changed.

Review notes

  • 17 commits, one per piece, so reviewing commit by commit should be easier than the whole diff. Commit f742165 added docs and a CLAUDE.md that the last two commits move out of the repo, so you can skip those files in the commit view.
  • The base gained Master 2.0 1.21.1 neo #80 (tooltips and shift-click) after this branch was cut. It merges cleanly, so I didn't rebase.
  • Two follow-up branches are stacked on this one and will get their own PRs after this lands:
    • test-infra: JUnit 5, NeoForge game tests, CI.
    • machine-recipes: registered recipe types and generic JEI.

Testing

  • The builder checks pass. They cover the expression parser, stat pipeline, internals codec, part files, ingredients, transactions, matcher, and mixed item and fluid outputs.
  • The runtime smoke tests pass: part-file loading, internals, and matcher against real storages.
  • verifyMachineImports passes.

🤖 Generated with Claude Code

https://claude.ai/code/session_018zpP6ZrFjaGe477FvWAZKo

faceofcat and others added 15 commits October 31, 2026 23:04
Duplicate-definition messages name file-system paths, which print with
backslashes on Windows, so the fragment check failed there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zpP6ZrFjaGe477FvWAZKo
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zpP6ZrFjaGe477FvWAZKo
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zpP6ZrFjaGe477FvWAZKo
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zpP6ZrFjaGe477FvWAZKo
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zpP6ZrFjaGe477FvWAZKo
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zpP6ZrFjaGe477FvWAZKo
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zpP6ZrFjaGe477FvWAZKo
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zpP6ZrFjaGe477FvWAZKo
Expression constants printed in E notation (1.0E7), which the parser
can't read back. Saved internals put sockets beside the orphans list, so
a socket named 'orphans' lost its parts on load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zpP6ZrFjaGe477FvWAZKo
…d mixed outputs

- MachineProduct now dispatches on its key like MachineIngredient: an entry
  with both 'item' and 'fluid', or neither, is an error instead of silently
  decoding as the item.
- MachineRecipeSmoke: a recipe that makes an item and a fluid in one run, and
  rolls both back when the tank is full.
- MachinePartsLoaderSmoke: part files from a real datapack, copied into the
  smoke world by the init script, reach MachineParts.current(); a file that
  doesn't decode is skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zpP6ZrFjaGe477FvWAZKo
@CLAassistant

CLAassistant commented Nov 2, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Face o' Cat seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

faceofcat and others added 2 commits November 2, 2026 10:06
The machine module docs, machines.md and later testing.md now live in
faceofcat/mod-design under mmdlib/docs. Links to them are removed from the
docs that stay here; CLAUDE.md points at the sibling checkout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zpP6ZrFjaGe477FvWAZKo
CLAUDE.md moves to the private design repo as AGENTS.md, and the local
pointer files are ignored, as in the other mod repos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zpP6ZrFjaGe477FvWAZKo
@dshadowwolf

Copy link
Copy Markdown
Collaborator

Questions:

  1. MachinePartLoader appears to be server only and nothing sync's to the client on a /reload
  • SSP and LAN hide this because of the way the integrated "logical" server works
  1. There is a lag between "consume input" and "produce output" with the consume happening at the start of processing and the production happening at the end -- while not uncommon, what happens to the "in process" ingredients if the machine is broken mid-cycle ?
  2. Item matching is greedy with no backtracking. Take a recipe [#logs ×1, oak_log ×1] with slots [oak ×1, birch ×1]. The tag claims the oak first, so the recipe fails even though it could run.
  3. Typos in part files fail silently. Part of the design in MMDLib is to loudly complain, in (at a minimum) the logs about any issues.
  4. Design nits:
  • def verifyStaticAssets =tasks.register is missing a space.
  • Expr accepts $a..b as a variable name, because only the trailing dot is rejected.
  • MachineInternals.wear() accepts negative points, which would repair a part.
  • InternalsDefinition.socketAt(-1) returns the first socket instead of throwing.
  • load() triggers onChange, and so setChanged, once per slot while loading.
  1. The .gitignore change points to an outside, "private" repo

faceofcat and others added 4 commits November 2, 2026 16:37
…eedily

A broad ingredient listed first could use up the only item a later one
accepts, so [#logs, oak_log] failed on slots [oak, birch] even though it
could run. check and consume now share one assignment that can move an
earlier claim elsewhere, for items and fluids alike, so ingredient order
no longer matters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zpP6ZrFjaGe477FvWAZKo
Keys a part file or modifier doesn't use are now decode errors, so
"durabilty" no longer gives a part that never wears. MachineParts.check
runs on every load: it logs part and item IDs that match nothing, warns
about special keys no material defines, and resolves every item so
template errors appear on reload instead of at first use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zpP6ZrFjaGe477FvWAZKo
- Expr rejects empty segments such as $a..b.
- MachineInternals.wear() refuses negative points, which would repair a part.
- InternalsDefinition.socketAt() refuses negative slots.
- MachineInternals.load() fires onChange once instead of once per slot.
- Add the missing space in verification.gradle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zpP6ZrFjaGe477FvWAZKo
These rules only matter on one machine, so they now live in
.git/info/exclude.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zpP6ZrFjaGe477FvWAZKo
@faceofcat

Copy link
Copy Markdown
Collaborator Author

Checked each of these against the code. Nothing is pushed yet; this is what's coming.

1. Client sync: will be added in a later PR.

2. Breaking mid-cycle: this PR has no processor, so nothing holds in-process ingredients yet. When the processor lands in a later PR, the items it took will drop when the machine is broken, as if the cycle never ran. Fluids and energy already used are lost.

3. Greedy matching: correct. Your example fails as you describe. Fluids have the same bug, and consume repeats the same greedy logic. I'll replace both with one proper assignment (augmenting paths over ingredients and slots), so ingredient order stops mattering, and add your case as a test.

4. Silent typos: partly. Malformed JSON, files that fail to decode and duplicate claims are already logged as errors. These are not, and I'll fix them:

  • Unknown keys are ignored, so "durabilty": 100 gives a part that never wears. This will become a logged error.
  • A part or item ID that doesn't exist does nothing. This will also be logged.
  • $material.special.typo evaluates to 0. I'll warn when no material defines that key.
  • Template errors only appear when an item is first resolved. I'll resolve everything at load, so they appear on reload.

Socket and stat names can't be checked yet: they belong to each machine, and nothing keeps a list of machines. I'd handle that in a later PR with a small registry of internals definitions, unless you'd prefer something else.

5. Nits: all correct, and I'll fix all five.

  • Add the missing space in verification.gradle.
  • Reject .. in Expr variable names.
  • Make wear() throw on negative points.
  • Make socketAt() throw on negative slots.
  • Make load() fire onChange once.

6. .gitignore: correct. Those are my local rules, so I'll drop them from the PR and keep them locally in .git/info/exclude.

@dshadowwolf

Copy link
Copy Markdown
Collaborator

good work.

@dshadowwolf
dshadowwolf merged commit 09a7f7b into master-2.0-1.21.1-neo Nov 2, 2026
1 check passed
@faceofcat
faceofcat deleted the machine-module branch November 2, 2026 16:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants