Commit Graph

29 Commits (8e5178ece690988bda79a1681726bbb7208637d7)

Author SHA1 Message Date
Amir Alexander Abdelbaki 8e5178ece6 A hue per object, and faces that cannot be confused with their opposite
Untextured, every model came out the same red. The hue was the box's index
within its model and almost every model here is one box, so a scene of eight
was eight objects sharing hue zero - the one thing the colour was supposed to
tell you was the one thing it did not. So `face_colour` takes a hue now, and
the quickstart hands each model its own: successive golden-ratio turns around
the wheel, so any two are far apart and the third is not squeezed between them,
indexed on the model's place in the whole list rather than on what happens to
be visible. The boxes of one model spread a tenth of a turn either side, near
enough to read as one object and far enough to count. Left alone the hue is
what it always was - the wheel split per shape - so ARMOUR_GUI and the unwrap
templates are unchanged.

The shades were the other half. Six faces were sharing three saturations, and
east and west were separated by nothing but a fifth of a value - which is the
worst place to be thin, because opposite faces are the pair you can only ever
see one of at a time and so have nothing to compare against. Each face now sits
at its own rung of both ladders, and each opposite pair is far apart on both:
up is pale and brightest against a down that is vivid and darkest, north vivid
and bright against a washed, dim south, west the palest against a vivid,
mid-dark east.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 21:23:45 +02:00
Amir Alexander Abdelbaki 44bd5e597c model adjustments 2026-08-24 21:17:12 +02:00
Amir Alexander Abdelbaki 6dba9d9f24 Two buttons that write: the transform back, and the UVs afresh
The quickstart could show that a model was in the wrong place and then leave you
to retype the number somewhere else. `Write to source` closes that: the panel's
translation and rotation go back into the module entry they were read from,
with its scale and its origin left as the module had them, because placement
lives in the module rather than in the geometry. A model no module names - a
sword part, an icon of its own - has nowhere to write to and says so instead of
guessing at a file. Whole numbers stay whole, so an axis a drag never touched
comes back reading `0` rather than `0.0`.

`Unwrap UVs` is the other half, and it is the same unwrap ARMOUR_GUI does when
it saves: every face gets a rectangle of its own, the atlas size is written
beside the boxes, and the faces keep the texture key they already name rather
than being pointed at `#0`. A template is painted to match only where there is
no texture yet - a guide is scaffolding, and overwriting art someone has
painted because the boxes moved would be the tool destroying the work it exists
for. A sprite has no boxes to cut, and says that rather than writing an empty
atlas.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 21:01:16 +02:00
Amir Alexander Abdelbaki 9449af92b1 Placeholder bezels for the small and great cases
The medium case has had art of its own since the model adjustments - a bezel
around an empty socket, on the greys a material's palette maps - even though
the module wears MIAPI's gemstone model at the moment. The two new sizes had
nothing at all, so here are their blanks, drawn to the same rules: the top and
left edges catch the light, the right edge is the shadow, the bottom is the mid
tone, and the socket in the middle stays the darkest stop so it reads as empty
whatever is in it. Three pixels across for the small, six for the great, either
side of the medium's four.

They are placeholders in the sense the templates are: the right size, the right
palette, and something to draw over rather than finished art. Each gets the
model beside it that the medium one has, so wiring a case to its own picture is
a path change in the module and nothing else.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 20:55:25 +02:00
Amir Alexander Abdelbaki 1f1ca94a17 An Apothic case in every gem size, weapons included
A gem slot takes what its `allowed` list names, and until now the case named
only `gem_armor_medium` - so Apotheosis could be socketed into a chestplate and
not into a sword, though Apotheosis itself sockets gems into either. The
generic case now names `gem_melee_medium` beside it and carries the matching
`gem_melee` tags, which is the whole of what makes it fit the socketed pommel.

Size is the other half of fitting, and a case that is one size fits one slot.
So there are three: the medium one that was here, a great case for the
`gem_melee_large` slot the great pommel offers, and a small case for
`gem_melee_small` - Arsenal's own socket pommel, and the dual socket guard that
carries two of them. The small one names `gem_armor_small` as well, because a
small slot is a small slot wherever it was cut and a key nothing asks for costs
nothing. Each wears MIAPI's gemstone model at its own size, as the medium one
has since the limb gems went in.

`ApothicSockets` counts cases rather than pieces, so the new ids go on its
list and nothing else changes: a sword with a case in its pommel reports one
socket the way a chestplate does, and a guard holding two small cases reports
two. All three are filed under `packs/apotheosis` rather than with the pommels,
because a case without Apotheosis is an item that grants nothing, while a case
without Arsenal is one that simply never fits anywhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 20:53:30 +02:00
Amir Alexander Abdelbaki e7b211932f A viewport that draws, and a front door that opens with no arguments
The armour tools would not start and then would not draw, for two reasons that
looked like one. pyvista and pyvistaqt are packaged by hardly any distribution,
so `guiplatform` now keeps a virtualenv at `tools/.venv` and re-runs the tool
inside it when the interpreter it was started with cannot find the stack -
built with `--system-site-packages`, so the distribution's Qt stays the Qt in
use. Only an absent module sends us there; a PySide6 that will not load is a
system problem the venv shares, and it still gets its own message.

The black viewport was the other half, and it was ours. Handing pyvistaqt a
`vtkGenericOpenGLRenderWindow` is the right idea for VTK's C++ widget and wrong
for this one: the Python `QVTKRenderWindowInteractor` paints from `paintEvent`,
never implements `paintGL`, never binds Qt's framebuffer and never makes a
context current, so the render window it was given drew into no context at all.
Nothing raised - VTK would not even read its own buffer back. pyvistaqt makes
its own render window now, which is the only one it knows how to drive, and the
probe builds its viewport the same way the tools do rather than a way that
always failed. That probe had been timing out on every launch for the same
reason, so Wayland could never be chosen anywhere; it now says what actually
went wrong, X protocol errors included.

ARMOUR_QUICKSTART is the front door: no arguments, every model in the tree -
worn armour, icons, sword parts, the loose item models - listed with the first
one showing, and a jar's models listed after ours when one is passed, because a
socket is geometry cut into a plate that lives in Armory's jar. Where each
model goes comes from the mod's own module data, since the `origin` a module
names is the part MIAPI draws it under; without Armory's slot transforms a worn
model is flipped onto its part but not offset along it, and it says so.

The left panel is that model's MIAPI transform. Three arrows on the model's
origin move it, a toggle swaps them for three rotation rings, and the numbers
are the ones a module writes - a drag is unwound back through the pivot and the
flip before it reaches them, so what the panel reads is what goes in the JSON.
Nothing here writes: the number is what you leave with.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 20:53:07 +02:00
Amir Alexander Abdelbaki 7442ee7ac7 A front door for the armour editor, and names that say what to run
Running it needed a scene and a jar path known in advance, which is a poor way
to meet a tool. With no scene named it now asks: the four scenes listed with
their own docstring's first line as the description, so the blurb cannot drift
from what the scene builds, and the sources pre-filled with the repo's own
resources first and Armory's jar after, found by looking where the pack's jars
actually live. Textures, wearer, material variant and gem size are there too.

The dialog is Qt and nothing else. The viewport it leads to is a plotter with a
window of its own, so there is no shared GL context to get wrong and no reason
to sit through the Wayland probe before a scene has been picked; the
QApplication is shut down before the viewport opens rather than left to run a
second event loop. `--shot` still insists on a scene, because it renders
without a window and an unattended render should not stop to ask.

Widgets carry object names. That began as a way to drive them from a test after
addressing them by index quietly drove the wrong ones, and it is right anyway -
an index moves the next time a row is added above it.

The three tools you start are ALL CAPS now and the libraries are not, so the
directory says which is which. main() also stops adding the repo's resources a
second time when the dialog has already listed them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 16:18:33 +02:00
Amir Alexander Abdelbaki 742705839d Gems that stay on the limb they were cut into
MIAPI draws a module's model in the pass its own origin names, and positions it
from the entry of the transform stack filed under that same name - a model with
no origin is drawn only for the inventory icon, from the entry a transform lands
in when it names none. Armory's gemstone names no origin, so it was never drawn
on worn armour by any route, and a slot transform aimed at the body reached
neither the limb nor the icon.

So the gems are ours now: one per limb, tagged so a left pauldron gem cannot go
in a knee and be drawn on the wrong arm, and an Apothic case beside each on the
same model and placement. The cases keep counting one Apotheosis socket apiece -
ApothicSockets already listed these module ids, so the Java is unchanged and two
armed cases still make two sockets on the chestplate.

Each carries two model entries, because one transform cannot aim both places: a
limb entry drawn under the part's animated pose, so it follows the walk cycle,
and an origin-less entry with its own offset for the icon. The limb placements
were solved rather than tried - the target taken in body space, Armory's limb
chain inverted, the result decomposed - and land on the pauldron face and the
knee to the pixel. The icon offsets sit on the pauldron boss and the knee band
of each of the four sprites.

The sockets themselves are drawn here too, under the limb origin, so there is a
setting for the stone to sit in. Their textures are unwrap templates for now and
want painting. The Apothic case wears the gemstone's model rather than its own
art, its materials unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 15:12:58 +02:00
Amir Alexander Abdelbaki 5e1b62fd9b Tools that draw the armour instead of guessing at it
Five of them, sharing one geometry layer. mcmodel.py reads Minecraft and MIAPI
models out of jars and implements both of the game's UV conventions separately -
entity cubes for vanilla armour, JSON model faces for every module - because
they disagree on four of six faces, which survives an eyeball on a symmetric
breastplate and then ruins a pauldron.

armour_editor.py places modules and writes the numbers back into the JSON they
came from. It replaces what preview_armour.py did and fixes what that got
wrong: sorting whole faces by average depth is not a depth buffer, and a gem
half-sunk into a plate was sorting arbitrarily - exactly the case the tool
existed to judge. VTK rasterises against a real z-buffer instead.

armour_gui.py draws new geometry onto a body part, with the origin settable,
since that is what decides which part a model is drawn under and therefore what
it moves with. It unwraps as it goes and writes a painting template beside the
model. The untextured view colours a hue per shape and a shade per face, and
the template uses the same key, so one is the legend for the other.

material_editor.py edits materials.py by rewriting one argument's source span
at a time, so ingots(...) stays a call, the comments stay put, and a two-number
change is a two-number diff. The generated JSON is downstream and would be
overwritten, so it is not what gets edited.

guiplatform.py prefers Wayland and falls back to X11. VTK's wheels have no
Wayland backend, but handing Qt the GL context works on both; where that fails
the process dies on an X BadAccess that nothing in-process can catch, so the
question is asked in a subprocess that renders the same features the tools use.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 15:12:31 +02:00
Amir Alexander Abdelbaki 532c3a0da8 model adjustments 2026-08-18 17:50:53 +02:00
Amir Alexander Abdelbaki 87a78136ec The fang's element, the core's new corners, and gem slots for limbs
Sea serpent fang was a fang anywhere: one of them, socketed, worn or held,
and you breathed underwater. It is an armour material now, and worth
something different in hand.

Worn, it grants half a lung per module in each armour slot and the handler
still only reads 1 as breathing, so it takes two worn fang modules - two
sockets, two pieces built of it, or a socketed piece that is both. That is
the trade the space set already makes, and a fang weapon no longer makes it
at all.

Held, it does what a serpent does in its element. Mining needs nothing of
ours: vanilla models underwater digging as an attribute of its own,
minecraft:player.submerged_mining_speed, base 0.2 for the fifth-speed
penalty, and +0.6 a module puts a single fang module four times past a plain
tool and two of them past dry land. Damage and swing speed have no such
switch, so WaterCombat.java registers cmmodular:water_combat the way
FireImmunity and WaterBreathing register theirs. Damage is answered on the
blow. Attack speed cannot be - the game reads it off the attribute map
whenever it likes - so a transient modifier under one id goes on and comes
off as the bearer enters and leaves the water.

The arcana core's recipe puts a trophy from each corner of the game in the
corners of the grid - nether star, dragon's breath, heart of the sea and
Iron's Spells' ancient knowledge fragment - with two cinder and two arcane
essence on the edges and nothing in the middle.

Armory sockets a helmet, a chestplate, a backplate and a belt and stops
there, so an arm or a leg could never hold a gem. Four pieces here finish it:
the heavy pauldron and the heavy pants, left and right, inheriting Armory's
own whole and adding the same gem_armor_medium slot at the same price, 5% of
the piece's armour. Where the gem sits is the other half - out on the
shoulder in the pauldron, forward on the knee in the pants - and the numbers
come from Armory's own art, which lies a sprite proud of the side of an arm
at 2.15 and of the front of a body at 2.35.

Last, a gem case, for anyone running Apotheosis. Its gems are not modules and
never can be, since what makes one worth having is its own rarity and purity
data, but Apotheosis counts sockets in a plain data component and MIAPI's
component property sets any component a module likes. So the case is one line
of data: put it in an armour piece's gem slot instead of a gemstone and the
piece reports an Apotheosis socket, which the gem goes into at a smithing
table. Modular armour extends vanilla ArmorItem, so Apotheosis' LootCategory
already calls it a helmet or a chestplate, and gem bonuses arrive through
ItemAttributeModifierEvent, where MIAPI is not looking. The pack is gated on
Apotheosis and Armory both, which is why a pack can now name more than one
mod.

None of this is compiled or run - there is no JDK on the machine it was
written on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 09:46:59 +02:00
Amir Alexander Abdelbaki 3f3b4bdef6 further update 2026-08-15 01:40:59 +02:00
Amir Alexander Abdelbaki 5098b5617a Make the near-invisible pixels in the drawn art opaque
Eight pixels of the khopesh blade and five of the great socketed pommel
were saved with an alpha between 1 and 27 out of 255. They have colours
- white, greys, near-black - and were clearly drawn rather than erased,
but at under 11% opacity they read in game as holes in the edge of the
blade. A soft or part-opacity brush is the likely cause; nothing else in
the mod's art has an alpha that is neither 0 nor 255.

Their colours are untouched. Only the alpha moves, to 255.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 15:50:46 +02:00
Amir Alexander Abdelbaki 891ea12338 Name materials with the lang key MIAPI actually reads
Every material showed as miapi.material.cmmodular.metal.arcane_metal in
game rather than as Arcane Metal. The name was being written as a
`translation` field on the material, which MIAPI reads only when a
`fake_translation` sits beside it - the two are a pair that registers a
name for a key at runtime, and alone the first does nothing. MIAPI's own
materials carry no such field: a material is named by the lang key
miapi.material.<namespace>.<group>.<name>, which is its id with the
separators turned into dots, and MIAPI says so in the warning it logs
about it.

So the generator writes those keys into the mod's lang file, and the
dead field is gone from all 128 materials. The value is the bare name -
the space before the module's own name is MIAPI's to add, which is why
the old field carried a trailing space that no longer belongs.

Names are written for every material, not only the ones a jar was found
for, so generating against a partial set of jars cannot strip the rest
of the file. Hand-written keys - modules, attributes, the arcana core -
are matched by prefix and left alone.

Includes the redrawn great socketed pommel texture.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 15:47:02 +02:00
Amir Alexander Abdelbaki 85eee5aeec File material extensions under their own name
An extension was written to the same path as the material it extends,
one pack over. A datapack file is identified by its path, so that never
added an extension to anything: it replaced the material with a stub,
whichever of the two packs loaded last. The stub then named itself as
its own parent, which MIAPI reads as a cycle and drops, and the material
was gone entirely for anyone with both mods installed:

  Unresolved extension cmmodular:metal/arcane_metal for target
  cmmodular:metal/arcane_metal (missing or cyclic dependency)

That is arcane metal and mithril, the two materials Create: Wizardry
adds forms of, and it explains why they worked before the extensions
existed. Ice and Fire's amethyst was never affected - it extends a
miapi: material, so its own cmmodular: path collided with nothing.

The generated name carries the pack that owns the extension, since the
pack is why the extension exists and there could be another from a
different mod later. Nothing else about the files changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 15:33:05 +02:00
Amir Alexander Abdelbaki 766d8afe2b khopesh texture 2026-08-14 15:19:24 +02:00
Amir Alexander Abdelbaki 1f0b4c5848 Stop build.sh cleaning away the jar it just built
Arguments were appended to the default task, so build.sh clean build ran
gradlew build clean build. Gradle folds that back into build and clean,
in that order, and the run ended by deleting the jar - with the script
then reporting the jar was missing, which was at least honest. The
README had claimed the invocation worked; nobody had run it.

A named task now replaces the default instead of being appended to it.
Flags are told apart from tasks by their leading dash, so --info still
gets a task to attach to rather than running gradlew with nothing to do.

The closing jar report is now conditional on a task that produces one
having run, since build.sh clean properly ends with no jar and should
not call that a failure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 15:04:57 +02:00
Amir Alexander Abdelbaki 9239f03fe0 Call the arcane metal what it is
The material was named Arcane Alloy on the grounds that Create: Wizardry
ships a resource pack renaming the ingot to that. It does not, and there
is no arcane alloy in any of these mods to rename it to. Iron's Spells
calls the item an Arcane Ingot and the material plain Arcane, in the
tetra.material lang keys it already ships.

It is Arcane Metal here rather than Arcane, because a material is named
for the substance and not the shape it is traded in, and because plain
Arcane belongs to the essence, which is the one of the two with no other
name to fall back on. So the pair now reads Arcane and Arcane Metal.

The generated JSON is edited in place rather than regenerated: the only
field that moves is the translation, and no jar of Create: Wizardry is
around to sample against - regenerating would have gone and rewritten
palettes off whatever jars are to hand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 15:02:35 +02:00
Amir Alexander Abdelbaki 5411797799 Add a build script that picks a JDK Gradle can run on
A plain ./gradlew build fails outright on a machine whose default java
is newer than 23, which is now an ordinary thing for a distro to ship.
Gradle 8.12 cannot run there, and what it says about it is that it
could not create task ':test' because type T is not present - nothing
about Java, and nothing to act on.

build.sh picks a JDK between 17 and 23 and builds with that. JAVA_HOME
wins if it already points at a usable one, otherwise the openjdk paths
are tried in turn; if there is nothing to pick it says so and names the
range, rather than leaving the Gradle error to be decoded. Arguments
pass through, so it is a wrapper around the wrapper and not a second
way to describe the build.

It reports the jar by reading mod_id and mod_version out of
gradle.properties instead of listing build/libs, which keeps old
versions sitting in that directory from being mistaken for the build
that just ran.

The toolchain is untouched by any of this - the mod still compiles
against Java 21, whatever Gradle itself is running on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 14:50:45 +02:00
Amir Alexander Abdelbaki cd8993ea25 Recolour the real heart of the sea for the arcana core
The texture was drawn to that silhouette rather than taken from it,
because there was no copy of the vanilla assets on hand. There is now:
the item texture is Minecraft's own heart of the sea with its hue
rotated from cyan into violet and nothing else touched, so the shading
is the vanilla item's.

That makes it the one piece of art here that is not drawn for this mod,
which the README now says next to the khopesh paragraph that takes the
opposite line. It also means the palette the material hands out is a
deep one with almost no highlight - the heart of the sea is a dark item,
and the sampler reports what a texture looks like rather than re-lighting
it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 12:07:57 +02:00
Amir Alexander Abdelbaki 29f78fa721 Sand phasing, fire on the strike, faster swimming, and the arcana core
Deathworm chitin moves through sand the way the worm does. Collision is
asked of the block, with the entity only along for the ride in the
collision context, so this is the one thing an attribute and an event
cannot carry between them and the mod's first mixin - two lines into
BlockStateBase, with SandPhasing.java owning the decision. Which blocks
count is the cmmodular:sand_like tag: the common c: sand, gravel and
concrete powder tags, each optional, plus the vanilla loose blocks by
name. Sand you stand inside cannot suffocate you.

It does not drop you through the desert. A block wholly below the feet
still holds you up, so sand stays something to walk on and a dune
becomes something to walk into - step in and you keep your level, jump
and the sand under you turns solid and puts you back on top. Without
that the first sand block you stood on would swallow you down to the
sandstone and leave you there.

Dragon bone burns what it hits, as Ice and Fire's own flamed dragonbone
sword does. That is a fake Fire Aspect: MIAPI's immolate is bonus damage
against something already alight, and its own wiki marks it
unimplemented. Ice and Fire ships one dragon bone for all three kinds of
dragon, so it cannot be the fire dragon's alone.

Sea serpent scales swim: +0.10 swim speed per module in each worn slot,
which lands a full suit near the mark Armory's scuba set sets.

And the arcana core, the one item here that is not a material or a
module. Four arcane essence, four cinder essence, a nether star in the
middle, and out comes a gem that fits any socket and ends Iron's Spells'
limits - a mana pool nothing can empty, cooldowns and cast times at
zero, ten times the spell power. The item is always registered because
an item cannot be conditional; its material and recipe live in the
Iron's Spells pack, so without that mod it is nothing. Its texture is
the heart of the sea's shape in purple, drawn here.

Attributes that a worn or held piece grants were going missing on the
way through a portal - the modifiers are transient, the attribute map is
the only place they live, and nothing re-applies them until the piece is
taken off and put back on, which is why cinder essence stopped being
fire immunity exactly where it was wanted. EquipmentAttributes.java
rebuilds the map from the equipment after a dimension change or a
respawn, and reads this mod's own attributes off the equipment as a
fallback.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 12:02:19 +02:00
Amir Alexander Abdelbaki dcf3b7e9c3 Let scales and bones build armour, and add sea serpent fang
Every scale material was in the "scute" group, and nothing in Truly
Modular accepts it - not Armory's armour, not Arsenal's or Archery's
parts. MIAPI's own turtle and armadillo scutes are stuck the same way.
So the 22 scale materials now carry "bone" as a hidden group: matched
when a module decides what it will accept, ignored when the workbench
decides what heading to file the material under, which is how Armory
hands its own gem slots vanilla materials. That is every piece of
armour except the scuba set, which takes fabric/metal/glass, plus tool
and bow parts.

Dragon bone gains "gem_armor" the same way, so it can be set into an
armour socket rather than only shaped into the armour; it already built
grips and handles as a bone.

Sea serpent fang is new - Ice and Fire has no sea serpent bone, and the
fang is what a serpent leaves besides its scales. It grants water
breathing, which is what Ice and Fire's own Tide Guardian armour does
and what nothing in MIAPI can express: WaterBreathing.java registers an
attribute and answers LivingBreatheEvent, exactly as FireImmunity and
VacuumSeal do, but only with the bearer's eyes in water so a fang is
not half a spacesuit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 11:39:02 +02:00
Amir Alexander Abdelbaki 4e470110c2 Add the modules Truly Modular is missing, and two more materials
Platinum (Create Propulsion: Simulated) and aviation alloy (AeroEngine) were
the only materials left in the pack; Pastel's gem fragments now count as an
eighth of a gem, and Create: Wizardry's arcane sheet, arcane block and mithril
nugget feed the metals they are made of, through extensions in its own pack so
they cost nothing to anyone without it.

Four modules of our own, in packs gated on the addon whose slots they use:

- A khopesh blade for any sword blade slot - axe damage, a point of knockback,
  an axe's mining level, fast, and short-reached and fragile to pay for it.
  Nothing in the ecosystem ships one, so it is drawn here.
- Socketed pommels. Arsenal's own socket pommel takes a small gem, and no
  module anywhere in Arsenal, Armory, Archery or MIAPI offers a gem_melee_large
  slot, so the large gemstone had nowhere to go. Now it does.
- A sealed helmet and a life support pack. Cosmonautics suffocates you in space
  through LivingBreatheEvent and exempts only items that *are* a Create diving
  helmet, which a modular helmet cannot be, so VacuumSeal.java answers the same
  event last. Half a seal each: it takes the suit, not the hat.

Arcane alloy is what it is called in game - Create: Wizardry renames the ingot -
and cutting spell cooldowns is now the reason to build out of it: 10% held per
module, less on armour, on Iron's Spells' cooldown * (2 - value), so a weapon
made of it beats the mod's best staff and a full kit still stays inside the
formula.

Two bugs found on the way: the Ice and Fire amethyst extension named an item
that does not exist (the mod spells it amythest_gem) in a shape MIAPI's codec
does not read, so it had never done anything - extensions are item-checked like
materials now. And the generator handed a namespace to whichever jar sorted
first, which is an addon as often as the mod that owns it; it goes to the jar
with the most of it now. A mod whose jar is missing is skipped with its files
left alone rather than failing the run, and only material folders are cleared,
since the module packs are hand-written.

Module art is placeholder-grade and meant to be repainted: it lives in
textures/items and textures/equipment, with blank templates and a guide sheet
giving the palette stops and every region a model samples.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 10:04:46 +02:00
Amir Alexander Abdelbaki d0ab532683 Give titanium an elytra glide bonus, and bump to 1.1.0
Armory decides elytra flight through miapi:generic.elytra_glide_efficiency,
which its heavy armour reduces by "-30 - density*2" on the chest. Titanium now
adds a flat +10 there - a nudge rather than a cancellation - and its flexibility
goes from 3 to 3.5, since the wing modules read flexibility and density. The
aerospace metal should fly.

Version bumped to 1.1.0 so the loaded build is identifiable at a glance. The
filename changes with it, which makes a stale jar in a mods folder obvious
instead of something that has to be inferred from tooltip values.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 20:18:06 +02:00
Amir Alexander Abdelbaki b7348a69a1 Give arcane essence per-slot mana and make one cinder gem enough
Arcane essence granted max mana through a single modifier with slot `armor`.
On a weapon that reads as "+200 Max Mana when worn", which is nonsense - you
do not wear a greatsword - and it gave every armour piece the same amount.

Each slot now carries its own modifier: 1300 on a helmet, 900 chest, 800 legs,
700 boots, and 900 in the main hand / 400 offhand so a weapon grants it while
held. They stack per module, so a piece built out of arcane essence that also
carries an arcane gem counts twice.

Cinder essence moves to slot `any`, so a single gem grants fire immunity
wherever it sits rather than only from an armour piece. The attribute is capped
at 1 and the handler treats anything at or above 1 as immune, so extra pieces
neither stack nor dilute it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 20:05:14 +02:00
Amir Alexander Abdelbaki 5828bbfb01 Treat every mod's version of a material as the same material
Silver comes from Ice and Fire, Immersive Engineering, Occultism and Superb
Warfare; steel from Mekanism, Immersive Engineering and Superb Warfare. Picking
one provider meant the other mods' ingots were not recognised as that material
at all - they simply could not be used.

Each material now also accepts the c: common tag that every provider registers
into, on top of the primary provider's own items, so any mod's silver ingot is
just silver. 43 materials unify this way.

No origin is recorded, because there is nothing to record: MIAPI stores only
the material id on the item. The primary provider's item stays first in the
ingredient list, so anywhere a single representative item is read back out it
resolves to the default rather than to whichever variant was used.

Tags are also the safe half of the pair - an unknown tag resolves to nothing
while an unknown item id fails the whole material - which is why the material
still lives in its primary provider's pack, where its icon and first ingredient
are guaranteed to exist. MIAPI's own wood material mixes items and tags the
same way. Create: The Air War ships no common tags, so its titanium is added to
c:ingots/titanium with optional entries.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 19:40:16 +02:00
Amir Alexander Abdelbaki 90f1b76864 Cover the whole pack: 124 materials from 20 mods, and fix gem-socket properties
Arcane essence gave no max mana when socketed into armour. Its attributes were
filed under the `armor` material-property key, but a gem module is not tagged
`armor` - the modules declare which material properties they accept and list
only `default` plus their own gem tags, so anything under `armor` or `handheld`
never applied. Everything on the gem-capable materials now hangs off `default`,
and the attribute's own slot decides where it counts. Cinder essence's fire
immunity had the same defect.

New materials, taken from the mods actually in the pack:

  Ender IO      the full alloy ladder plus its six crystals
  AE2           certus quartz, charged certus, fluix
  Occultism     iesnium, spirit attuned gem
  Ars Nouveau   source gem
  NauTec        cast iron, atlantic gold, aquarine steel
  Superb War    tungsten, cemented carbide
  Pastel        ten gems
  Crystal Chron eight shards
  plus pink slime, dimensional shard, industrial iron, ferdonzor, glowshard

The two gem-set mods are generated from a table with shared stat bands per
tier, since their gems differ by colour and lore rather than by kind; the
palette still comes from each gem's own texture.

Metals that overlap a mod already covered are skipped rather than duplicated:
Superb Warfare's steel/lead/silver and Create Crafts & Additions' electrum.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 19:35:54 +02:00
Amir Alexander Abdelbaki e3accaf83f Add Immersive Engineering materials, fix gem sockets, pick one provider per metal
Gem sockets never accepted the two Iron's Spells essences: Arsenal gates its
melee gem modules on the `gem_melee` material group and Armory gates its armour
ones on `gem_armor`, neither of which is the `gem` group they carried. Every
gem-like material now carries both, so it fits any gem slot.

Immersive Engineering adds aluminium, nickel, constantan, electrum and
graphite. Its steel, lead, uranium and silver overlap mods already covered, so
rather than one material accepting either mod's ingot, each metal simply names
one provider - Mekanism for steel/lead/uranium, Ice and Fire for silver. That
keeps one entry per metal in the crafting screen.

Titanium moves to the same rule with Cosmonautics as its provider, which drops
the c: tag files and the any-of pack machinery along with it. That also removes
a latent crash: a tag-fed material still needed an item for its icon, and MIAPI
parses icon items through the registry and throws when one is missing.

The generator now clears its output directory first, so a material that moves
between packs cannot leave a stale copy claiming the same id.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 19:19:36 +02:00
Amir Alexander Abdelbaki c55caa1d1e Add Truly Modular materials for Create, Mekanism, Ice and Fire and friends
72 materials sourced from Create, Mekanism, Ice and Fire, Iron's Spells,
Create: The Air War, Create: Cosmonautics and Aeronautics: Interstellar
Expansion.

Stats follow Tinkers' Construct 3 re-scaled onto MIAPI's vanilla-derived
numbers; materials with no Tinkers counterpart are built from what the
material actually is and then fitted to the nearest tier band.

Palettes are not hand-picked. tools/generate_materials.py reads each
material's texture out of the source mod's jar and samples it into MIAPI's
seven-stop grayscale_map, reproducing MIAPI's own hand-authored copper
palette to within ~2% per channel.

MIAPI's material loader honours no datapack conditions, and a material whose
ingredient item is missing fails to parse, so each mod's materials ship as a
built-in datapack registered only when that mod is installed. Titanium is
shared by both aerospace addons and so sources its ingredients from
c:ingots/titanium instead of an item id.

Cinder essence grants fire immunity through an attribute this mod registers
and watches itself, rather than coupling to MIAPI internals.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 18:40:07 +02:00