Phase 1: Core Foundation
The initial commit (4dc759c) established the foundational architecture of Jepia.
- Addon, Pack, and base Component classes — the core object model for describing Minecraft Bedrock add-ons programmatically
- Manifest builder with UUID generation — automatic
manifest.jsoncreation with persistent UUIDs - File system template generation — produces the full
behavior_pack/+resource_pack/directory structure - Item, Block, Entity components with AJV validation — schema-driven validation at build time
- Cross-component references — dependency resolution between components across packs
// Phase 1: core API shape
const addon = new Addon('MyAddon', 'A Bedrock add-on');
addon.addItem(sword);
addon.addBlock(ore);
addon.addEntity(zombie);
await addon.generate('./output');
Phase 2: Enhanced Texture System (Rendering Phase 1-2)
Days 1 through 10 built out the rendering pipeline's texture and material foundations.
Material Component (Days 1-5)
Full implementation of Bedrock's .material file format with inheritance support.
- Core Material class with inheritance via
parent:childkey format - BlendFactor, MaterialDefine, MaterialState enums for type-safe shader configuration
- VanillaMaterials class with 25 presets and
getMaterialDefinition()for quick access to Bedrock's built-in materials .materialfile generation inresource_pack/materials/- Integration with Entity (
setMaterial) and Addon (auto-collection)
const mat = new Material('custom:glowing_ore');
mat
.setParent('entity_alphatest')
.addDefine('FANCY')
.setBlendSrc('SourceAlpha')
.setBlendDst('OneMinusSrcAlpha')
.setState('DisableCulling', true);
Test coverage: 115 material tests + 39 file generation tests.
TextureLayer (Day 6)
Individual image layers with PBR roles and blending support.
- PBR layer types:
color,normal,mer,mers,heightmap,overlay - Blending modes:
opaque,alpha,additive,multiply,screen - UV offset/scale for atlas sub-regions
- Static factories:
colorLayer(),normalLayer(),merLayer(), etc.
const color = TextureLayer.colorLayer('iron_color', {
path: 'textures/blocks/iron_block',
resolution: { width: 16, height: 16 }
});
const normal = TextureLayer.normalLayer('iron_normal', {
path: 'textures/blocks/iron_block_normal'
});
Test coverage: 94 tests.
TextureSet (Day 7)
PBR texture sets targeting Bedrock format versions 1.16.100 and 1.21.30 (Vibrant Visuals).
- Mutual exclusion enforcement:
normalvsheightmap,mervsmers - Color accepts
string, RGBA array, or hex values - Generates
.texture_set.jsonfiles for Bedrock's deferred rendering pipeline
const textureSet = new TextureSet('iron_pbr');
textureSet
.setColorLayer(colorLayer)
.setNormalLayer(normalLayer)
.setMERLayer(merLayer);
Test coverage: 108 tests.
Texture Rewrite (Days 8-10)
Major refactor of the Texture class for multi-layer support while preserving backward compatibility.
- Multi-layer support via
addLayer(TextureLayer) - Backward-compatible constructor — the old single-path API still works
- Atlas mode with
enableAtlas(width, height) - PBR via
setTextureSet(TextureSet) - Entity + texture integration tests to ensure the full pipeline works end-to-end
Phase 3: Render Controllers (Days 11-17)
RenderController Class (Day 11)
Full implementation of Bedrock's render controller format with Molang expression support.
- Geometry, materials, textures configuration — the three pillars of entity rendering
- Named arrays (texture, material, geometry) for variant selection via Molang
- Part visibility with wildcard patterns for showing/hiding geometry parts
- Color tinting — base, overlay, on_fire, is_hurt color channels
- UV animation for atlas scrolling effects
- Lighting flags:
filterLighting,ignoreLighting - Static factory:
fromTextureLayers()for quick setup from existing layers
const rc = new RenderController('controller.render.my_entity');
rc
.setGeometry("Geometry.default")
.addTexture("Texture.default")
.addMaterial("Material.default", "*")
.setPartVisibility("head", "query.is_baby")
.setUVAnimation([0, "math.sin(query.life_time * 10)"]);
Test coverage: 158 tests.
Entity Integration (Days 13-17)
Deep integration between Entity and the rendering pipeline components.
- Array management and auto-generation — entities automatically build their texture, material, and geometry arrays
- Entity.setRenderController() integration for wiring render controllers
- Texture layer management on Entity for multi-layer setups
- Script animation support via
scripts.animate - Client entity generation overhaul with full resource pack output
- Part visibility and color tinting integration tests
Phase 4: File System & Auto-Generation (Days 18-19)
Automatic Render Controller Generation (Day 18)
Intelligent auto-generation that detects entity patterns and creates appropriate render controllers.
addon.generateRenderControllerForEntity()method- Handles 3 patterns:
- Single-texture — simple direct texture reference
- Multi-variant — texture array +
q.variantMolang query - UV atlas — atlas scrolling with UV animation
- Auto-generates appropriate Material for UV atlas entities
ResourcePack File Integration (Day 19)
Batch file output for all resource pack components.
addMaterialFile(),addRenderControllerFile(),addTextureSetFile()writeFiles()for batch disk output- Full pipeline: Entity → Material → RenderController → files on disk
// Full pipeline in action
const rp = addon.resourcePack;
rp.addMaterialFile(material);
rp.addRenderControllerFile(renderController);
rp.addTextureSetFile(textureSet);
await rp.writeFiles('./output/resource_pack');
Phase 5: Texture Atlas System (Days 21-24)
TextureAtlas Class (Day 21)
Sprite sheet generation with multiple layout strategies.
- Grid and packed layout modes for different texture organization needs
- Entity UV integration with
applyToEntity()for automatic UV coordinate mapping - Shelf packing strategy for row-based placement of same-height textures
const atlas = new TextureAtlas('item_atlas', {
strategy: 'shelf',
maxWidth: 1024,
maxHeight: 1024
});
atlas.addSprite('sword', 16, 16);
atlas.addSprite('pickaxe', 16, 16);
atlas.applyToEntity(entity);
Standalone AtlasPacker (Day 22)
Dedicated packing algorithms extracted for reuse and configurability.
- Shelf algorithm — sort by height, place in rows. Optimal for uniform-size textures.
- Maxrects algorithm — best short-side fit heuristic. Optimal for mixed-size textures.
- Configurable padding between textures to prevent bleeding at mip levels
const packer = new AtlasPacker({
strategy: 'maxrects',
maxWidth: 2048,
maxHeight: 2048,
padding: 1
});
packer.addRect('sword', 16, 16);
packer.addRect('shield', 32, 32);
const result = packer.pack();
// result.placements: Map of sprite positions
Memory Optimization (Day 24)
Analysis and reporting tools for atlas efficiency.
- Optimization reports — efficiency percentage and wasted area calculations
- Memory reports — original vs atlas memory usage with reduction percentage
- Atlas budget validation — ensures atlas dimensions stay within GPU texture limits
CLI & Packaging
Command-line interface for project scaffolding and build automation.
- CLI with Commander:
init,add,build,validatecommands - Interactive prompts with Inquirer.js for guided setup
- Configuration system with Zod schema validation
- .mcaddon/.mcpack packaging with Archiver for ZIP archive creation
- Help system with man pages for offline documentation
# Scaffold a new project
jepia init my-addon
# Add components
jepia add item diamond_sword
jepia add block reactor_core
# Build and validate
jepia validate
jepia build
Test Coverage
Comprehensive test suite covering all components and integration paths.
| Area | Test Count |
|---|---|
| Material | 115 |
| Material File Generation | 39 |
| Entity-Material Integration | 33 |
| TextureLayer | 94 |
| TextureSet | 108 |
| Texture (rewrite) | ~100 |
| RenderController | 158 |
| Entity Integration | ~200 |
| TextureAtlas | ~150 |
| AtlasPacker | ~100 |
| Memory Optimization | ~70 |
| Other (items, blocks, etc.) | ~200 |
| Script types & validation | 55 |
| Script extractor | 56 |
| Script bundler | 25 |
| Script pipeline | 42 |
| BehaviorPack script module | 18 |
| Addon script integration | 23 |
| Addon generate & fs helpers | 26 |
| Custom components | 41 |
| Custom commands | 37 |
| Constants generator & cross-ref | 39 |
| Total | 2617 |
All tests run under Bun's built-in test runner with no external test framework dependencies.
Phase 6: Scripting Support (Days 1-14)
The scripting phase added full Minecraft Script API integration to Jepia. Users can define runtime game logic in the same TypeScript file as their JSON components. Jepia extracts, compiles, and bundles the script code into behavior_pack/scripts/main.js during addon.generate().
| Phase | Days | What was built | Tests added |
|---|---|---|---|
| Phase 1 | 1-2 | Schema & manifest fixes (entry field, semver dependency versions, script module types) | +55 → 2310 total |
| Phase 2 | 3-5 | Script extraction (Function.toString() + ctx rewriting), bundler (Bun.build), pipeline orchestrator | +123 → 2433 total |
| Phase 3 | 6-8 | BehaviorPack script module, addon.script() / addon.scriptFile(), generate() integration | +67 → 2500 total |
| Phase 4 | — | CLI scripting config — skipped (Jepia is a programmatic API only) | — |
| Phase 5 | 11-13 | Custom Components V2, Custom Commands, cross-reference helpers, constants injection | +117 → 2617 total |
| Phase 6 | 14 | Scripted examples (scripted-addon.ts, custom-components-addon.ts), polish | all 2617 passing |
Phase 1: Schema & Manifest Fixes (Days 1-2)
The existing ManifestModule was missing the entry field required by script modules, and ManifestDependency.version only accepted tuple versions — but script dependencies require semver strings (e.g. "1.17.0").
src/manifest/schema.ts— addedentry?: stringtoManifestModule; changedManifestDependency.versiontoVersion | string.src/manifest/builder.ts—validateModule()requiresentrystarting with"scripts/"whentype === "script";validateDependency()accepts semver strings.src/script/types.ts— definedScriptModuleId,ScriptOptions,ScriptBlock,ScriptDependency,ScriptConfig,STABLE_MODULE_VERSIONS, and all helper functions.
// Correct Bedrock manifest structure for a script module:
{
"type": "script",
"language": "javascript",
"uuid": "...",
"version": [1, 0, 0],
"entry": "scripts/main.js" // ← was missing in original schema
}
// Dependency uses semver string, not version tuple:
{ "module_name": "@minecraft/server", "version": "2.5.0" }
Phase 2: Extraction & Bundling (Days 3-5)
Extractor (src/script/extractor.ts)
Uses Function.prototype.toString() to get callback source text, then:
- Strips the outer function wrapper (supports arrow functions, regular functions, async variants, concise arrow bodies).
- Detects
ctx.server/ctx.serverUi/ etc. with word-boundary matching (preventsctx.serverfrom matchingctx.serverUi). - Extracts destructured imports and generates proper ES
importstatements, orimport * as server from '...'for namespace usage. - Merges multiple blocks into one ES module with deduplicated, sorted imports.
Bundler (src/script/bundler.ts)
Wraps Bun.build() with:
target: "browser",format: "esm"external: ["@minecraft/server", "@minecraft/server-ui", ...]— all 5@minecraft/*modules excluded from the bundle- Temp file write → build → read → cleanup pattern
bundleScriptFiles()for user-provided TS/JS files (single or multi-file barrel)
Pipeline (src/script/pipeline.ts)
ScriptPipeline orchestrates the full flow: collect blocks/files → extract → merge raw sources (custom components, commands, constants) → bundle → return { bundledJs, dependencies, scriptModuleUUID }.
Phase 3: Addon & Pack Integration (Days 6-8)
src/pack/behavior.ts— replacedhasScriptModule: booleanwith fullscriptConfig+scriptModuleUUID+setScriptDependencies(). Script manifest module now includesentryandlanguagefields; dependencies use string semver versions.src/addon.ts— addedscript(),scriptFile(),setScriptModuleVersion().generate()runs the pipeline before manifest generation, writesscripts/main.js, persistsscriptModuleUUIDtojepia.config.json.src/utils/fs.ts— addedwriteScriptFile()andcreateAddonFolderStructure({ includeScripts }).
Phase 5: Custom Components & Commands (Days 11-13)
Custom Components V2 (src/script/custom-components.ts)
Block.addCustomComponent(name, handlers, params?) and Item.addCustomComponent(name, handlers, params?) store handler functions and optional JSON params. During generate(), generateCustomComponentScript(registrations) produces the startup registration block:
system.beforeEvents.startup.subscribe((event) => {
const { blockComponentRegistry } = event;
blockComponentRegistry.registerCustomComponent('myaddon:crumble_on_step', {
onStepOn: (event) => { /* extracted handler */ },
});
});
Custom Commands (src/script/custom-commands.ts)
addon.addCustomCommand(config, enums?) stores command registrations. generateCustomCommandScript(registrations) produces startup code with registerEnum() calls before registerCommand() calls. Handler callbacks are inlined via Function.prototype.toString().
Constants Generator (src/script/constants-generator.ts)
When scripts are enabled, Jepia auto-injects a constants export at the top of scripts/main.js:
export const ITEMS = { FIRE_SWORD: "myaddon:fire_sword" };
export const BLOCKS = { MAGIC_ORE: "myaddon:magic_ore" };
export const ENTITIES = {};
Identifiers are converted to SCREAMING_SNAKE_CASE via identifierToConstantName(). No constants are generated when no components are registered (zero overhead).
Phase 5: Cross-Reference Helpers (Day 13)
Added addon.getItemId(item), getBlockId(block), getEntityId(entity), and getRegisteredItemIds() / getRegisteredBlockIds() / getRegisteredEntityIds(). These methods bridge component identifiers between JSON definitions and script code, making it easy to reference the same ID in both places without string duplication.
Key Technical Notes
| Challenge | Solution |
|---|---|
@minecraft/* modules don't exist at build time | ctx phantom type pattern + extractor rewrite; Bun.build marks modules as external |
| Word-boundary matching for ctx.server vs ctx.serverUi | Regex with negative lookahead prevents partial matches |
| Script module UUID stability | scriptModuleUUID persisted to jepia.config.json alongside pack UUIDs |
| Startup registration context | Custom components/commands always placed in system.beforeEvents.startup.subscribe() — correct Bedrock execution context |
| Dependency deduplication | higherVersion(a, b) picks the larger semver when multiple blocks request the same module |
Phase 9: Visual & Client Systems
Phase 9 adds five new component types covering resource-pack-side visual and audio systems: particles, standalone animations, animation controllers, attachables, and client biomes. All five follow the same fluent component pattern as previous phases and write to the resource pack.
| Component | File | Output folder | format_version | Tests added |
|---|---|---|---|---|
Particle | src/component/particle.ts | resource_pack/particles/ | 1.10.0 | +15 |
Animation | src/component/animation.ts | resource_pack/animations/ | 1.8.0 | +16 |
AnimationController | src/component/animation-controller.ts | resource_pack/animation_controllers/ | 1.10.0 | +15 |
Attachable | src/component/attachable.ts | resource_pack/attachables/ | 1.8.0 | +14 |
ClientBiome | src/component/client-biome.ts | resource_pack/biomes_client/ | 1.0.0 | +13 |
ResourcePack storage adds animationControllers, attachables, and clientBiomes maps (particles and animations were already stored). Total: 86 new tests. All 2811 pass.
Particle
Emits format_version: "1.10.0" with a particle_effect root. The description block holds the identifier and basic_render_parameters (material + texture path). The components object is built from fluent methods:
setEmitterRate(type, params)— "steady" / "instant" / "manual"setEmitterLifetime(type, params)— "looping" / "once" / "expression"setEmitterShape(type, params)— "point" / "sphere" / "box" / "disc" / "entity_aabb" / "custom"setParticleLifetime(maxLifetime)— fixed number or Molang expressionsetAppearanceBillboard(params)— size, facing_camera_mode, UV mappingsetAppearanceTinting(color)— RGBA Molang array or gradient objectaddCurve(name, curve)/addEvent(name, event)— Molang curves and lifecycle eventssetComponent(name, value)— escape hatch for any other component
Animation
Emits format_version: "1.8.0" with an animations dict. Each Animation instance represents one named animation in a single file. The animation identifier in the dict is derived from the component id ("myaddon:walk" → "animation.myaddon.walk") and can be overridden via setAnimationId(). Bone keyframes are added per-property (position/rotation/scale) via addBoneKeyframes(boneName, property, keyframes) where keyframes is a time-string→[x,y,z] map. Timeline events and raw data merging are also supported.
AnimationController
Emits format_version: "1.10.0" with an animation_controllers dict. The controller identifier is derived from the component id ("myaddon:base" → "controller.animation.myaddon.base"). States are added via addState(name, config) where config includes animations, transitions, blend_transition, particle_effects, and sound_effects. Validation checks that the initial state exists in the states dict.
Attachable
Emits format_version: "1.8.0" with a minecraft:attachable root. The description block is built from fluent setters: setMaterials(), setTextures(), setGeometry(), setAnimations(), setRenderControllers(), setScripts(), setParticleEffects(), and setSoundEffects(). Empty collections are omitted from the output. Validation requires at least one texture and one geometry.
ClientBiome
Emits format_version: "1.0.0" with a minecraft:client_biome root. The components object is built from: setWaterColor(), setSkyColor(), setFogAppearance(), setGrassColor(), setFoliageColor(), and setComponent() for less common components like minecraft:ambient_sounds or Vibrant Visuals identifiers. Pairs with a server-side Biome for a complete biome definition.
Addon integration
Five new methods on the Addon class, each accepting either a component instance or a legacy (id, data) pair:
addon.addParticle(particle); // → resource_pack/particles/
addon.addAnimation(animation); // → resource_pack/animations/
addon.addAnimationController(controller); // → resource_pack/animation_controllers/
addon.addAttachable(attachable); // → resource_pack/attachables/
addon.addClientBiome(clientBiome); // → resource_pack/biomes_client/
ResourcePack gains addAnimationController(), addAttachable(), and addClientBiome() storage methods with corresponding getters and has*() checks. All three new resource-pack types are written in addon.generate() after particles.
Phase 8: World Generation
Phase 8 adds four new component types that cover the core Minecraft world generation pipeline: biomes, features, feature rules, and dimensions. All four follow the same fluent component pattern as previous phases.
| Component | File | Output folder | format_version | Tests added |
|---|---|---|---|---|
Biome | src/component/biome.ts | behavior_pack/biomes/ | 1.21.110 | +13 |
Feature | src/component/feature.ts | behavior_pack/features/ | 1.13.0 | +10 |
FeatureRule | src/component/feature-rule.ts | behavior_pack/feature_rules/ | 1.13.0 | +12 |
Dimension | src/component/dimension.ts | behavior_pack/dimensions/ | 1.20.0 | +8 |
BehaviorPack integration adds 7 new tests. Total: 50 new tests. All 2725 pass.
Biome
Emits format_version: "1.21.110" with a minecraft:biome wrapper. The components object is built from the fluent API: minecraft:climate, minecraft:surface_builder, minecraft:tags, minecraft:creature_spawn_probability, and minecraft:humidity. The setComponent() escape hatch allows any additional component (e.g. minecraft:mountain_parameters, minecraft:overworld_generation_rules).
Feature
Features use a type/data pattern: setType() sets the root JSON key (e.g. minecraft:ore_feature), and setData() sets all type-specific fields. The description block (identifier) is always injected automatically. Supports all 19+ feature types defined in the Bedrock Creator docs including tree, ore, scatter, geode, cave carver, growing plant, multiface, sculk patch, and structure template features.
FeatureRule
Emits format_version: "1.13.0" with a minecraft:feature_rules wrapper. The description block holds both identifier and places_feature. The conditions block contains placement_pass and an optional minecraft:biome_filter array. The optional distribution block is written only when at least one field is set.
Dimension
Emits format_version: "1.20.0" with a minecraft:dimension_type wrapper. The components block always includes minecraft:dimension_bounds (min/max Y) and minecraft:generation (generator_type). The setComponent() escape hatch allows additional components. Note: Bedrock behavior pack dimensions currently "slice" existing terrain by restricting the accessible Y range — areas outside the bounds become inaccessible voids until the pack is removed.
Addon integration
Four new methods on the Addon class, each accepting either a component instance or a legacy (id, data) pair:
addon.addBiome(biome); // → behavior_pack/biomes/
addon.addFeature(feature); // → behavior_pack/features/
addon.addFeatureRule(rule); // → behavior_pack/feature_rules/
addon.addDimension(dimension); // → behavior_pack/dimensions/
BehaviorPack.getComponentCount() now includes all four world generation types in its total.
Phase 7: Gameplay Systems
Phase 7 added four new component types covering the core gameplay loop: crafting recipes, loot drops, entity spawning, and merchant trading. All four follow the same fluent component pattern as Item, Block, and Entity.
| Component | File | Output folder | Tests added |
|---|---|---|---|
Recipe | src/component/recipe.ts | behavior_pack/recipes/ | +21 |
LootTable | src/component/loot-table.ts | behavior_pack/loot_tables/ | +14 |
SpawnRule | src/component/spawn-rule.ts | behavior_pack/spawn_rules/ | +12 |
TradeTable | src/component/trade-table.ts | behavior_pack/trading/ | +10 |
Total: 57 new tests. All 2675 pass (2725 including Phase 8).
Recipe
Supports four recipe types that map 1:1 to Bedrock's JSON keys:
| Method | JSON key emitted | Default tags |
|---|---|---|
setShaped() | minecraft:recipe_shaped | ["crafting_table"] |
setShapeless() | minecraft:recipe_shapeless | ["crafting_table"] |
setFurnace() | minecraft:recipe_furnace | ["furnace"] |
setBrewing() | minecraft:recipe_brewing_container | ["brewing_stand"] |
All recipes emit "format_version": "1.17" per the official Bedrock recipe schema. Tags can be overridden (e.g. add "smoker" or "blast_furnace" to furnace recipes). assume_symmetry is only included in the JSON when explicitly set.
LootTable
Each addPool() call appends a pool to the pools array. Rolls can be a fixed number or a { min, max } object. Entry types are "item", "loot_table" (nested table reference), and "empty". The pool and individual entries both accept functions and conditions arrays, passed through as-is to the output JSON.
SpawnRule
Emits format_version: "1.8.0" with a minecraft:spawn_rules wrapper matching the official Bedrock spawn rule schema. Each addCondition() call appends one object to the conditions array — that object can contain any combination of minecraft:* spawn condition keys. Population control defaults to "animal".
TradeTable
Emits a { tiers: [...] } structure. Each tier has an optional total_exp_required (0 = novice) and either a flat trades array or a groups array for random selection (num_to_select). TradeItems support fixed or range quantity and a price_multiplier for supply/demand scaling.
Addon integration
All four methods on the Addon class accept either a component instance or a legacy (id, data) pair for backwards compatibility:
addon.addRecipe(recipe); // → behavior_pack/recipes/
addon.addLootTable(lootTable); // → behavior_pack/loot_tables/
addon.addSpawnRule(spawnRule); // → behavior_pack/spawn_rules/
addon.addTradeTable(tradeTable);// → behavior_pack/trading/
BehaviorPack.getComponentCount() now includes spawn rules and trade tables in its total.
Phase 10: Polish & Edge Cases
Phase 10 rounds out the framework with six additions: NPC dialogue, custom camera presets, fog definitions, sound definitions, block culling rules, and a plugin system for lifecycle hooks.
| Component / Feature | File | Output location | format_version |
|---|---|---|---|
Dialogue | src/component/dialogue.ts | behavior_pack/dialogue/ | 1.20.80 |
Camera | src/component/camera.ts | resource_pack/cameras/presets/ | 1.21.0 |
Fog | src/component/fog.ts | resource_pack/fogs/ | 1.16.100 |
SoundDefinition | src/component/sound-definition.ts | resource_pack/sounds/sound_definitions.json | 1.20.20 |
BlockCullingRule | src/component/block-culling.ts | resource_pack/block_culling/ | 1.21.80 |
JepiaPlugin / PluginRegistry | src/plugin.ts | — | — |
Total: 61 new tests. All 2872 pass.
Dialogue
NPC dialogue scenes are defined with addScene(sceneTag, options). Each scene may have an NPC name, body text, open/close commands, and interactive buttons — all of which support both plain strings and Bedrock's rawtext format for i18n. The @initiator selector (unique to NPC dialogue) targets the player who opened the dialogue.
const dlg = new Dialogue('Trader Dialogue', 'myaddon:trader');
dlg.addScene('intro', {
npc_name: 'Mysterious Trader',
text: 'Welcome, traveller. What do you seek?',
on_open_commands: ['/effect @initiator speed 10 1'],
buttons: [
{ name: 'Buy items', commands: ['/dialogue open @e[type=myaddon:trader,r=3] @initiator shop'] },
{ name: 'Nothing', commands: [] },
],
});
addon.addDialogue(dlg); // → behavior_pack/dialogue/trader.json
Camera
Camera presets inherit from a vanilla base (e.g. "minecraft:follow_orbit") and override radius, view/entity offset, starting rotation, and aim-assist settings.
const cam = new Camera('Over-Shoulder Cam', 'myaddon:over_shoulder');
cam.setInheritFrom('minecraft:follow_orbit')
.setRadius(4)
.setViewOffset(2.0, 0.5)
.setEntityOffset(0, 1.5, 0);
addon.addCamera(cam); // → resource_pack/cameras/presets/over_shoulder.json
Fog
Fog definitions support distance fog (start/end/color/render-distance-type) for any medium (air, water, lava, powder_snow, weather) and volumetric fog density + media coefficients (scattering / absorption).
const fog = new Fog('Mystic Fog', 'myaddon:mystic');
fog.setDistanceFog('air', { fog_start: 0.8, fog_end: 1.0, fog_color: '#c0c0ff', render_distance_type: 'render' })
.setDistanceFog('water', { fog_start: 0, fog_end: 30, fog_color: '#1a3080', render_distance_type: 'fixed' });
addon.addFog(fog); // → resource_pack/fogs/mystic.json
Sound Definitions
All SoundDefinition instances are merged into a single resource_pack/sounds/sound_definitions.json at generation time. Sound event names use Bedrock's dot-notation (e.g. "random.chestopen"). Simple file references or full entries with volume, pitch, weight, and 3D-audio flags are both supported.
const snd = new SoundDefinition('Drill Use', 'myaddon.drill.use');
snd.setCategory('block')
.addSoundFile('sounds/myaddon/drill_use1')
.addSoundEntry({ name: 'sounds/myaddon/drill_use2', volume: 0.9, pitch: 1.1 });
addon.addSoundDefinition(snd); // merged → resource_pack/sounds/sound_definitions.json
Block Culling Rules
Face culling rules allow custom blocks to participate in Bedrock's geometry culling system. Each rule maps a neighbour direction to a geometry part (bone + cube + face) under a given condition (same_block, same_culling_layer, etc.).
const cull = new BlockCullingRule('Glass Pane Culling', 'myaddon:glass_pane');
cull.addFaceRule('block', 0, 'north', 'same_block')
.addFaceRule('block', 0, 'south', 'same_block')
.addFaceRule('block', 0, 'east', 'same_block')
.addFaceRule('block', 0, 'west', 'same_block');
addon.addBlockCullingRule(cull); // → resource_pack/block_culling/glass_pane.json
Plugin System
The JepiaPlugin interface exposes two lifecycle hooks. onBeforeGenerate fires before any file is written; onAfterGenerate fires after generation completes. Both receive a PluginContext (addon name, output path, metadata bag). Hooks are called in registration order and support async functions.
import type { JepiaPlugin } from 'jepia';
const loggerPlugin: JepiaPlugin = {
name: 'logger',
onBeforeGenerate({ addonName }) {
console.log(`Building "${addonName}"...`);
},
onAfterGenerate({ outputPath }) {
console.log(`Written to ${outputPath}`);
},
};
addon.use(loggerPlugin);
await addon.generate('./output');
Phase 11 — Hardening
Phase 11 focuses on code quality, validation coverage, and correctness fixes across the codebase. No new content types; instead, existing components are strengthened.
Changes
| Area | Change |
|---|---|
| Biome | Removed dead code in toJSON() — an unused surfaceBuilder variable was constructed but never referenced. |
| Item | Simplified primitive storage — removed __primitive__ sentinel pattern; primitives now stored directly like Block does. |
| SoundDefinition | Tightened identifier regex — rejects names starting/ending with special chars (e.g. "...", "---"). |
| Dialogue | Updated format_version from 1.17 to 1.20.80 per latest Minecraft creator docs. |
| Dialogue | Added validate(): checks ≥1 scene, no duplicate tags, ≤6 buttons per scene. |
| Camera | Added validate(): checks radius 0.1–100, pitch ±90°, yaw ±180°. |
| Fog | Added validate(): checks fog_start ≤ fog_end, max_density 0–1, render_distance_type valid. |
| Particle | Added validate(): checks render parameters present, emitter lifetime + rate configured. |
33 new tests → 2905 total (all passing).