Skip to content

Port reactor logic from Forge branch with enhancements and fixes - #42

Open
Giovanniricotta2002 wants to merge 33 commits into
V2from
V2-Reacteur
Open

Port reactor logic from Forge branch with enhancements and fixes#42
Giovanniricotta2002 wants to merge 33 commits into
V2from
V2-Reacteur

Conversation

@Giovanniricotta2002

Copy link
Copy Markdown
Member

No description provided.

mathi and others added 30 commits August 6, 2026 14:57
Groundwork required before porting the reactor's logic layer (services,
reactorLogic, consumable, snapshot, display) from the Forge branch. None
of it compiles without these five prerequisites.

- CReactorHeat: new config holding the DANGER heat thresholds per reactor
  size (5x5 / 7x7 / 9x9), wired into CNCServer.
- CRods: add the uranium/graphite/thorium proximity, base and ratio values
  the heat model reads. Existing key names are kept so current configs are
  not reset.
- RodType: numeric values become Suppliers so they are re-read from the
  config at runtime instead of being frozen at registration, as on Forge.
  Adds ratio() (weight used by the fuel/cooler thermal balance), the
  isFuel/isCooled overloads taking a resolved RodType, and the level-aware
  tooltipKey. Drops the useConfig machinery: its three switches were fully
  commented out, so it always returned the default value, and nothing
  called setRodConfig().
- IHeat.HeatLevel: thresholds now depend on the reactor size, plus
  isNotDanger() and the BigItemStack overload.
- ReactorControllerBlock: add the ACTIVE property, recomputed server-side
  every tick and synced to the client. It drives the running-sound loop and
  the lit controller texture (off / standby / on) generated by
  ReactorControllerGenerator.

Also register the RodType of URANIUM_ROD and GRAPHITE_ROD, which had none:
resolveRodType() fell back to FALLBACK (type NONE), which the heat
calculator skips, so the ported model would have computed zero heat
forever without any visible error. THORIUM_ROD switches from hardcoded
values to the config-backed ones.

PORTAGE_REACTEUR.md documents the full gap analysis and the remaining lots.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ports the reactor's thermal model from the Forge branch. Six of the seven
files are byte-identical to their Forge counterpart; DefaultHeatCalculator
only differs by the ItemStackHandler import.

- DefaultHeatCalculator: per-slot heat from the blueprint pattern, with the
  adjacency bonus/malus resolved through a precomputed neighbour map. Only
  rods actually present in the inputs contribute, so a pattern asking for
  more rods than are loaded no longer over-reports heat.
- DefaultOverheatController: overheat escalates while the reactor is out of
  balance, and the escalation accelerates as overFlowLimiter shrinks. Two
  independent malus sources: fuel/cooler ratio above the 6:1 equilibrium,
  and coolant missing or past its maxHeat.
- HeatBalance / EquilibriumState: the 6:1 wiki equilibrium, weighted by the
  RodType ratio introduced in lot 0.
- HeatManager: facade resolving the coolant type before delegating.

ReactorDisplayState comes along from lot 4: it is a hard dependency of
computeHeat(). Its NBT methods take a HolderLookup.Provider because
BigFluidStack.write()/read() require one on NeoForge, and item lookup goes
through BuiltInRegistries.ITEM.getOptional() so an item removed with its
mod is dropped instead of silently resolving to air.

Nothing calls HeatManager yet - wiring lands with lot 3 (services) and lot 6
(block entity), which is also when DefaultHeatCalculatorGameTest gets ported
to actually exercise this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ports the consumable cycle from the Forge branch. Four of the five carried
files are byte-identical; ConsumptionCycleManager only differs by the
registry lookup and the empty-pattern check.

- ConsumptionCycleManager: one timer per rod type present in the pattern,
  persisted in NBT so a cycle survives a reload. Timers are rebuilt when the
  pattern changes rather than restarted from scratch, so editing a blueprint
  does not refund the elapsed lifetime.
- ItemConsumable / FluidConsumable / ConsumableTimer / IConsumable: unchanged
  from Forge.

PatternReader is rewritten rather than translated. Forge parsed the raw
patternAll/pattern NBT elements off the blueprint stack; on NeoForge the
blueprint carries a typed ReactorBluePrintData component, so it now reads
patternAll() directly, falling back to pattern().

Fixes a divergence in ReactorBluePrintMenu that this exposed: on Forge,
pattern is the player's raw grid and patternAll is the normalized view where
empty slots AND non-rod items become glass panes. On NeoForge both fields
pointed at the same array and kept non-rod items. Any stray item left in the
grid would then be counted by PatternReader as a pattern requirement, and
since updateHeatOnly() requires every requirement to be present in the
inputs, heat would have stayed pinned at 0 with nothing to show for it.
saveData now builds the two arrays separately. getItemStorage() keeps
reading pattern() (raw), matching the Forge version which reads the raw
pattern NBT element.

Also drops the duplicated ReactorBluePrintData construction in saveData: both
instances were built from identical inputs, so the follow-up equals() check
could never fire.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ports the reactor's service layer from the Forge branch. Nine of the ten
straight ports are byte-identical; DefaultHeatService differs by one
trailing space.

- ReactorMeltdownMonitor: 300-tick danger countdown driving the stabilized /
  overheating / meltdown-flash / critical-failure notifications.
- ReactorMeltdownExecutor: spawns the explosion sized from reactor footprint
  and fuel count (sqrt for diminishing returns), then irradiates the biome.
- ReactorAlarmCoordinator: drives the alarm blocks off the danger flag and
  awards SILENCE_THE_CORE on the rising edge.
- ReactorHeatUpdateCoordinator: fuel-only gate for canRun() vs full-pattern
  gate for updateHeatOnly().
- FluidConsumptionRateCalculator: coolant drain rate per reactor size.

Neutralizes the copyTag() trap documented in PORTAGE_REACTEUR.md section 5.1.
The Forge code stored heat by mutating configuredPattern.getOrCreateTag() in
place. On NeoForge reading that tag back yields a defensive copy, so a literal
translation would compile, throw nothing, and leave heat pinned at zero
forever. Writing now goes through CNDataComponents.HEAT, which already existed
as a DataComponentType<Float>, isolated in writeHeat(). The empty-pattern
check moves to isEmptyPattern(), testing for the absence of
ReactorBluePrintData. Those are the only non-javadoc differences from Forge.

IPersistenceService and DefaultPersistenceService are deferred to lot 6: they
call block entity accessors that do not exist yet on the NeoForge controller
(Direction vs String facing, BoundingBox vs int[] bounds, inventory
serialization, display state). Porting them now would mean rewriting the block
entity, which is lot 6 itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ports the per-tick input snapshot and the display helpers from the Forge
branch. No behavioural adaptation was needed on this lot: the differences are
the minecraftforge -> neoforged imports and one renamed FluidStack accessor.

- ReactorInputSnapshot: byte-identical to Forge. Immutable per-tick view of
  the loaded rods, coolants and total tank capacity.
- ReactorInputSnapshotBuilder: aggregates the live item handlers and tanks
  once per tick, so heat, tooltip and client sync all read the same numbers.
- ReactorFrameDisplayManager: extracts the frameFluidCache* / frameColumn*
  fields currently inlined in the block entity. Fluid and fill ratio are
  recomputed at most once per game tick from the same tanks, so they cannot
  disagree. The block entity keeps its own copies until lot 6 replaces them.
- ReactorGoggleTooltipRenderer: stateless, reads only from a
  ReactorDisplayState snapshot. Uses getHoverName() since FluidStack lost
  getDisplayName() in 1.21.

Its javadoc example is also corrected: the Forge version showed render() being
called with four arguments while the method takes five.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both files are byte-identical to their Forge counterpart: no API adaptation
was needed. AbstractTickableSoundInstance is unchanged between 1.20.1 and
1.21, and the already-ported ReactorAlarmSoundInstance confirms the shape.

- ReactorRunningSoundInstance: looping client-side sound driven by the ACTIVE
  blockstate added in lot 0. It stops itself when the block stops being an
  active controller, so it survives the controller being broken or the block
  entity's tick no longer driving it.
- ReactorDebugDiagnostics: dumps registered vs. currently valid positions for
  each of the four IO managers.

Adds the eight createnuclear.reactor.debug.* lang keys, which were missing
from lang/default/reactor.json. The nine notification.reactor.* keys that
lot 3's ReactorMeltdownMonitor depends on were already present. runData is
needed to propagate the new keys into the generated en_us.json.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replaces the monolithic NeoForge controller with the Forge one and wires it to
the 33 files ported in lots 0-5. ReactorControllerBlockEntity goes from 868
lines to 424, and its divergence from Forge from 864 lines to 76 - all of them
1.21 adaptations (HolderLookup.Provider on read/write, DataComponents instead
of raw NBT, neoforged imports).

The reactor now actually runs: heat model, overheat escalation, meltdown
countdown with its notifications, explosion, alarms, coolant consumption, rod
consumption cycles, the ACTIVE blockstate driving the running sound and the lit
controller texture, and the goggle tooltip fed from the synced snapshot.

Carried along: ReactorControllerBlock, ReactorAssembler, CNMultiblock,
ReactorPattern, MultiblockHelpers, ReactorOutput(Entity), ReactorOutputManager,
ReactorFrame(Renderer), ReactorCasing, ReactorCooler, ReactorCoreEntity,
ReactorRodInput, the two persistence services deferred from lot 3, and
IMultiblockController (lot 7).

Four features turned out to be missing rather than merely reorganized:

- Only the 5x5 reactor existed. CNMultiblock registered a single pattern with
  the rod input and output pinned to fixed positions; Forge registers three
  (5x5 / 7x7 / 9x9) where IO blocks sit anywhere on the shell. 7x7 and 9x9 were
  simply impossible to assemble.
- ReactorFrameEntity was never registered. The class and its renderer both
  existed, but no BlockEntityEntry did, so frame blocks had no block entity at
  all and the fluid could never render in the windows.
- Fluid capacity was multiplied by the number of inputs.
  applyReactorTierCapacity gave every input the reactor's full capacity, so four
  inputs held four times the intended total. Replaced by applyCapacity(int),
  with the assembler splitting the total as Forge does.
- ReactorOutputEntity drove its speed through a KineticScrollValueBehaviour
  instead of the manager-driven persisted generatedSpeed, which also kept the
  DIR interaction on ReactorOutput that Forge had dropped.

Note for future syncs: in tick(), `int heat = ...` is deliberately a local
shadowing the field, exactly as on Forge. Promoting it to a field assignment
would change the previousHeat value fed to the heat calculation.

CNShapes.REACTOR_INPUT and CNBlockEntityTypes.REACTOR_INPUT keep their NeoForge
names (Forge: REACTOR_ROD_INPUT); renaming them would change the
createnuclear:reactor_input registry id and break existing worlds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ports DefaultHeatCalculatorGameTest and ReactorInputFluidManagerGameTest from
the Forge branch. `./gradlew runGameTestServer` runs 30 tests: 28 pass, 2 fail
by design.

The four heat-model tests pass, including threeByThreeDiamond which is
cross-checked against the community wiki calculator. This is the first runtime
validation of the whole port - everything before this only compiled.

The two failures are the *_expectedContract regression markers, which fail on
the Forge branch too: extractFluids never decrements fluidNeeded between
handlers (over-extraction) and silently drops requests for exactly one unit.
Identical behaviour on both sides, which is what these tests are there to
verify. Note this makes runGameTestServer exit non-zero, so it should not gate
CI until extractFluids is fixed in both repos.

Port adaptations:
- loadPattern writes a typed ReactorBluePrintData (57 PatternData entries)
  instead of the raw `pattern` NBT tag, since the blueprint carries a data
  component on NeoForge.
- Capabilities go through level.getCapability(Capabilities.FluidHandler.BLOCK,
  pos, null) rather than be.getCapability(ForgeCapabilities.FLUID_HANDLER).
- BuiltInRegistries.FLUID replaces ForgeRegistries.FLUIDS, and the annotations
  come from net.neoforged.neoforge.gametest.
- empty_platform.nbt lands in data/createnuclear/structure/ - singular, as 1.21
  renamed the data folders Forge 1.20.1 spelled `structures`.

Also fixes a NeoForge-only bug the suite made obvious: ReactorInputFluidManager
read getFluidInTank(handler.getTanks()), an out-of-bounds index, in both
getInventory() and extractFluids(). Forge reads index 0. Corrected, and a
leftover debug LOGGER.warn in getBlocksPosition() that fired on every call is
removed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit swept in run/world/ (25 region and data files) because the
gametest server writes its scratch world there. Untracked and added to
.gitignore. run/server.properties stays tracked - it already was - but its
gametest-time rewrite is reverted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CNDataComponents.HEAT was registered with ExtraCodecs.POSITIVE_FLOAT, which
rejects 0.0. The controller writes the heat on every tick, so a blueprint
sitting in an idle or stopped reactor made the chunk save throw
"Value must be positive: 0.0" while serializing the stack, and the block entity
stopped persisting entirely.

Zero is the normal heat of a reactor that is not running, so the codec was
simply wrong. 1.21.1 has no ExtraCodecs.NON_NEGATIVE_FLOAT, so this uses plain
Codec.FLOAT, matching the Forge branch which stores the value in an NBT double.
The non-negative invariant stays enforced upstream in
DefaultHeatCalculator#computeHeat, where violating it cannot take a save down: a
persistent codec that can refuse a value fails at save time, far from the write
that caused it.

Introduced in lot 3 when heat storage moved from the blueprint's NBT tag to the
typed data component.

Adds heatComponent_atZero_survivesSerialization, which saves and re-parses a
blueprint carrying heat=0 and would have caught this. The other four components
use NON_NEGATIVE_INT and accept zero, so they carry no equivalent trap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ogen

Three defects reported from in-game testing.

Frame fluid rendered almost black. The renderer passed
fluid.getFluid().defaultFluidState() to renderFluidBox, and the FluidState
overload resolves texture and tint from the fluid's block state, losing the
stack's tint. It now passes the FluidStack, as the Forge branch does.
CatnipServices.FLUID_RENDERER is declared FluidRenderHelper<?>, so the platform
type has to be reintroduced with a cast - that wildcard is why the FluidStack
overload appeared unavailable during the lot 6 port.

Rod input exposed two slots, one hardcoded to uranium and one to graphite. Forge
has a single slot accepting any fuel or cooler rod, resolved through RodType, so
several rod inputs can be placed around the shell to feed different rods. The
inventory drops to one slot, the menu draws one slot at Forge's coordinates, and
quickMoveStack comes over as well.

Liquid nitrogen was commented out of CNReactorFluidTypes, so no nitrogen.json
was generated and the fluid had no registered ReactorFluidType. An unregistered
fluid falls back to efficiency -1, which makes fluidMalus permanently true in
DefaultOverheatController: overheat then climbs without bound and the reactor
always melts down, whatever the rod pattern. Registered with Forge's values
(maxHeat 8196, efficiency 100).

Regenerated data: nitrogen.json, the 16 controller blockstate variants carrying
the ACTIVE property with their off/standby/on models, and the eight
reactor.debug lang keys.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…er layers

Two defects reported from in-game testing.

The reactor output produced 81920 SU where Forge produces 512000. Its stress
capacity was registered at 10240.0 instead of Forge's 64000.0 - the 6.25x ratio
matches the observed numbers exactly. Restored.

The fluid in the frame windows rendered almost black. The frame block was
missing noOcclusion(), so it counted as a full opaque block: light stopped
propagating through it and the fluid rendered inside the window came out at
near-zero light. Passing the FluidStack instead of a FluidState (previous
commit) was necessary but not sufficient - this is the other half.

While checking that, four more blocks turned out to have lost their render
layer relative to Forge, which has five addLayer calls to NeoForge's one:

- reactor_frame and reactor_rod_input and reactor_fluid_input: cutoutMipped
- reinforced_glass: translucent, without which it renders opaque
- enriching_fire: cutout

addLayer is deprecated for removal in this registrate version, but it is what
Forge uses and PalettesVariantEntry already relies on it here, so switching
away from it is left as its own change rather than mixed into a fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Marks the port complete: eight lots done, validated in game, 31 gametests with
the two intentional failures. Adds lot 9, the six defects found while playing,
with which of them the port introduced (one) and which were pre-existing
NeoForge divergences (five).

Adds section 9, a full inventory of what is still not ported. The raw count is
42 Forge files with no NeoForge counterpart, but six of those exist under
another name - two of them only differ by a typo - so the real remainder is 36,
listed by feature rather than by file. Also lists the shared files that diverge
heavily but were never audited, with the caveat that a large diff is not
evidence of a missing feature: most of it is 1.21 API. Lot 9 showed those
diffs do hide real bugs though - the stress capacity and the missing render
layers were both inside CNBlocks.

Records the extractFluids over-extraction bug as present in BOTH versions, so
it gets fixed in both, and warns against gating CI on runGameTestServer until
then.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	src/generated/resources/.cache/35ba5b194a3aa18fb27424c32b9e16e01b900a51
#	src/main/java/net/nuclearteam/createnuclear/CNItems.java
It was tracked at the repo root but missing from the working tree, so the merge
commit recorded a deletion nobody asked for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
extractFluids never decremented fluidNeeded between handlers, so the requested
amount was treated as a per-input quota instead of a total: a reactor asking for
10 units with two fluid inputs holding 10 each drained both, removing 20. The
more inputs the player placed, the faster the coolant vanished.

An `if (toExtract > 1)` guard also silently dropped requests for exactly one
unit, which is what FluidConsumptionRateCalculator asks for at the low end of
the consumption curve, so the smallest reactors leaked their buffer accounting.

The loop now tracks what is left to extract, stops once satisfied, and subtracts
what the handler actually returned rather than what was asked of it.

The Javadoc claimed "true if the full amount was extracted" while the code
returned true on any partial extraction. The code's behaviour is the right one -
the reactor should consume whatever coolant it can reach rather than refuse to
run - so the Javadoc now documents that instead.

The two gametests that pinned the buggy behaviour are removed, and
extractFluids_returnsTrueEvenWhenNotFullyExtracted_javadocMismatch is renamed
now that there is no mismatch. The suite goes from 31 tests with 2 intentional
failures to 29 all passing, so runGameTestServer exits 0 and can gate CI.

The same fix is applied to the Forge branch, which carries the identical bug.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Section 9.4 described extractFluids as a known bug present in both versions; it
is fixed in both now, so it documents what the bug was and what the contract is.
runGameTestServer exits 0 on both branches and can gate CI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Leftover screenshot at the repo root.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PARITE_FORGE_NEOFORGE.md lists every feature still missing from the NeoForge
version, with the hook points to wire each one, a difficulty estimate and a
suggested order.

A raw tree diff reports 42 Forge files with no NeoForge counterpart, which is
misleading. Eight exist under another name - two differ only by a typo - and
four are deliberately not ported because the 1.21 equivalent is better: the
radiation capability plumbing is replaced by NeoForge data attachments,
CriterionTriggerBase by vanilla SimpleCriterionTrigger, and the bundled
SimplexNoise by vanilla's. Re-porting those would be a regression, so they are
called out explicitly. That leaves 30 real gaps, grouped by feature.

The document also records what the reactor port taught us: the method that kept
~35 of 50 files byte-identical to Forge, the 1.21 translation table, and the two
traps that compile and throw nothing - a CUSTOM_DATA tag read back as a
defensive copy, and a persistent codec that fails at world-save time rather than
at the write.

Section 9 of PORTAGE_REACTEUR.md now points at it; that document stays focused
on the reactor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Chantier 1 de PARITE_FORGE_NEOFORGE.md. Porte les 9 fichiers Forge qui
manquaient, et branche la feature qui etait entierement morte cote NeoForge.

Fichiers portes depuis Forge :
- CNDisplaySources
- content/redstone/displayLink/source/ : AbstractReactorStatDisplaySource,
  HeatDisplaySource, FuelDisplaySource, CoolerDisplaySource,
  LiquidLevelDisplaySource, ReactorSizeDisplaySource,
  ReactorDisplayConstants, ReactorGaugeRenderer

Branchements (aucun display source n'etait attache cote NeoForge, pas meme
REACTOR_SUMMARY : la classe existait mais n'etait referencee nulle part) :
- CreateNuclear.java  : CNDisplaySources.register()
- CNBlocks            : les 6 .transform(displaySource(...)) sur
                        REACTOR_CASING et REACTOR_CONTROLLER

ReactorSummaryDisplaySource : fuel / cooler / fluid / heat etaient commentes
faute de ReactorGaugeRenderer. Reactives, ainsi que les branches "gauge" de
formatValue/formatFluid. Le resume n'affichait que statut + taille.

Divergences assumees vs Forge (ne pas les "corriger" a la prochaine synchro) :
- CNDisplaySources : Registrate 1.21 type ses entrees RegistryEntry<R, T>
  (registre + entree) la ou 1.20.1 n'avait que RegistryEntry<T>.
- HeatDisplaySource et ReactorSummaryDisplaySource : Forge lit la chaleur via
  getConfiguredPattern().getOrCreateTag().getDouble("heat"). En 1.21 elle vit
  dans le data component CNDataComponents.HEAT, et relire le tag NBT renvoie
  une copie defensive ou la valeur est toujours absente : le resume affichait
  donc 0. On passe par controller.getConfiguredPatternHeat().
- AbstractReactorStatDisplaySource, ReactorSizeDisplaySource : imports
  net.minecraftforge.api.distmarker -> net.neoforged.api.distmarker.
- LiquidLevelDisplaySource : 8 imports inutilises supprimes (deja morts
  cote Forge).
- FuelDisplaySource, CoolerDisplaySource, ReactorGaugeRenderer,
  ReactorDisplayConstants : identiques a l'octet.

Toutes les cles de lang display_source.* etaient deja presentes dans
interface.json, identiques a Forge. Aucun ajout de ressource necessaire.

Verifie : compileJava OK, 29/29 gametests passent.

A noter, hors perimetre de ce lot : runGameTestServer logue une
IllegalStateException a la sauvegarde du chunk du controleur
("Value must be within range [1;99]: 0; Item must not be minecraft:air"),
depuis SmartInventory.serializeNBT via ReactorControllerBlockEntity.write.
Verifie preexistante (reproduite sur 9e7aba0 sans ce lot). A traiter a part.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…lueprint

PatternData.CODEC utilisait ItemStack.CODEC, qui refuse la stack vide
("Item must not be minecraft:air" / "Value must be within range [1;99]: 0").
Un motif de reacteur fait 57 slots et n'est presque jamais plein : des qu'un
blueprint se trouvait dans un controleur, ReactorControllerBlockEntity.write
levait a la sauvegarde du chunk, et Minecraft desactivait silencieusement la
persistance du block entity ("It will not persist"). Le controleur perdait
donc son blueprint a chaque rechargement du monde.

Meme defaut sur le chemin reseau : PatternData.STREAM_CODEC utilisait
ItemStack.STREAM_CODEC, qui leve EncoderException("Empty ItemStack not
allowed"). REACTOR_BLUE_PRINT_DATA etant networkSynchronized, l'encodage
echouait des que la stack partait vers un client.

Correctif : ItemStack.OPTIONAL_CODEC et ItemStack.OPTIONAL_STREAM_CODEC.
L'encodage des stacks non vides est inchange entre les deux variantes (seule
la stack vide passe de "refusee" a "{}"), donc les blueprints deja sur disque
se relisent sans migration.

Le flatXmap qui entourait ItemStack.CODEC est supprime : sa condition
(!stack.isEmpty() || stack.is(FUEL) || stack.is(COOLER)) etait morte, le
premier disjoint rendant les deux autres inatteignables, et l'ensemble se
reduisait a l'identite. ItemStack.CODEC refusait de toute facon les stacks
vides avant meme que ce mapping ne les voie.

C'est le piege n2 du paragraphe 6 de PARITE_FORGE_NEOFORGE.md, deja rencontre
sur CNDataComponents.HEAT declare en POSITIVE_FLOAT : un codec persistant qui
peut refuser une valeur fait planter la sauvegarde, tres loin de la cause.
Le code compile, ne leve rien a l'ecriture, et perd les donnees.

Verifie : 2 gametests de regression ajoutes (disque et reseau), qui echouent
avant le correctif avec l'erreur de production exacte. 31/31 passent apres,
et runGameTestServer ne logue plus aucune erreur de sauvegarde (5 avant).

Verifie aussi les autres usages de ItemStack.CODEC/STREAM_CODEC du mod :
CNDataComponents.ClothItemStack n'est ecrit que si l'item est un ClothItem,
donc jamais vide ; les encodages de ReactorBluePrintItem/Menu portent sur le
blueprint tenu en main, jamais vide. Rien d'autre a corriger.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- $3.1 Jauges DisplayLink : marque fait (3f99c87), diagnostic d'origine
  conserve en <details> et complete par ce qu'il sous-estimait (la feature
  etait morte, pas incomplete) ainsi que par les deux bugs trouves dans
  ReactorSummaryDisplaySource et la resolution du doublon ReactorGaugeOverrides.
- $6 piege n2 : ajoute le cas PatternData / ItemStack.CODEC (a8d1f1b), et la
  lecon associee : les gametests ne rechargent pas le monde, l'erreur ne
  sortait que dans le log sous un 'All 29 tests passed'.
- Journal des chantiers, compte de fichiers et nombre de gametests mis a jour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Les deux depots portaient 8 fichiers identiques sous des noms differents, ce
qui obligeait a les traiter comme des faux positifs a chaque diff d'arborescence
(section 1 de PARITE_FORGE_NEOFORGE.md). Ils sont desormais nommes pareil des
deux cotes.

Sept renommages cote NeoForge, vers le nom Forge :

  content/rod/CNRodTypes            -> content/multiblock/rod/CNRodTypes
  foundation/damagesTypes/          -> foundation/damageTypes/
  CNArmorMaterials                  -> ArmorMaterials
  CExplose                          -> CExplode
  PlayerInteracteReactorFluidInput  -> PlayerInteractReactorFluidInput
  IrradiatedCatLieOnBedGoal         -> CatLieOnBedGoal
  IrradiatedCatSitOnBlockGoal       -> CatSitOnBlockGoal

Le cas damagesTypes n'etait pas qu'un nom : le fichier declarait deja
"package ...foundation.damageTypes" alors qu'il vivait dans un dossier
damagesTypes. Dossier et package se contredisaient ; seul le dossier bougeait.

Le huitieme ecart est traite dans l'autre sens, dans le depot Forge : c'est
Forge qui ecrivait BiomeIrradationExtractorItem, NeoForge avait deja
l'orthographe correcte. Renommer NeoForge aurait propage la faute.

Trois des nouveaux noms masquent une classe vanilla homonyme : ArmorMaterials
(net.minecraft.world.item), CatLieOnBedGoal et CatSitOnBlockGoal
(net.minecraft.world.entity.ai.goal). Les fichiers concernes importent ces
packages en wildcard, mais une classe du meme package l'emporte sur un import a
la demande, et aucun de ces fichiers n'utilise la classe vanilla homonyme.
Forge compile ainsi depuis toujours. A savoir si l'un d'eux a un jour besoin de
la version vanilla : il faudra la qualifier completement.

Verifie : compileJava passe sur les deux depots, et le diff d'arborescence de
la section 7 ne liste plus ces 8 fichiers. Aucune ressource touchee (aucune
reference a ces noms hors .java).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Chantier 2 de PARITE_FORGE_NEOFORGE.md (section 3.8) : RodsStats,
RodsTooltipHandler, UraniumOreItem et CNOpenPipeEffectHandlers. Les quatre sont
portes a l'identique de Forge, seule l'API 1.21 change (ItemTooltipEvent passe
sous net.neoforged, ForgeRegistries.ITEMS.getKey -> BuiltInRegistries.ITEM.getKey,
@Mod.EventBusSubscriber -> @EventBusSubscriber sans attribut bus, le bus GAME
etant le defaut en NeoForge et l'attribut etant deprecie).

CNOpenPipeEffectHandlers etait le fichier important. RadiationEffectHandler
existait deja cote NeoForge, complet et correct, mais n'etait reference nulle
part faute de ce point d'enregistrement : un tuyau ouvert Create crachant de
l'uranium n'irradiait donc personne. C'est le meme schema que les jauges
DisplayLink du chantier 1 -- une feature entierement morte plutot qu'incomplete.
Deux cas en deux chantiers : cote NeoForge, chercher systematiquement les
classes jamais referencees.

Deux ecarts avec le diagnostic de la roadmap, trouves en portant :

  - trois blocs portent UraniumOreItem cote Forge, pas deux. La roadmap citait
    uranium_ore et deepslate_uranium_ore (radiation 3), et oubliait
    raw_uranium_block, dont la radiation vaut 27. Les trois sont branches.
  - la cle de lang tooltip.ratio manquait cote NeoForge, alors que les cinq
    autres cles tooltip.* des barres etaient deja presentes. Sans elle,
    RodsStats aurait affiche la cle brute. Ajoutee dans lang/default et
    regeneree via runData (en_us et en_ud).

RodsStats et RodsTooltipHandler ne font pas doublon, malgre les apparences :
RodsStats est branche sur setTooltipModifierFactory de Registrate, qui ne couvre
que les items du mod ; RodsTooltipHandler ecoute ItemTooltipEvent et ne sert que
les items externes (autres mods, ou RodType definis par datapack et resolus au
runtime), d'ou son return anticipe sur le namespace createnuclear. Supprimer
l'un des deux ou inverser la condition double le tooltip des barres du mod. Le
commentaire qui le dit est present dans le fichier, des deux cotes.

Verifie : compileJava passe, runData ne produit que la cle de lang ajoutee
(aucune regeneration de modele malgre le passage de .item() a
.item((b, p) -> new UraniumOreItem(...))), et 31/31 gametests passent sans
aucune erreur dans le log complet.

Non verifie -- aucun gametest ne couvre ces trois comportements, ils demandent
un test en jeu :
  1. tooltip d'une barre : cinq lignes de stats, type en vert (FUEL) ou cyan ;
  2. raw_uranium_block en inventaire : la radiation monte de 27 par item ;
  3. tuyau ouvert crachant de l'uranium : irradie les entites de la zone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Les depots ont ete transferes sous l'organisation Create-Nuclear-Team, mais
plusieurs liens pointaient encore sur les comptes personnels d'origine. GitHub
redirige, donc rien n'etait casse -- mais une redirection cesse de fonctionner
des que quelqu'un recree un depot sous l'ancien nom.

Le cas le plus genant n'etait pas le proprietaire mais le depot lui-meme :
issueTrackerURL de neoforge.mods.toml pointait sur CreateNuclearForge, pas sur
CreateNuclearNeoForge. Les rapports de bug des joueurs NeoForge, ouverts depuis
l'ecran de crash du jeu, atterrissaient donc dans le tracker de la version
Forge. Corrige en meme temps que le proprietaire.

Le README de CreateNuclearForge etait deja correct sur les trois liens : c'est
lui qui a servi de reference.

Non modifie volontairement : le CHANGELOG (ce sont des liens vers des commits
et des PR historiques, les reecrire falsifierait l'historique) et les rapports
de crash archives dans run/, qui citent l'URL en vigueur au moment du crash.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Le fichier accumulait le fait et le restant : sections barrees, encadres
"FAIT le ...", diagnostics d'origine replies dans des <details>, journal des
chantiers. A la lecture, il fallait trier avant de savoir quoi faire.

Desormais il ne contient que du travail restant. Ce qui est fait en sort --
l'historique est dans les commits, qui sont plus fiables qu'un journal tenu a
la main.

Supprime : le journal, la section des 8 fichiers renommes (les noms sont
alignes des deux cotes), les chantiers Jauges DisplayLink et Divers, et les
lignes barrees de l'ordre de travail.

Conserve, mais deplace, ce qui n'etait pas "du travail fait" mais du savoir
utile pour la suite :

  - la lecon des deux chantiers -- une feature peut etre presente mais morte,
    faute de point d'enregistrement -- passe en encadre du paragraphe 0 et en
    premiere etape de la methode, avec le grep qui la detecte ;
  - le fait que trois classes du mod masquent une classe vanilla homonyme
    (ArmorMaterials, CatLieOnBedGoal, CatSitOnBlockGoal) rejoint les pieges du
    paragraphe 6 : ce n'est pas une tache faite, c'est une propriete du code
    actuel qui peut mordre plus tard ;
  - les deux cas de codec persistant trop strict sont fusionnes en un seul
    piege, avec les deux exemples.

Ajoute un paragraphe 4 "Verifications en jeu en attente" : la radiation et les
trois comportements livres au chantier 2 ne sont couverts par aucun gametest.
C'est du travail restant, meme si ce n'est pas du portage -- le supprimer avec
le reste du chantier l'aurait perdu.

Renumerotation en 0-7 sans trou. Les messages de commit anterieurs a celui-ci
referencent l'ancienne numerotation (section 1, section 3.1, section 3.8).

Verifie : la somme des fichiers des six chantiers fait bien 17, le diff
d'arborescence en donne 21 (17 + les 4 a ne pas porter), et aucune reference
ne pointe vers une section supprimee.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… HolderSet

RodType previously stored a HolderSet<Item> so a rod type could map to
multiple items; in practice each type only ever mapped to one, so the
API/data model is simplified to a single Holder<Item>.

- RodType: replace `items: HolderSet<Item>` with `item: Holder<Item>`,
  switch codec from RegistryCodecs.homogeneousList to
  RegistryFixedCodec, and update isNotEmptyItem()/toString()/
  getTypeForItem() accordingly
- RodType.Builder: replace addItems(ItemLike...) with item(ItemLike),
  add explicit type(TypeRod) setter, drop the redundant *Set boolean
  flags in favor of null checks in build(), default type to NONE
- TypeRodPredicate: drop the non-level-aware IS_FUEL/IS_COOLED
  predicates and tag shortcuts; isFuel/isCooled now always resolve
  the rod type through the level's registries
- ItemRodTypesValue: build DEFAULT_ROD_TYPE from Items.AIR's holder
  instead of an empty HolderSet, update setRodTypeInfos() to use
  builder.item(item) instead of addItems(item)
- ReactorInputManager: switch fuel/cooler checks to the level-aware
  isFuel(stack, level)/isCooled(stack, level) methods
- Regenerate fallback.json rod-type data to use "item":
  "minecraft:air" instead of "items": []
…FluidType

Following the RodType refactor, apply the same "one item per type"
simplification to ReactorFluidType, and centralize the fallback
registry keys.

- ReactorFluidType: replace `fluids: HolderSet<Fluid>` with
  `fluid: Holder<Fluid>`; switch codec from
  RegistryCodecs.homogeneousList to RegistryFixedCodec; simplify
  getTypeForFluid()/isNotEmptyFluid()/toString() accordingly
- ReactorFluidType.Builder: single fluid(Fluid)/fluid(FluidStack)
  setters instead of accumulating a list; drop the unused
  setRodConfig()/useConfig runtime-config override in maxHeat()/
  efficiency() (build() now always requires maxHeat and efficiency)
- CreateNuclearRegistries: introduce FALLBACK_ROD and FALLBACK_FLUID
  ResourceKey constants, replacing the ones previously declared in
  CNRodTypes.FALLBACK and CNReactorFluidTypes.FALLBACK
- CNRodTypes / CNReactorFluidTypes: remove the now-relocated FALLBACK
  ResourceKey fields
- RodType: point resolveRodType() at
  CreateNuclearRegistries.FALLBACK_ROD instead of CNRodTypes.FALLBACK
- ReactorFluidTypesValue: build DEFAULT_REACTOR_FLUID_TYPE from
  Fluids.EMPTY's holder instead of an empty HolderSet
- MultiBlockManagerBeta: widen findStructure()'s parameter from the
  concrete ReactorControllerBlockEntity to the IMultiblockController
  interface
- Regenerate fluid-type data (fallback.json, nitrogen.json,
  water.json) to use the singular "fluid" field instead of "fluids"
…ult bus

@EventBusSubscriber previously pinned the class to the GAME bus
explicitly; switch to the default bus argument so the class's
listeners register on NeoForge's default mod event bus instead.

- RadiationCapability: drop `bus = EventBusSubscriber.Bus.GAME` from
  the @EventBusSubscriber annotation, keeping only `modid`
Giovanniricotta2002 and others added 3 commits August 8, 2026 15:45
… foundation to the api package

Relocate the addon-facing recipe generator base classes into the
`api.data.recipe` package (mirroring how other API surfaces of the
mod are organized), restore EnrichedRecipeGen's full
StandardProcessingRecipeGen<EnrichedRecipe> implementation, and wire
up the new camera/renderer client mixins for the radiation-effect
rendering hook.

Staged:
- Move SnowPowderRecipeGen from
  foundation/data/recipe → api/data/recipe (package updated)
- Delete the old foundation/data/recipe/EnrichedRecipeGen.java (now
  superseded by the moved/rewritten api/data/recipe/EnrichedRecipeGen.java)

Not staged:
- EnrichedRecipeGen (api): extend
  StandardProcessingRecipeGen<EnrichedRecipe> again instead of the
  raw ProcessingRecipeGen, restoring the convert(...) helper methods,
  CNRecipeTypes.ENRICHED wiring, and class-level Javadoc pointing at
  CNEnrichedRecipeGen/CNRecipeProvider
- CNEnrichedRecipeGen / CNSnowPowderRecipeGen: update imports to
  reference the relocated api.data.recipe base classes
- CNRecipeProvider: drop now-unused imports (Create, ProcessingRecipe,
  ProcessingRecipeBuilder, CreateRecipeProvider, IRecipeTypeInfo,
  CatnipServices, ResourceLocation, Ingredient, ItemLike,
  CreateNuclear, Supplier, UnaryOperator) left over from the recipe
  gen cleanup
- createnuclear.neoforge.mixins.json: register the two new client
  mixins, client.CameraAccessor and client.GameRendererMixin,
  alongside the existing RadiationHeartMixin and
  AntiRadiationArmorTextureMixin
- PARITE_FORGE_NEOFORGE.md: update the Forge→NeoForge parity notes —
  mark CriterionTriggerBase as resolved (now tracks Create's own
  evolution instead of vanilla SimpleCriterionTrigger), note that the
  Alex's Caves compat hook doesn't apply since that mod isn't
  available on 1.21.1, and note that comparing mixins.json is
  mandatory given Minecraft's internal changes between 1.20.1 and 1.21.1

Untracked (new files, not yet staged):
- compat/Mods.java and compat/alexscave/AlexscaveCompat.java: initial
  mod-compat scaffolding (mod-loaded checks and an Alex's Caves
  compatibility handler)
- foundation/mixin/client/CameraAccessor.java and
  foundation/mixin/client/GameRendererMixin.java: new client mixins
  backing the mixins.json registration above, for camera/renderer
  access per the radiation-effect rendering parity notes
… dose/contagion model

Radiation damage sources (campfire, fan enriching, the RADIATION mob
effect) previously applied the effect/damage directly; they now go
through RadiationCapability's dose-based contagion system, and the
fan_radiation damage type (previously commented out) is wired up and
tagged as NO_KNOCKBACK alongside RADIATION.

- RadiationEffect: extend the new VicinityEffect base instead of
  MobEffect directly, adding a contagion radius/duration and an
  onContaminate(...) hook that calls
  RadiationCapability.applyContagion(...) on nearby entities;
  applyEffectTick() now defers to super, computes damage from
  RadiationCapability.getRadiationResistance(...), and deals it via
  CNDamageSources.radiation(...) instead of livingEntity.damageSources().magic()
- EnrichingCampfireBlock: entityInside() now calls
  RadiationCapability.applyContagion(livingEntity, CAMPFIRE_DOSE, 100)
  instead of directly adding a RADIATION MobEffectInstance
- CNFanProcessingTypes.EnrichedType: affectEntity() now applies a
  contagion dose via RadiationCapability.applyContagion(...) and
  separately deals 1 point of fan_radiation damage via
  CNDamageSources.fanRadiation(level), instead of only adding a
  RADIATION MobEffectInstance
- CNDamageSources: un-comment and enable fanRadiation(Level), which
  was previously dead code
- CNDamageTypes: clean up radiation(Level) to use imported DamageSource/
  Level types instead of fully-qualified names (no behavior change)
- Add CNDamageTypeTagsProvider: a plain TagsProvider<DamageType>
  (not a Registrate dynamic tag generator, since DamageType entries
  only resolve through the datapack-registry HolderLookup.Provider)
  tagging RADIATION and FAN_RADIATION under DamageTypeTags.NO_KNOCKBACK
- CreateNuclearDatagen: register the new CNDamageTypeTagsProvider as
  a server data provider
- CameraAccessor: widen callMove(...)'s parameters from double to
  float, matching the updated Camera#move signature
- GameRendererMixin: update the @Inject target from the old
  render(FJZ)V signature to the current
  render(Lnet/minecraft/client/DeltaTracker;Z)V signature, and read
  the partial tick via deltaTracker.getGameTimeDeltaPartialTick(false)
  instead of a raw float parameter
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.

1 participant