A map of how blocks get defined, how their assets (blockstate/model/item/lang) get generated, how engraving recipes + the creative tabs are built, and where it's coupled — written to help decide what to simplify. Goal end-state (yours): spreadsheets + material tags, minimal hand-written Java.
[1] DEFINE a block [2] GENERATE its assets [3] GROUP it [4] SHOW it
---------------------- ----------------------- ------------ -----------
(a) Java register() blockstate + block model material tag creative tab
(b) ModBlocks (minecraft:) item model ---> ott:material/X ---> + ENGRAVED tab =
(c) block_templates.csv ---> lang (display name) EngravingEntries
(d) ctm_blocks.tsv recipes (engrave/stonecut/ .tabItems()
smelt/craft)
Two things to internalize:
- Defining a block and generating its assets are separate steps, done in different places. A CTM block is registered in Java but its model is described in a TSV. Miss either half → broken/invisible.
EngravingEntries.enumerate()is read twice — once to write recipes, once to build the ENGRAVED tab. That's why one edit there changes both recipes and the tab.
| # | Path | Where | Namespace | Spreadsheet? | Used for |
|---|---|---|---|---|---|
| 1 | OttBlocks.register(name, supplier) |
ott_blocks/.../OttBlocks.java |
ott: |
❌ hand-written Java | Most decorative blocks, CTM blocks, panes, doors/trapdoors, marble, mosaics, glass |
| 2 | ModBlocks.registerBackportedBlock(...) |
root ModBlocks.java |
minecraft: |
❌ hand-written Java | Backported vanilla (pale oak, resin, copper, …) |
| 3 | block_templates.csv → OttTemplateBlocks |
ott_blocks/.../block_templates.csv |
ott: |
✅ CSV | Plain cube/column/glass blocks ("template blocks"), auto-paned |
| 4 | ctm_blocks.tsv (assets only) |
ott_blocks/.../ctm_blocks.tsv |
ott: |
✅ TSV (assets, not registration) | Connecting-texture blocks |
Important nuances:
- Paths 1 & 2 are loop-or-hand Java. Color sets, wood sets, marble, mosaics, pattern blocks are all
Path 1 — some loop over a list (color/wood sets), some are 100+ individual
register(...)lines (marble). - Path 3 (CSV) is the closest to your spreadsheet dream — one row
name,material,template,renderproduces the DeferredBlock and (fortemplate=glass) an auto static pane. Columns:name– block idmaterial– texture folder and theott:material/<material>group it joins (drives engraving!)template–cube_all(default) /cube_column/glass/glass_column/crying_obsidianrender–/solid/cutout/cutout_mipped/translucent
- Path 4 (TSV) only generates ASSETS — the block must also be registered via Path 1. So a CTM block
lives in two places: a
register("x_ctm", …)line inOttBlocks.javaand a row inctm_blocks.tsv. This double-bookkeeping is a real friction point.
| Asset | Provider | How it iterates |
|---|---|---|
| blockstate + block model (ott:) | OttBlockStateProvider |
Explicit sweeps over collections (COLOR_SETS, OPAL_SETS, STONE_SETS, marble…) + a loop over OttTemplateBlocks.TEMPLATE_BY_NAME for CSV blocks |
| blockstate + model (minecraft:) | MinecraftBackportBlockStateProvider |
Sweeps ModBlocks.MINECRAFT_BLOCKS |
| CTM blockstate/model/item | OttCtmModelProvider |
Reads ctm_blocks.tsv; for each row whose block is registered, writes 3 JSONs. Has a coverage check that errors if a registered _ctm block has no row |
| CTM panes | OttCtmPaneProvider |
Hardcoded buildSpecs() list of PaneSpec(name, paneTex, edgeTex, render, …) |
| item models | ModItemModelProvider / MinecraftBackportItemModelProvider |
Per-block; doors/trapdoors special-cased |
| lang (display names) | ModLangMergeProvider |
Merges committed assets/ott/lang/en_us_base.json + auto-title-cased entries generated from collections (wood doors, glass, etc.) |
| recipes | ModRecipeProvider |
Engraving via EngravingEntries.enumerate; stonecut/smelt/craft hand-written |
Base model helpers live in ModBlockStateProvider (doors/trapdoors, etc.).
Each fancy connecting block is two blocks: a connecting (_ctm) one and a plain static twin.
Using stone_framed_glass as the template — to add a new member you touch all of these:
| Half | What | File |
|---|---|---|
| CTM block | register("X_framed_glass_ctm", () -> new TransparentBlock(...)) |
OttBlocks.java |
| CTM block assets | one TSV row: X_framed_glass_ctm <mat> ctm_cube_all_pieces minecraft:translucent pieces_full all=ott:block/<mat>/ctm/X_framed_glass all solo minecraft:translucent |
ctm_blocks.tsv |
| CTM texture | ott:block/<mat>/ctm/X_framed_glass.png (atlas) |
ott_blocks/.../textures/block/<mat>/ctm/ |
| CTM pane block | register("X_framed_glass_ctm_pane", () -> new CtmPaneBlock(...)) |
OttBlocks.java |
| CTM pane assets | a PaneSpec(...) entry |
OttCtmPaneProvider.buildSpecs() |
| static twin (block + pane) | one CSV row: X_framed_glass,<mat>,glass,translucent (auto-registers block and X_framed_glass_pane) |
block_templates.csv |
| engraving | s.tagged(materialTag("glass"), …, "X_framed_glass_ctm_engraving") |
EngravingEntries.java |
| lang | "block.ott.X_framed_glass_ctm": "Framed … Glass" (+ pane) |
en_us_base.json |
That's ~8 touch-points across 5 files for one block — the core reason this feels out of control.
EngravingEntries.java is the single source of truth for both engraving recipes and the ENGRAVED tab.
enumerate(Sink s) walks every engraving once and calls one of three Sink methods:
| Method | Ingredient | Meaning | Count |
|---|---|---|---|
s.tagged(materialTag("X"), out, id) |
the tag ott:material/X |
any block in the group → out. Put any group member in the table, see the whole group as outputs. This is the model you want everywhere. | ~1,911 |
s.one(input, out, id) |
one specific block | base-only → out, one-directional (variants can't be re-engraved into each other) | ~488 |
s.group(ingredient, out, id) |
a custom Ingredient |
tagged-but-inline; a workaround from before a tag existed | 3 |
Why three exist: accretion, not design. s.one/s.group predate having a material tag for every
material. The simplification is to make everything s.tagged — that gives the consistent "any-in →
all-out" behavior. The only prerequisite per converted material: a tag file listing all its members.
The same list feeds the tab via tabItems() (unique ott: outputs, first-seen order). The
ENGRAVED tab (OttCreativeCategories) just iterates that list and pairs each cube with its pane/carpet.
- Live at
data/ott/tags/item/material/<name>.json— 296 files, hand-authored, committed. - A tag lists every block that counts as that material (the engraving inputs).
block_templates.csv'smaterialcolumn is what slots a CSV block into a group; hand-registered blocks are added by editing the tag file (or viaModItemTagProvidercopies for door families).- To convert an
s.onematerial tos.tagged: ensureott:material/<x>.jsoncontains the base- all variants, then flip the entries. (e.g. marble: needs
ott:material/<color>_marblepopulated.)
- all variants, then flip the entries. (e.g. marble: needs
OttCreativeCategories.java — ~20 tabs. Membership is set per tab by either:
- explicit
output.accept(...)sweeps over collections (COLORS, WOOD_SETS, STONE_CUSTOM, BLOCKS…), or - ENGRAVED =
EngravingEntries.tabItems()(so it mirrors engraving exactly; reordered at render time by the section-cluster logic we added), or - COVERAGE = audit net: everything in
OttBlocks/ModBlocksnot already placed in another tab.
| Family | Defined in | Members | Spreadsheet today? | Engraving coupling |
|---|---|---|---|---|
| CTM blocks | ctm_blocks.tsv (assets) + Java register |
~2,000 rows | ✅ assets fully TSV; ❌ registration still Java | tagged loops |
| Template blocks | block_templates.csv |
thousands | ✅ fully CSV | via material column |
| Color sets | ModColorSets.ALL (Java list) + ColorSetBlockRegistrar |
17 colors × ~20 types | base material, light | |
| Wood sets | ModWoodSets.ALL (Java list) + WoodSetBlockRegistrar |
2 woods × ~29 types | base material | |
| Pattern blocks | ModPatterns (scans texture folder) + loop |
~20 patterns × ~33 colors | white base engraved | |
| Marble | individual register(...) fields in OttBlocks |
16 colors × ~7 = ~112+ | ❌ fully hand-written | heavy (~150 s.one lines) |
| Mosaics | individual fields + ctm_blocks.tsv |
25 + floor/fresco/masonry | smooth_sandstone section |
Biggest pain / best ROI to simplify: marble (hand-written everywhere), and the CTM "register-in-Java-AND-describe-in-TSV" double bookkeeping.
EngravingEntries.enumerate()→ recipes AND the ENGRAVED tab (the big one).block_templates.csvmaterialcolumn → whichott:material/Xgroup → engraving input set → tab pairing.- A CTM block needs both a Java
registerline and actm_blocks.tsvrow (+ a pane block needs aregisterline and aPaneSpec). materialtag files are hand-maintained; a block not in its tag is silently un-engravable.- Marble variants are wired in ~4 places each (register, datagen, stone-set wrapper, engraving).
Target: a spreadsheet per "kind of thing" (you fill in textures + details), datagen turns rows into
blockstates/models/items/lang/recipes/tags. You already have two of these sheets working
(block_templates.csv, ctm_blocks.tsv). The work is widening them and deleting bespoke Java, one
family at a time. Each step below is standalone and safe to do/verify on its own:
- Unify engraving on
s.tagged. Pick ones.onefamily at a time (start with marble), populate itsott:material/<x>tag, flip the entries. Removes the "base-only" inconsistency. (behavioral win you asked for) - Make
ctm_blocks.tsvalso register the block. Have the CTM provider/registrar create the DeferredBlock from the row (likeblock_templates.csvdoes), so CTM blocks live in one place, not two. Kills the biggest double-bookkeeping. - Lang from the sheets. Add an optional
display_namecolumn to the CSV/TSV;ModLangMergeProviderreads it. Removes most hand-authored lang entries. - Move marble to the CSV/TSV (it's just colored cube/brick/tile/pillar variants) and delete the ~112
hand fields + ~150 engraving lines; engraving comes free via the
materialcolumn once ons.tagged. - A
families.csv(later): one row defines a whole family (members + recipe relationships) and a generator expands it — the fullest version of your spreadsheet dream. Optional capstone.
Do them in order; each is a small, reviewable PR-sized change, and none requires gutting anything.
Generated 2026-06-26 as a living map — update as systems change.
leave marble in custom stone, stonecutter recipes remove from engraving system
ALL trapdoors missing material group engraving tags (only tag they have is minecraft:mineable/axe) they should get all the same tags as vanilla trapdoors
all pale oak windows missing material group engraving tags
all window panes need removed from engraving recipes (only default 6 blocks -> 16 panes recipe)
wood type window and glass blocks need all tags for BOTH vanilla minecraft planks for their wood type AND glass we retain only our ott material group tag for engraving
the wood type bookshelves (acacia, bamboo, birch, cherry, crimson, dark oak, jungle, mangrove, oak, spruce, warped) need to have its own material group created each wood type bookshelf needs to be added to their own material group each wood type bookshelf needs to have a unique shaped recipe created reusing the vanilla bookshelf recipe but substituting the proper wood type planks for the oak planks the vanilla bookshelf recipe should be altered to only accept oak planks we currently have a list of oak bookshelves that should be added to the oak bookshelf material group the vanilla bookshelf should also be added to this group the others will only have the one bookshelf for now but I will be adding more we should create a pale oak group which will be empty for now
wood type trapdoors should only accept other trapdoors of their wood type as unput they are currently accepting doors
the non-connecting and connecting sets of framed stone glass, framed sandstone glass, framed red sandstone glass, framed obsidian glass are not grouped together
connecting sea lantern needs to be added to the sea_lantern material group as it is currently only accepting the vanilla sea lantern as input I suspect this is part of the s.one tag issues we saw that we should be fixing anyway
we are also missing a LOT of minecraft tags for blocks, doors, trapdoors, glass, etc it appears we only have the mineable tags set but missing all others that would be applied we need to audit all of the blocks to ensure they have all of the same tags as their vanilla counterparts
chisels sets (nexus, etc) are missing minecraft AND ott tags I suspect this is due to their oddball generation they should be added to their parent stone material group and tagged as appropriate
all 17 faux trapdoor blocks need all tags for vanilla wood planks
items are still split up (example: one set of trapdoors (barn, barrel, beach etc) is listed in one group and another (airy, checkered, classic, etc) is listed in another group later doors have the same issue I suspect this is caused by the weird way we have doors and trapdoors being processed via different loops we should unify the process for each
I'd also like to revert our models for both doors and trapdoors to the vanilla models I will revisit 3D models at a later date we can remove those bespoke blockstates and models
wood type windows and wood type glass should be grouped together by wood type