NeoForge — Loose Planning Notes¶
Status: Pre-proposal scratch. Not a plan, not an ADR, not a commitment. These notes exist so we don't have to re-derive the landscape every time NeoForge comes up. Anything here that turns into actual work must go through a D-005 proposal first and (likely) a formal ADR + an entry in
MULTI_PLATFORM_PLAN.md.Current scope reminder (updated 2026-06-02): NeoForge is in scope per ADR-033 (Accepted), the Fabric stabilization gate is clear (Fabric confirmed stable 2026-06-01), the D-005 proposal (
scratch/PROPOSAL-neoforge-bringup.md) is approved, and the named maintainer is assigned (project lead,@leaf_26). The Phase N1 module skeleton has landed underplatforms/rtp-neoforge/: ModDevGradle build wiring,neoforge.mods.toml, the@Modentry point, a complete server-thread scheduler, and command/version-adapter scaffolds, merged into the unified LeafRTP-Pro jar so a single artifact loads on Bukkit/Fabric/Velocity and NeoForge. The NeoForge modules build BY DEFAULT; drop them with-PexcludeNeoforge(or EXCLUDE_NEOFORGE) on network-constrained hosts to keep the offline build green. Still open: theRTPServerAccessorimplementation, command Brigadier-bridge wiring, the S-005 async chunk path, the/rtpround-trip exit gate, the S-005/S-006 REQ-traceable guards, and a network-host build verification.
1. Why NeoForge, and why now (eventually)¶
- Audience. NeoForge is the active successor to legacy Forge and currently the dominant modded-Java platform on 1.20.4+. Large-modpack servers (technical/exploration packs) are exactly the user profile that benefits most from a bounded-distribution RTP.
- Strategic ordering. Fabric first (in flight), NeoForge second. Reasons:
rtp-fabric's obf/unobf carrier split and Mojmap-decoupling work (rtp-fabric-ADR-007, rtp-fabric-ADR-009) directly informs NeoForge's mappings problem.- The anvil-prefilter parity work (rtp-fabric-ADR-005) and non-blocking chunk generation (rtp-fabric-ADR-008) are the same problems on NeoForge, with slightly different APIs.
- Forge (legacy ≤1.20.1). Out of scope. Sunsetting. If we ever feel pressure here, address via a NeoForge backport, not a parallel module tree.
2. API surface differences from Fabric (the part that matters for adapter design)¶
These are the things that make NeoForge a genuinely distinct adapter, not a Fabric reskin:
- Event bus. NeoForge uses its own annotation-driven event bus (
@SubscribeEvent,IEventBus, mod bus vs game bus split). Fabric uses callback registration (ServerLifecycleEvents.SERVER_STARTED.register(...)). Wiring point inFabricVersionAdapteris not directly portable. - Registries. NeoForge has its own
DeferredRegister<T>/Holder<T>flow on top of the vanilla registry. We don't register much (no blocks/items), so this is mostly relevant for command registration and tag access. - Command registration. NeoForge fires
RegisterCommandsEventon the mod bus; Fabric usesCommandRegistrationCallback. Both terminate in vanillaBrigadier, socommands-apiBrigadier bridge (commands-api-ADR-001) is reusable; only the registration trampoline differs. - Mod metadata.
META-INF/neoforge.mods.tomlvsfabric.mod.json. Trivial. - Mappings. NeoForge is Mojmap-at-runtime (same as modern Fabric without Yarn). The Mojmap-decoupling discipline from
rtp-fabric-ADR-007carries over: keeprtp-core/rtp-apifree of NM-typed surfaces; isolate NM in a per-version carrier. - Threading. NeoForge servers are single-main-thread, same as vanilla / Fabric (no Folia-style regions). All S-005 reasoning carries over unchanged. No region-ownership checks needed. Bukkit's
Entity#teleportsemantics don't exist; we'd be callingEntity.teleportTo/ theTeleportTransitionAPI directly. - Chunk tickets. Same vanilla
DistanceManager/ChunkHoldersubstrate as Fabric. The non-persistent ticket work (rtp-fabric-ADR-003) and ticket-radius work (rtp-fabric-ADR-006) should port near-verbatim; if anything, NeoForge sometimes provides slightly friendlier accessors.
3. What we can reuse from rtp-fabric (high)¶
rtp-core,rtp-api,rtp-anvil,commands-api,effects-api— all platform-neutral. Reused 1:1.- The obf/unobf carrier split (rtp-fabric-ADR-009) pattern. NeoForge is Mojmap-at-runtime for 1.20.4+, so we may not need an obf carrier — but the structural separation (NM-typed surfaces in a per-version carrier; deobf top-level) still applies, because runtime class names will shift across MC versions and we don't want
rtp-corelinked to a specific MC rev. - The multi-version submodule layout (rtp-fabric-ADR-001): per-MC-version carriers (
rtp-neoforge-v1_20_R1,rtp-neoforge-v1_21_R1, …) dispatched by aNeoForgeVersionAdapter. Mirror the rtp-fabric naming. - Anvil prefilter (rtp-fabric-ADR-005) — same
.mcasubstrate, same code. No work. - Non-blocking chunk generation (rtp-fabric-ADR-008) — same vanilla async generation primitives.
- Typed block-tag snapshot (rtp-fabric-ADR-010) — same registry concept; the snapshot reader will adapt cleanly.
4. What we cannot reuse (must rewrite per platform)¶
- The platform entry point (
@Mod-annotated class vs Fabric'sModInitializer). - Event wiring (mod bus / game bus subscribe vs Fabric callbacks).
- Command registration trampoline.
- Build system: NeoForge uses NeoGradle (or ModDevGradle / ModsDotGroovy depending on version) instead of Fabric Loom. Toolchain choice will need a decision (see open questions).
- The S-006 entry-point guarantees — same shape, different listener type.
5. Module layout sketch (illustrative, not approved)¶
rtp-neoforge/
docs/
adr/ # per-subproject ADRs, restart at 001 per AGENTS.md self-update rules
rtp-neoforge-common/ # Mojmap, no per-version NM (mirrors rtp-fabric-common-unobf in spirit)
rtp-neoforge-v1_20_R1/ # per-MC carrier (NeoForge supports 1.20.4+; numbering follows existing convention)
rtp-neoforge-v1_21_R1/
rtp-neoforge-v1_21_R11/ # if/when we add later MC revs
Open: whether to additionally split a *-unobf carrier the way Fabric does. NeoForge being Mojmap-at-runtime suggests no, but if NeoForge ever ships an SRG/Mercury-mapped intermediate (it has experimented), revisit.
6. Threading & S-00x mapping¶
| Rule | NeoForge implication |
|---|---|
| S-001 | Same as Fabric — block-safety logic lives in rtp-core. No fork-API second-check. |
| S-002 | Same. DistanceManager tickets via the non-persistent ticket pattern (rtp-fabric-ADR-003). |
| S-003 | Claim plugins effectively don't exist on NeoForge; the few that do are mod-side (FTB Chunks, etc.). Treat as a reflection-gated hook entry per ADR-026 if/when demand appears. |
| S-004 | Same; FailTypes.nullChunk attribution path is platform-neutral. |
| S-005 | Critical. NeoForge single-main-thread; ServerLevel#getChunk(int, int, ...) is sync by default. Adapter must route through the same async-generation pattern used in rtp-fabric. The Fabric FabricWorld.getChunkAt regression (S-005 blocker called out in Current Development Focus) must not be re-introduced here. |
| S-006 | Same — IllegalStateException on early API use, not null/no-op. |
| S-007 | Same — messages.yml already covers this; no platform work. |
7. FTB Chunks / claim mods — soft-depend, not adapter scope¶
Mod-side land protection (FTB Chunks, OpenPartiesAndClaims, Argonauts, etc.) is the rough equivalent of Bukkit claim plugins. Per ADR-019 and S-003, integrations are folded into the plugin via reflection / soft API. Same playbook for NeoForge: catalog any hook in EXTERNAL_HOOKS.md per ADR-026. No claim-mod code inside the pipeline.
8. Build / toolchain open questions¶
- Gradle plugin: NeoGradle vs ModDevGradle. ModDevGradle is the newer official path (2024+) and is closer to Loom in spirit; lean toward ModDevGradle pending a quick spike.
- Java level: NeoForge tracks vanilla — Java 21 for 1.20.5+, Java 21+ for 1.21+. Matches our REQ-RTP-SYS-001 baseline.
- Run config: NeoForge dev-launch uses its own runtime; need to verify
run_testand the existing IntelliJ run-configs can drive a NeoForge dev server. Likely requires a new.runXML per per-version submodule. - Lite-jar matrix: Each NeoForge carrier added expands the assembly matrix (ADR-024). Decide whether NeoForge ships only the full jar initially.
9. Risks / gotchas to expect¶
- Mappings drift across MC revs. Mojmap class/field/method names move between 1.20.4 → 1.21 → 1.21.x. Per-version carriers absorb this; do not let names leak into
rtp-core. - NeoForge "mod loading phases." Some APIs (registries, tags) are only safe to touch after specific phases. The S-006 guarantee is friendly to this — we already throw on early use.
- AccessTransformers / Mixins. Hopefully unnecessary. If we need either, that's a red flag — re-examine whether the public API suffices first.
- CompletableFuture interop with the server thread executor. NeoForge's
ServerLevelexecutor (server::tell/MinecraftServer#submit) is the safe sink for "back to main thread"; codify in the platform adapter the same way Fabric does. - Folia analogue. None. Do not invent one. If NeoForge ever ships region-threading, treat as a new platform, not a flag.
Entity#teleportTosemantics change across MC versions (notably 1.21.x'sTeleportTransitionrework). Carrier per-version absorbs this.
10. Minimum-viable scoping (when work actually starts)¶
In order, do not skip:
- D-005 proposal referencing this notes file.
- ADR: NeoForge platform in-scope (mirror rtp-fabric-ADR-002), under
rtp-neoforge/docs/adr/rtp-neoforge-ADR-001-…. MULTI_PLATFORM_PLAN.mdphase rows for NeoForge (Phases 0–4 mirror layout).- Module skeleton + dev-launch + a single
/rtpround-trip on the default world before any optimization or anvil work. - REQ-traceable tests for S-005 and S-006 first (
ReqRtpNeoforgeS005ChunkLoadingTest,ReqRtpNeoforgeS006EarlyApiTest). - Only then: anvil parity, ticket parity, the multi-version carrier split.
11. Out of scope for these notes (deliberate)¶
- Forge (legacy) bring-up.
- Sponge.
- Bukkit-via-Mohist / Magma / Arclight (hybrid servers). If demand appears, treat as a
rtp-paperruntime-compatibility issue, not a new adapter — they ship a Paper API surface. - Cross-server (Velocity/Bungee) interaction. Tracked separately in
MULTI_SERVER_PLAN.md. - Whether NeoForge should share a carrier directory tree with Fabric. Tempting; almost certainly a trap (build-system divergence, dependency graph divergence). Notes say no; revisit only with hard evidence.
Last touched: 2026-05-11. Owner: unassigned. Delete or fold into a real plan/ADR once NeoForge work is actually approved.