From fb0937cc162299f08081259ba3d70f205ffe0e0c Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Sun, 1 Mar 2026 01:43:52 +0100 Subject: [PATCH 01/18] Add flint cicd to test the portal creation --- .github/workflows/flint.yml | 78 +++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 .github/workflows/flint.yml diff --git a/.github/workflows/flint.yml b/.github/workflows/flint.yml new file mode 100644 index 00000000000..c9d6a200076 --- /dev/null +++ b/.github/workflows/flint.yml @@ -0,0 +1,78 @@ +name: Flint Tests + +on: + push: + branches: [main, dev,dimensions_new] + pull_request: + +env: + CARGO_TERM_COLOR: always + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout SteelMC + uses: actions/checkout@v4 + + - name: Add flint-steel to workspace + run: sed -i 's/"steel-crypto",/"steel-crypto",\n "flint-steel",/' Cargo.toml + + - name: Checkout flint-steel + uses: actions/checkout@v4 + with: + repository: FlintTestMC/flint-steel + ref: steel-dimensions + path: flint-steel + + - name: Clone JunkysGenerator + uses: actions/checkout@v4 + with: + repository: FlintTestMC/JunkysGenerator + path: flint/JunkysGenerator + + - name: Setup Rust nightly + uses: dtolnay/rust-toolchain@nightly + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + . -> target + flint/JunkysGenerator -> target + + - name: Build test generators + working-directory: flint/JunkysGenerator + run: cargo build --release -p create-portal -p destroy-portal + + - name: Generate create-portal tests + working-directory: flint/JunkysGenerator/create-portal + run: cargo run --release -p create-portal + + - name: Generate destroy-portal tests + working-directory: flint/JunkysGenerator/destroy-portal + run: cargo run --release -p destroy-portal + + - name: Check generated tests for created-portal + working-directory: flint/JunkysGenerator/create-portal + run: find ./test -type f -printf '.' | wc -c + + - name: Check generated tests for destroy-portal + working-directory: flint/JunkysGenerator/destroy-portal + run: find ./test -type f -printf '.' | wc -c + + - name: Clean up generator build artifacts + run: rm -rf flint/JunkysGenerator/target + + - name: Run flint-steel tests + timeout-minutes: 15 + env: + TEST_PATH: ${{ github.workspace }}/flint/JunkysGenerator + run: cargo test --release --lib -p flint-steel test_run_all_flint_benchmarks -- --nocapture + + - name: Upload Flint summary + if: always() + uses: actions/upload-artifact@v4 + with: + name: flint-summary + path: flint-steel/log/flint_summary.json From 29c19617198d4455da9f644e3383a4e7dc300b23 Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Sun, 1 Mar 2026 02:30:05 +0100 Subject: [PATCH 02/18] creating a portal with flint & steel --- steel-core/build/blocks.rs | 13 +- steel-core/build/items.rs | 8 +- steel-core/src/behavior/blocks/fire.rs | 59 +++++ steel-core/src/behavior/blocks/mod.rs | 4 + .../behavior/blocks/nether_portal_block.rs | 44 ++++ .../src/behavior/items/flint_and_steel.rs | 25 +++ steel-core/src/behavior/items/mod.rs | 3 + steel-core/src/lib.rs | 1 + steel-core/src/portal/mod.rs | 2 + steel-core/src/portal/portal_shape.rs | 210 ++++++++++++++++++ 10 files changed, 367 insertions(+), 2 deletions(-) create mode 100644 steel-core/src/behavior/blocks/fire.rs create mode 100644 steel-core/src/behavior/blocks/nether_portal_block.rs create mode 100644 steel-core/src/behavior/items/flint_and_steel.rs create mode 100644 steel-core/src/portal/mod.rs create mode 100644 steel-core/src/portal/portal_shape.rs diff --git a/steel-core/build/blocks.rs b/steel-core/build/blocks.rs index dea237e5bff..4c1dcc0eda3 100644 --- a/steel-core/build/blocks.rs +++ b/steel-core/build/blocks.rs @@ -59,6 +59,8 @@ pub fn build(blocks: &[BlockClass]) -> String { let mut wall_torch_blocks = Vec::new(); let mut redstone_torch_blocks = Vec::new(); let mut redstone_wall_torch_blocks = Vec::new(); + let mut fire_blocks = Vec::new(); + let mut nether_portal_blocks = Vec::new(); for block in blocks { let const_ident = to_const_ident(&block.name); @@ -104,6 +106,8 @@ pub fn build(blocks: &[BlockClass]) -> String { "WallTorchBlock" => wall_torch_blocks.push(const_ident), "RedstoneTorchBlock" => redstone_torch_blocks.push(const_ident), "RedstoneWallTorchBlock" => redstone_wall_torch_blocks.push(const_ident), + "FireBlock" => fire_blocks.push(const_ident), + "NetherPortalBlock" => nether_portal_blocks.push(const_ident), _ => {} } } @@ -124,6 +128,8 @@ pub fn build(blocks: &[BlockClass]) -> String { let wall_torch_type = Ident::new("WallTorchBlock", Span::call_site()); let redstone_torch_type = Ident::new("RedstoneTorchBlock", Span::call_site()); let redstone_wall_torch_type = Ident::new("RedstoneWallTorchBlock", Span::call_site()); + let fire_type = Ident::new("FireBlock", Span::call_site()); + let nether_portal_block = Ident::new("NetherPortalBlock", Span::call_site()); let barrel_registrations = generate_registrations(barrel_blocks.iter(), &barrel_type); let button_registrations = { @@ -181,6 +187,9 @@ pub fn build(blocks: &[BlockClass]) -> String { generate_registrations(redstone_torch_blocks.iter(), &redstone_torch_type); let redstone_wall_torch_registrations = generate_registrations(redstone_wall_torch_blocks.iter(), &redstone_wall_torch_type); + let fire_registrations = generate_registrations(fire_blocks.iter(), &fire_type); + let nether_portal_registrations = + generate_registrations(nether_portal_blocks.iter(), &nether_portal_block); let output = quote! { //! Generated block behavior assignments. @@ -191,7 +200,7 @@ pub fn build(blocks: &[BlockClass]) -> String { BarrelBlock, ButtonBlock, CandleBlock, CraftingTableBlock, CropBlock, EndPortalFrameBlock, FarmlandBlock, FenceBlock, LiquidBlock, RotatedPillarBlock, StandingSignBlock, WallSignBlock, CeilingHangingSignBlock, WallHangingSignBlock, TorchBlock, WallTorchBlock, - RedstoneTorchBlock, RedstoneWallTorchBlock, + RedstoneTorchBlock, RedstoneWallTorchBlock,FireBlock, NetherPortalBlock, }; pub fn register_block_behaviors(registry: &mut BlockBehaviorRegistry) { @@ -213,6 +222,8 @@ pub fn build(blocks: &[BlockClass]) -> String { #wall_torch_registrations #redstone_torch_registrations #redstone_wall_torch_registrations + #fire_registrations + #nether_portal_registrations } }; diff --git a/steel-core/build/items.rs b/steel-core/build/items.rs index 0e25f63a114..0c15bf24149 100644 --- a/steel-core/build/items.rs +++ b/steel-core/build/items.rs @@ -110,6 +110,7 @@ pub fn build(items: &[ItemClass]) -> String { let mut standing_and_wall_items: Vec<(Ident, Ident, Ident)> = Vec::new(); let mut ender_eye_items: Vec = Vec::new(); let mut shovel_items: Vec = Vec::new(); + let mut flint_and_steel_items: Vec = Vec::new(); for item in items { let item_field = to_item_field(&item.name); @@ -158,6 +159,7 @@ pub fn build(items: &[ItemClass]) -> String { } "EnderEyeItem" => ender_eye_items.push(item_field), "ShovelItem" => shovel_items.push(item_field), + "FlintAndSteelItem" => flint_and_steel_items.push(item_field), _ => {} } } @@ -174,13 +176,16 @@ pub fn build(items: &[ItemClass]) -> String { generate_simple_registrations(ender_eye_items.iter(), &ender_eye_type); let shovel_type = Ident::new("ShovelBehaviour", Span::call_site()); let shovel_registrations = generate_simple_registrations(shovel_items.iter(), &shovel_type); + let flint_and_steel_type = Ident::new("FlintAndSteelBehavior", Span::call_site()); + let flint_and_steel_registrations = + generate_simple_registrations(flint_and_steel_items.iter(), &flint_and_steel_type); let output = quote! { //! Generated item behavior assignments. use steel_registry::{vanilla_blocks, vanilla_items}; use crate::behavior::ItemBehaviorRegistry; - use crate::behavior::items::{BlockItemBehavior, EnderEyeBehavior, HangingSignItemBehavior, SignItemBehavior, StandingAndWallBlockItem, ShovelBehaviour}; + use crate::behavior::items::{BlockItemBehavior, EnderEyeBehavior, HangingSignItemBehavior, SignItemBehavior, StandingAndWallBlockItem, ShovelBehaviour, FlintAndSteelBehavior}; pub fn register_item_behaviors(registry: &mut ItemBehaviorRegistry) { #block_item_registrations @@ -189,6 +194,7 @@ pub fn build(items: &[ItemClass]) -> String { #standing_and_wall_item_registrations #ender_eye_registrations #shovel_registrations + #flint_and_steel_registrations } }; diff --git a/steel-core/src/behavior/blocks/fire.rs b/steel-core/src/behavior/blocks/fire.rs new file mode 100644 index 00000000000..d3cc878d678 --- /dev/null +++ b/steel-core/src/behavior/blocks/fire.rs @@ -0,0 +1,59 @@ +//! Barrel block behavior implementation. +//! +//! Opens a 27-slot container menu when right-clicked. + +use steel_registry::blocks::BlockRef; +use steel_registry::vanilla_blocks::{NETHER_PORTAL, OBSIDIAN}; +use steel_utils::{BlockPos, BlockStateId}; + +use crate::behavior::block::BlockBehaviour; +use crate::behavior::context::BlockPlaceContext; +use crate::portal::portal_shape::{PortalShape, PortalTest}; +use crate::world::World; + +/// Behavior for barrel blocks. +/// +/// Barrels are container block entities with 27 slots (3x9 grid). +/// They use the same menu as chests but cannot form double containers. +pub struct FireBlock { + block: BlockRef, +} + +impl FireBlock { + /// Creates a new barrel block behavior. + #[must_use] + pub const fn new(block: BlockRef) -> Self { + Self { block } + } +} + +impl BlockBehaviour for FireBlock { + fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option { + Some(self.block.default_state()) + } + + fn on_place( + &self, + _state: BlockStateId, + world: &World, + pos: BlockPos, + _old_state: BlockStateId, + _moved_by_piston: bool, + ) { + if let Some(tester) = PortalTest::find_portal_shape( + world, + pos, + &PortalShape { + min_x: 2, + max_x: 21, + min_y: 3, + max_y: 21, + frame: OBSIDIAN, + portal: NETHER_PORTAL, + }, + ) { + tester.place_portal_blocks(world); + // TODO: Play ignite sound, damage item + } + } +} diff --git a/steel-core/src/behavior/blocks/mod.rs b/steel-core/src/behavior/blocks/mod.rs index 40d900163d7..085642d419b 100644 --- a/steel-core/src/behavior/blocks/mod.rs +++ b/steel-core/src/behavior/blocks/mod.rs @@ -11,7 +11,9 @@ mod crop_block; mod end_portal_frame_block; mod farmland_block; mod fence_block; +mod fire; mod liquid_block; +mod nether_portal_block; mod redstone_torch_block; mod rotated_pillar_block; mod sign_block; @@ -25,7 +27,9 @@ pub use crop_block::CropBlock; pub use end_portal_frame_block::EndPortalFrameBlock; pub use farmland_block::FarmlandBlock; pub use fence_block::FenceBlock; +pub use fire::FireBlock; pub use liquid_block::LiquidBlock; +pub use nether_portal_block::NetherPortalBlock; pub use redstone_torch_block::{RedstoneTorchBlock, RedstoneWallTorchBlock}; pub use rotated_pillar_block::RotatedPillarBlock; pub use sign_block::{ diff --git a/steel-core/src/behavior/blocks/nether_portal_block.rs b/steel-core/src/behavior/blocks/nether_portal_block.rs new file mode 100644 index 00000000000..80a312bb6de --- /dev/null +++ b/steel-core/src/behavior/blocks/nether_portal_block.rs @@ -0,0 +1,44 @@ +//! Nether portal block behavior. + +use steel_registry::blocks::BlockRef; +use steel_registry::blocks::block_state_ext::BlockStateExt; +use steel_registry::vanilla_blocks::AIR; +use steel_utils::{BlockPos, BlockStateId, Direction}; + +use crate::behavior::block::BlockBehaviour; +use crate::behavior::context::BlockPlaceContext; +use crate::world::World; + +/// Behavior for the nether portal block. +pub struct NetherPortalBlock { + #[allow(dead_code)] + block: BlockRef, +} +impl NetherPortalBlock { + /// Create a new `NetherPortalBlock` + #[must_use] + pub const fn new(block: BlockRef) -> Self { + Self { block } + } +} + +impl BlockBehaviour for NetherPortalBlock { + fn update_shape( + &self, + state: BlockStateId, + _world: &World, + _pos: BlockPos, + _direction: Direction, + _neighbor_pos: BlockPos, + neighbor_state: BlockStateId, + ) -> BlockStateId { + if neighbor_state.is_air() { + return AIR.default_state(); + } + state + } + + fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option { + None // TODO: add this functionality but has low priority + } +} diff --git a/steel-core/src/behavior/items/flint_and_steel.rs b/steel-core/src/behavior/items/flint_and_steel.rs new file mode 100644 index 00000000000..090ed38b7ce --- /dev/null +++ b/steel-core/src/behavior/items/flint_and_steel.rs @@ -0,0 +1,25 @@ +//! Flint and steel item behavior with portal ignition. + +use crate::behavior::context::{InteractionResult, UseOnContext}; +use crate::behavior::item::ItemBehavior; +use steel_registry::vanilla_blocks::FIRE; +use steel_utils::types::UpdateFlags; + +/// Behavior for flint and steel items. +pub struct FlintAndSteelBehavior; + +impl ItemBehavior for FlintAndSteelBehavior { + fn use_on(&self, context: &mut UseOnContext) -> InteractionResult { + let click_pos = context.hit_result.block_pos; + let fire_pos = click_pos.relative(context.hit_result.direction); + + context.world.set_block( + fire_pos, + FIRE.default_state(), + UpdateFlags::UPDATE_NEIGHBORS, + ); + + // TODO: Place fire block at fire_pos if it's air on a solid block + InteractionResult::Pass + } +} diff --git a/steel-core/src/behavior/items/mod.rs b/steel-core/src/behavior/items/mod.rs index 1c627ed0a1d..0fc6f6580dd 100644 --- a/steel-core/src/behavior/items/mod.rs +++ b/steel-core/src/behavior/items/mod.rs @@ -11,10 +11,13 @@ mod shovel; mod sign_item; mod standing_and_wall_block_item; +mod flint_and_steel; + pub use block_item::BlockItemBehavior; pub use bucket::FilledBucketBehavior; pub use default::DefaultItemBehavior; pub use ender_eye::EnderEyeBehavior; +pub use flint_and_steel::FlintAndSteelBehavior; pub use shovel::ShovelBehaviour; pub use sign_item::{HangingSignItemBehavior, SignItemBehavior}; pub use standing_and_wall_block_item::StandingAndWallBlockItem; diff --git a/steel-core/src/lib.rs b/steel-core/src/lib.rs index 972ebb9e6f6..cb6e411f8cd 100644 --- a/steel-core/src/lib.rs +++ b/steel-core/src/lib.rs @@ -15,6 +15,7 @@ pub mod inventory; pub mod level_data; pub mod physics; pub mod player; +pub mod portal; pub mod server; pub mod world; pub mod worldgen; diff --git a/steel-core/src/portal/mod.rs b/steel-core/src/portal/mod.rs new file mode 100644 index 00000000000..fb0d23bba17 --- /dev/null +++ b/steel-core/src/portal/mod.rs @@ -0,0 +1,2 @@ +//! Dimension portal system for nether/end portals and future portal types. +pub mod portal_shape; diff --git a/steel-core/src/portal/portal_shape.rs b/steel-core/src/portal/portal_shape.rs new file mode 100644 index 00000000000..447e0154d8d --- /dev/null +++ b/steel-core/src/portal/portal_shape.rs @@ -0,0 +1,210 @@ +//! Portal shape detection for validating obsidian frames. + +use std::ptr; + +use steel_registry::blocks::BlockRef; +use steel_registry::blocks::block_state_ext::BlockStateExt; +use steel_registry::blocks::properties::BlockStateProperties; +use steel_registry::vanilla_blocks; +use steel_utils::math::Axis; +use steel_utils::types::UpdateFlags; +use steel_utils::{BlockPos, Direction}; + +use crate::world::World; + +/// A detected portal shape with axis, position, and dimensions. +pub struct PortalTest { + /// The axis of the portal (X or Z). + pub axis: Axis, + /// Bottom-left corner of the portal interior. + pub bottom_left: BlockPos, + /// Width of the interior (2-21). + pub width: u32, + /// Height of the interior (3-21). + pub height: u32, + /// The block type of the frame. + pub portal: BlockRef, +} + +/// Definition of a portal shape in rectangular form, like the nether portal frame. +pub struct PortalShape { + /// min size of the portal in x direction + pub min_x: u32, + /// max size of the portal in x direction + pub max_x: u32, + /// min size of the portal in y direction + pub min_y: u32, + /// max size of the portal in y direction + pub max_y: u32, + /// The block type of the frame. + pub frame: BlockRef, + /// The block type of the portal. + pub portal: BlockRef, +} + +impl PortalTest { + const MIN_WIDTH: u32 = 2; + const MAX_WIDTH: u32 = 21; + const MIN_HEIGHT: u32 = 3; + const MAX_HEIGHT: u32 = 21; + + /// Tries to find a valid portal shape from a position inside or adjacent to a frame. + pub fn find_portal_shape( + world: &World, + fire_pos: BlockPos, + shape: &PortalShape, + ) -> Option { + Self::try_axis(world, fire_pos, Axis::X, shape) + .or_else(|| Self::try_axis(world, fire_pos, Axis::Z, shape)) + } + /// Tries to find a valid portal + /// It doesn't loop over the obsidian, it loops over the air in the portal, to get the size of the portal + fn try_axis(world: &World, pos: BlockPos, axis: Axis, shape: &PortalShape) -> Option { + // Width direction: portal axis=X means width along Z, axis=Z means width along X + let dir: Direction = match axis { + Axis::X => Direction::East, + Axis::Z => Direction::North, + Axis::Y => return None, + }; + tracing::info!("axis: {:?}", dir); + + // searches the bottom obsidian + let mut cur = pos; + for _ in 0..=Self::MAX_HEIGHT as i32 { + let next = BlockPos::new(cur.x(), cur.y() - 1, cur.z()); + if Self::is_frame_block(world, next, shape) { + break; + } + cur = next; + } + + // searches for the left obsidian (-1) because we don't want to be at the obsidian block + let to_left = Self::get_width(world, cur, dir, shape); + tracing::info!("to_left: {}", to_left); + cur = cur.relative_n(dir, to_left as i32); + + tracing::info!("left_bottom: {:?}", cur); + + let width = Self::get_width(world, cur, dir.opposite(), shape) + 1; + tracing::info!("width: {}", width); + if width < Self::MIN_WIDTH { + return None; + } + let height = Self::get_height(world, cur, dir, shape); + tracing::info!("height: {}", height); + if height < Self::MIN_HEIGHT { + return None; + } + // Measure width (walk right from bottom_left) + + // Validate entire frame + if !Self::validate_frame(world, cur, width, height, dir.opposite(), shape) { + tracing::info!("invalid frame"); + return None; + } + + Some(Self { + axis, + bottom_left: cur, + width, + height, + portal: shape.portal, + }) + } + + /// Returns the width - 1 of the portal interior starting from the given position. + fn get_width(world: &World, pos: BlockPos, direction: Direction, shape: &PortalShape) -> u32 { + for i in 1..Self::MAX_WIDTH { + let next = pos.relative_n(direction, i as i32); + if !Self::is_valid_interior(world, next) && Self::is_frame_block(world, next, shape) { + return i - 1; + } + if !Self::is_frame_block(world, next.below(), shape) { + return 0; + } + } + 0 + } + fn get_height(world: &World, pos: BlockPos, direction: Direction, shape: &PortalShape) -> u32 { + let mut cur = pos; + for i in 1..Self::MAX_HEIGHT { + let next = cur.above(); + tracing::info!("next: {:?}", next); + if !Self::is_valid_interior(world, next) && Self::is_frame_block(world, next, shape) { + return i; + } + if !Self::is_frame_block(world, next.relative(direction), shape) { + return 0; + } + cur = next; + } + 0 + } + + fn is_frame_block(world: &World, pos: BlockPos, shape: &PortalShape) -> bool { + ptr::eq(world.get_block_state(&pos).get_block(), shape.frame) + } + + fn is_valid_interior(world: &World, pos: BlockPos) -> bool { + let block = world.get_block_state(&pos).get_block(); + ptr::eq(block, vanilla_blocks::AIR) || ptr::eq(block, vanilla_blocks::FIRE) + } + + fn validate_frame( + world: &World, + bottom_left: BlockPos, + width: u32, + height: u32, + direction: Direction, + shape: &PortalShape, + ) -> bool { + // Check top frame row + let top_row = bottom_left.above_n(height as i32); + for w in 0..width as i32 { + if !Self::is_frame_block(world, top_row.relative_n(direction, w), shape) { + return false; + } + } + + // Check right columns + interior + for h in 0..height as i32 { + // Right column + let height_pos = bottom_left.above_n(h); + if !Self::is_frame_block(world, height_pos.relative_n(direction, width as i32), shape) { + return false; + } + + // Interior blocks + for w in 0..width as i32 { + if !Self::is_valid_interior(world, height_pos.relative_n(direction, w)) { + return false; + } + } + } + + true + } + + /// Fills the interior with nether portal blocks. + pub fn place_portal_blocks(&self, world: &World) { + let portal_state = self + .portal + .default_state() + .set_value(&BlockStateProperties::HORIZONTAL_AXIS, self.axis); + let dir = match self.axis { + Axis::X => Direction::West, + Axis::Z => Direction::South, + Axis::Y => return, + }; + let flags = UpdateFlags::UPDATE_ALL; + for w in 0..self.width { + for h in 0..self.height { + world.set_block( + self.bottom_left.above_n(h as i32).relative_n(dir, w as i32), + portal_state, + flags, + ); + } + } + } +} From 91823524408672945644fab24b01a10572394a1a Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Sun, 1 Mar 2026 02:31:29 +0100 Subject: [PATCH 03/18] should be ready for PR --- .github/workflows/flint.yml | 78 ------------------------------------- 1 file changed, 78 deletions(-) delete mode 100644 .github/workflows/flint.yml diff --git a/.github/workflows/flint.yml b/.github/workflows/flint.yml deleted file mode 100644 index c9d6a200076..00000000000 --- a/.github/workflows/flint.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: Flint Tests - -on: - push: - branches: [main, dev,dimensions_new] - pull_request: - -env: - CARGO_TERM_COLOR: always - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout SteelMC - uses: actions/checkout@v4 - - - name: Add flint-steel to workspace - run: sed -i 's/"steel-crypto",/"steel-crypto",\n "flint-steel",/' Cargo.toml - - - name: Checkout flint-steel - uses: actions/checkout@v4 - with: - repository: FlintTestMC/flint-steel - ref: steel-dimensions - path: flint-steel - - - name: Clone JunkysGenerator - uses: actions/checkout@v4 - with: - repository: FlintTestMC/JunkysGenerator - path: flint/JunkysGenerator - - - name: Setup Rust nightly - uses: dtolnay/rust-toolchain@nightly - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: | - . -> target - flint/JunkysGenerator -> target - - - name: Build test generators - working-directory: flint/JunkysGenerator - run: cargo build --release -p create-portal -p destroy-portal - - - name: Generate create-portal tests - working-directory: flint/JunkysGenerator/create-portal - run: cargo run --release -p create-portal - - - name: Generate destroy-portal tests - working-directory: flint/JunkysGenerator/destroy-portal - run: cargo run --release -p destroy-portal - - - name: Check generated tests for created-portal - working-directory: flint/JunkysGenerator/create-portal - run: find ./test -type f -printf '.' | wc -c - - - name: Check generated tests for destroy-portal - working-directory: flint/JunkysGenerator/destroy-portal - run: find ./test -type f -printf '.' | wc -c - - - name: Clean up generator build artifacts - run: rm -rf flint/JunkysGenerator/target - - - name: Run flint-steel tests - timeout-minutes: 15 - env: - TEST_PATH: ${{ github.workspace }}/flint/JunkysGenerator - run: cargo test --release --lib -p flint-steel test_run_all_flint_benchmarks -- --nocapture - - - name: Upload Flint summary - if: always() - uses: actions/upload-artifact@v4 - with: - name: flint-summary - path: flint-steel/log/flint_summary.json From d4099ba9a9cafce59b2624e23b76b890861af54a Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Thu, 5 Mar 2026 18:44:14 +0100 Subject: [PATCH 04/18] remove tracing --- steel-core/src/portal/portal_shape.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/steel-core/src/portal/portal_shape.rs b/steel-core/src/portal/portal_shape.rs index 447e0154d8d..f87c81f6462 100644 --- a/steel-core/src/portal/portal_shape.rs +++ b/steel-core/src/portal/portal_shape.rs @@ -66,7 +66,6 @@ impl PortalTest { Axis::Z => Direction::North, Axis::Y => return None, }; - tracing::info!("axis: {:?}", dir); // searches the bottom obsidian let mut cur = pos; @@ -80,18 +79,13 @@ impl PortalTest { // searches for the left obsidian (-1) because we don't want to be at the obsidian block let to_left = Self::get_width(world, cur, dir, shape); - tracing::info!("to_left: {}", to_left); cur = cur.relative_n(dir, to_left as i32); - tracing::info!("left_bottom: {:?}", cur); - let width = Self::get_width(world, cur, dir.opposite(), shape) + 1; - tracing::info!("width: {}", width); if width < Self::MIN_WIDTH { return None; } let height = Self::get_height(world, cur, dir, shape); - tracing::info!("height: {}", height); if height < Self::MIN_HEIGHT { return None; } @@ -99,7 +93,6 @@ impl PortalTest { // Validate entire frame if !Self::validate_frame(world, cur, width, height, dir.opposite(), shape) { - tracing::info!("invalid frame"); return None; } @@ -129,7 +122,6 @@ impl PortalTest { let mut cur = pos; for i in 1..Self::MAX_HEIGHT { let next = cur.above(); - tracing::info!("next: {:?}", next); if !Self::is_valid_interior(world, next) && Self::is_frame_block(world, next, shape) { return i; } From 97c59dd24994fcb0ff2a15289cbefbb295446ebb Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Thu, 5 Mar 2026 23:49:14 +0100 Subject: [PATCH 05/18] rename structs and use the data and make comments --- steel-core/src/behavior/blocks/fire.rs | 25 +++++------- steel-core/src/portal/portal_shape.rs | 56 +++++++++++++++----------- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/steel-core/src/behavior/blocks/fire.rs b/steel-core/src/behavior/blocks/fire.rs index d3cc878d678..69167dd8976 100644 --- a/steel-core/src/behavior/blocks/fire.rs +++ b/steel-core/src/behavior/blocks/fire.rs @@ -1,6 +1,4 @@ -//! Barrel block behavior implementation. -//! -//! Opens a 27-slot container menu when right-clicked. +//! fire block behavior implementation. use steel_registry::blocks::BlockRef; use steel_registry::vanilla_blocks::{NETHER_PORTAL, OBSIDIAN}; @@ -8,19 +6,18 @@ use steel_utils::{BlockPos, BlockStateId}; use crate::behavior::block::BlockBehaviour; use crate::behavior::context::BlockPlaceContext; -use crate::portal::portal_shape::{PortalShape, PortalTest}; +use crate::portal::portal_shape::{PortalFrameConfig, PortalShape}; use crate::world::World; -/// Behavior for barrel blocks. +/// Behavior for fire blocks. /// -/// Barrels are container block entities with 27 slots (3x9 grid). -/// They use the same menu as chests but cannot form double containers. +/// Fire burns, makes hot, and hurts pub struct FireBlock { block: BlockRef, } impl FireBlock { - /// Creates a new barrel block behavior. + /// Creates a new fire block behavior. #[must_use] pub const fn new(block: BlockRef) -> Self { Self { block } @@ -40,14 +37,14 @@ impl BlockBehaviour for FireBlock { _old_state: BlockStateId, _moved_by_piston: bool, ) { - if let Some(tester) = PortalTest::find_portal_shape( + if let Some(tester) = PortalShape::find_portal_shape( world, pos, - &PortalShape { - min_x: 2, - max_x: 21, - min_y: 3, - max_y: 21, + &PortalFrameConfig { + min_width: 2, + max_width: 21, + min_height: 3, + max_height: 21, frame: OBSIDIAN, portal: NETHER_PORTAL, }, diff --git a/steel-core/src/portal/portal_shape.rs b/steel-core/src/portal/portal_shape.rs index f87c81f6462..5d7f195071e 100644 --- a/steel-core/src/portal/portal_shape.rs +++ b/steel-core/src/portal/portal_shape.rs @@ -13,7 +13,7 @@ use steel_utils::{BlockPos, Direction}; use crate::world::World; /// A detected portal shape with axis, position, and dimensions. -pub struct PortalTest { +pub struct PortalShape { /// The axis of the portal (X or Z). pub axis: Axis, /// Bottom-left corner of the portal interior. @@ -27,39 +27,39 @@ pub struct PortalTest { } /// Definition of a portal shape in rectangular form, like the nether portal frame. -pub struct PortalShape { +pub struct PortalFrameConfig { /// min size of the portal in x direction - pub min_x: u32, + pub min_width: u32, /// max size of the portal in x direction - pub max_x: u32, + pub max_width: u32, /// min size of the portal in y direction - pub min_y: u32, + pub min_height: u32, /// max size of the portal in y direction - pub max_y: u32, + pub max_height: u32, /// The block type of the frame. pub frame: BlockRef, /// The block type of the portal. pub portal: BlockRef, } -impl PortalTest { - const MIN_WIDTH: u32 = 2; - const MAX_WIDTH: u32 = 21; - const MIN_HEIGHT: u32 = 3; - const MAX_HEIGHT: u32 = 21; - +impl PortalShape { /// Tries to find a valid portal shape from a position inside or adjacent to a frame. pub fn find_portal_shape( world: &World, fire_pos: BlockPos, - shape: &PortalShape, + shape: &PortalFrameConfig, ) -> Option { Self::try_axis(world, fire_pos, Axis::X, shape) .or_else(|| Self::try_axis(world, fire_pos, Axis::Z, shape)) } /// Tries to find a valid portal /// It doesn't loop over the obsidian, it loops over the air in the portal, to get the size of the portal - fn try_axis(world: &World, pos: BlockPos, axis: Axis, shape: &PortalShape) -> Option { + fn try_axis( + world: &World, + pos: BlockPos, + axis: Axis, + shape: &PortalFrameConfig, + ) -> Option { // Width direction: portal axis=X means width along Z, axis=Z means width along X let dir: Direction = match axis { Axis::X => Direction::East, @@ -69,7 +69,7 @@ impl PortalTest { // searches the bottom obsidian let mut cur = pos; - for _ in 0..=Self::MAX_HEIGHT as i32 { + for _ in 0..=shape.max_height as i32 { let next = BlockPos::new(cur.x(), cur.y() - 1, cur.z()); if Self::is_frame_block(world, next, shape) { break; @@ -82,11 +82,11 @@ impl PortalTest { cur = cur.relative_n(dir, to_left as i32); let width = Self::get_width(world, cur, dir.opposite(), shape) + 1; - if width < Self::MIN_WIDTH { + if width < shape.min_width { return None; } let height = Self::get_height(world, cur, dir, shape); - if height < Self::MIN_HEIGHT { + if height < shape.min_height { return None; } // Measure width (walk right from bottom_left) @@ -106,8 +106,13 @@ impl PortalTest { } /// Returns the width - 1 of the portal interior starting from the given position. - fn get_width(world: &World, pos: BlockPos, direction: Direction, shape: &PortalShape) -> u32 { - for i in 1..Self::MAX_WIDTH { + fn get_width( + world: &World, + pos: BlockPos, + direction: Direction, + shape: &PortalFrameConfig, + ) -> u32 { + for i in 1..shape.max_width { let next = pos.relative_n(direction, i as i32); if !Self::is_valid_interior(world, next) && Self::is_frame_block(world, next, shape) { return i - 1; @@ -118,9 +123,14 @@ impl PortalTest { } 0 } - fn get_height(world: &World, pos: BlockPos, direction: Direction, shape: &PortalShape) -> u32 { + fn get_height( + world: &World, + pos: BlockPos, + direction: Direction, + shape: &PortalFrameConfig, + ) -> u32 { let mut cur = pos; - for i in 1..Self::MAX_HEIGHT { + for i in 1..shape.max_height { let next = cur.above(); if !Self::is_valid_interior(world, next) && Self::is_frame_block(world, next, shape) { return i; @@ -133,7 +143,7 @@ impl PortalTest { 0 } - fn is_frame_block(world: &World, pos: BlockPos, shape: &PortalShape) -> bool { + fn is_frame_block(world: &World, pos: BlockPos, shape: &PortalFrameConfig) -> bool { ptr::eq(world.get_block_state(&pos).get_block(), shape.frame) } @@ -148,7 +158,7 @@ impl PortalTest { width: u32, height: u32, direction: Direction, - shape: &PortalShape, + shape: &PortalFrameConfig, ) -> bool { // Check top frame row let top_row = bottom_left.above_n(height as i32); From 3801afaa258dd80bb5d5e4ad4ad99e598fba5e56 Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Fri, 6 Mar 2026 00:12:22 +0100 Subject: [PATCH 06/18] more tests in general needed and will follow, it's not 100% the same impl but in my opinion the same logic --- .../src/behavior/blocks/nether_portal_block.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/steel-core/src/behavior/blocks/nether_portal_block.rs b/steel-core/src/behavior/blocks/nether_portal_block.rs index 80a312bb6de..8438a78995d 100644 --- a/steel-core/src/behavior/blocks/nether_portal_block.rs +++ b/steel-core/src/behavior/blocks/nether_portal_block.rs @@ -1,14 +1,15 @@ //! Nether portal block behavior. +use crate::behavior::block::BlockBehaviour; +use crate::behavior::context::BlockPlaceContext; +use crate::world::World; use steel_registry::blocks::BlockRef; use steel_registry::blocks::block_state_ext::BlockStateExt; +use steel_registry::blocks::properties::BlockStateProperties; use steel_registry::vanilla_blocks::AIR; +use steel_utils::math::Axis; use steel_utils::{BlockPos, BlockStateId, Direction}; -use crate::behavior::block::BlockBehaviour; -use crate::behavior::context::BlockPlaceContext; -use crate::world::World; - /// Behavior for the nether portal block. pub struct NetherPortalBlock { #[allow(dead_code)] @@ -32,7 +33,11 @@ impl BlockBehaviour for NetherPortalBlock { _neighbor_pos: BlockPos, neighbor_state: BlockStateId, ) -> BlockStateId { - if neighbor_state.is_air() { + if neighbor_state.is_air() + && (state.get_value(&BlockStateProperties::AXIS) == _direction.axis() + || _direction.axis() == Axis::Y) + && neighbor_state.0 != state.0 + { return AIR.default_state(); } state From 787ed0cf915de4c705e19a71d8fc42ae6ef8997f Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Fri, 6 Mar 2026 01:33:31 +0100 Subject: [PATCH 07/18] clippy --- steel-core/src/behavior/blocks/nether_portal_block.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/steel-core/src/behavior/blocks/nether_portal_block.rs b/steel-core/src/behavior/blocks/nether_portal_block.rs index 8438a78995d..cc84db7a19b 100644 --- a/steel-core/src/behavior/blocks/nether_portal_block.rs +++ b/steel-core/src/behavior/blocks/nether_portal_block.rs @@ -29,13 +29,13 @@ impl BlockBehaviour for NetherPortalBlock { state: BlockStateId, _world: &World, _pos: BlockPos, - _direction: Direction, + direction: Direction, _neighbor_pos: BlockPos, neighbor_state: BlockStateId, ) -> BlockStateId { if neighbor_state.is_air() - && (state.get_value(&BlockStateProperties::AXIS) == _direction.axis() - || _direction.axis() == Axis::Y) + && (state.get_value(&BlockStateProperties::AXIS) == direction.axis() + || direction.axis() == Axis::Y) && neighbor_state.0 != state.0 { return AIR.default_state(); From b6a15d86a10535bdcdeb888fc08954bf795c576f Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Wed, 11 Mar 2026 12:29:56 +0100 Subject: [PATCH 08/18] clippy --- steel-core/build/items.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/steel-core/build/items.rs b/steel-core/build/items.rs index b4dae3893e5..bda4919a6b4 100644 --- a/steel-core/build/items.rs +++ b/steel-core/build/items.rs @@ -124,6 +124,7 @@ fn generate_simple_registrations<'a>( quote! { #(#registrations)* } } +#[allow(clippy::too_many_lines)] pub fn build(items: &[ItemClass]) -> String { let mut block_items: Vec<(Ident, Ident)> = Vec::new(); let mut sign_items: Vec<(Ident, Ident, Ident)> = Vec::new(); From cef3c3ebfbe46a6dc985d7b2439bede55c7a8568 Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Wed, 11 Mar 2026 14:32:20 +0100 Subject: [PATCH 09/18] make clippy happy and run tests --- .github/workflows/flint.yml | 78 +++++++++++++++++++++++++++ steel-core/src/portal/portal_shape.rs | 6 +-- 2 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/flint.yml diff --git a/.github/workflows/flint.yml b/.github/workflows/flint.yml new file mode 100644 index 00000000000..c8e3bce7343 --- /dev/null +++ b/.github/workflows/flint.yml @@ -0,0 +1,78 @@ +name: Flint Tests + +on: + push: + branches: [main, dev,dimensions] + pull_request: + +env: + CARGO_TERM_COLOR: always + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout SteelMC + uses: actions/checkout@v4 + + - name: Add flint-steel to workspace + run: sed -i 's/"steel-crypto",/"steel-crypto",\n "flint-steel",/' Cargo.toml + + - name: Checkout flint-steel + uses: actions/checkout@v4 + with: + repository: FlintTestMC/flint-steel + ref: steel-dimensions + path: flint-steel + + - name: Clone JunkysGenerator + uses: actions/checkout@v4 + with: + repository: FlintTestMC/JunkysGenerator + path: flint/JunkysGenerator + + - name: Setup Rust nightly + uses: dtolnay/rust-toolchain@nightly + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + . -> target + flint/JunkysGenerator -> target + + - name: Build test generators + working-directory: flint/JunkysGenerator + run: cargo build --release -p create-portal -p destroy-portal + + - name: Generate create-portal tests + working-directory: flint/JunkysGenerator/create-portal + run: cargo run --release -p create-portal + + - name: Generate destroy-portal tests + working-directory: flint/JunkysGenerator/destroy-portal + run: cargo run --release -p destroy-portal + + - name: Check generated tests for created-portal + working-directory: flint/JunkysGenerator/create-portal + run: find ./test -type f -printf '.' | wc -c + + - name: Check generated tests for destroy-portal + working-directory: flint/JunkysGenerator/destroy-portal + run: find ./test -type f -printf '.' | wc -c + + - name: Clean up generator build artifacts + run: rm -rf flint/JunkysGenerator/target + + - name: Run flint-steel tests + timeout-minutes: 15 + env: + TEST_PATH: ${{ github.workspace }}/flint/JunkysGenerator + run: cargo test --release --lib -p flint-steel test_run_all_flint_benchmarks -- --nocapture + + - name: Upload Flint summary + if: always() + uses: actions/upload-artifact@v4 + with: + name: flint-summary + path: flint-steel/log/flint_summary.json diff --git a/steel-core/src/portal/portal_shape.rs b/steel-core/src/portal/portal_shape.rs index 5d7f195071e..207853d3c7f 100644 --- a/steel-core/src/portal/portal_shape.rs +++ b/steel-core/src/portal/portal_shape.rs @@ -1,7 +1,5 @@ //! Portal shape detection for validating obsidian frames. -use std::ptr; - use steel_registry::blocks::BlockRef; use steel_registry::blocks::block_state_ext::BlockStateExt; use steel_registry::blocks::properties::BlockStateProperties; @@ -144,12 +142,12 @@ impl PortalShape { } fn is_frame_block(world: &World, pos: BlockPos, shape: &PortalFrameConfig) -> bool { - ptr::eq(world.get_block_state(&pos).get_block(), shape.frame) + world.get_block_state(&pos).get_block() == shape.frame } fn is_valid_interior(world: &World, pos: BlockPos) -> bool { let block = world.get_block_state(&pos).get_block(); - ptr::eq(block, vanilla_blocks::AIR) || ptr::eq(block, vanilla_blocks::FIRE) + block == vanilla_blocks::AIR || block == vanilla_blocks::FIRE } fn validate_frame( From 25adc82f9b9cea35bcfe9e3d361bf8c37473802b Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Wed, 11 Mar 2026 16:29:04 +0100 Subject: [PATCH 10/18] try now --- .github/workflows/flint.yml | 47 +++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/.github/workflows/flint.yml b/.github/workflows/flint.yml index c8e3bce7343..3310c862c08 100644 --- a/.github/workflows/flint.yml +++ b/.github/workflows/flint.yml @@ -2,7 +2,7 @@ name: Flint Tests on: push: - branches: [main, dev,dimensions] + branches: [main, dev, ignite_portal] pull_request: env: @@ -14,22 +14,21 @@ jobs: steps: - name: Checkout SteelMC uses: actions/checkout@v4 - - - name: Add flint-steel to workspace - run: sed -i 's/"steel-crypto",/"steel-crypto",\n "flint-steel",/' Cargo.toml + with: + path: SteelMC - name: Checkout flint-steel uses: actions/checkout@v4 with: repository: FlintTestMC/flint-steel - ref: steel-dimensions + ref: main path: flint-steel - name: Clone JunkysGenerator uses: actions/checkout@v4 with: repository: FlintTestMC/JunkysGenerator - path: flint/JunkysGenerator + path: JunkysGenerator - name: Setup Rust nightly uses: dtolnay/rust-toolchain@nightly @@ -38,37 +37,49 @@ jobs: uses: Swatinem/rust-cache@v2 with: workspaces: | - . -> target - flint/JunkysGenerator -> target + flint-steel -> target + JunkysGenerator -> target - name: Build test generators - working-directory: flint/JunkysGenerator + working-directory: JunkysGenerator run: cargo build --release -p create-portal -p destroy-portal - name: Generate create-portal tests - working-directory: flint/JunkysGenerator/create-portal + working-directory: JunkysGenerator/create-portal run: cargo run --release -p create-portal - name: Generate destroy-portal tests - working-directory: flint/JunkysGenerator/destroy-portal + working-directory: JunkysGenerator/destroy-portal run: cargo run --release -p destroy-portal - - name: Check generated tests for created-portal - working-directory: flint/JunkysGenerator/create-portal + - name: Check generated tests for create-portal + working-directory: JunkysGenerator/create-portal run: find ./test -type f -printf '.' | wc -c - name: Check generated tests for destroy-portal - working-directory: flint/JunkysGenerator/destroy-portal + working-directory: JunkysGenerator/destroy-portal run: find ./test -type f -printf '.' | wc -c - name: Clean up generator build artifacts - run: rm -rf flint/JunkysGenerator/target - + run: rm -rf JunkysGenerator/target + + - name: Configure SteelMC patch + working-directory: steel/flint-steel + run: | + mkdir -p .cargo + cat > .cargo/config.toml << 'EOF' + [patch."https://github.com/Steel-Foundation/SteelMC.git"] + steel-core = { path = "../SteelMC/steel-core" } + steel-protocol = { path = "../SteelMC/steel-protocol" } + steel-registry = { path = "../SteelMC/steel-registry" } + steel-utils = { path = "../SteelMC/steel-utils" } + EOF - name: Run flint-steel tests + working-directory: flint-steel timeout-minutes: 15 env: - TEST_PATH: ${{ github.workspace }}/flint/JunkysGenerator - run: cargo test --release --lib -p flint-steel test_run_all_flint_benchmarks -- --nocapture + TEST_PATH: ${{ github.workspace }}/JunkysGenerator + run: cargo test --profile cicd --lib test_run_all_flint_benchmarks -- --nocapture - name: Upload Flint summary if: always() From 88663be9347294b3acfdbf0091201576b77d7827 Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Wed, 11 Mar 2026 16:59:04 +0100 Subject: [PATCH 11/18] working on cicd --- .github/workflows/flint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/flint.yml b/.github/workflows/flint.yml index 3310c862c08..1804bd51949 100644 --- a/.github/workflows/flint.yml +++ b/.github/workflows/flint.yml @@ -64,7 +64,7 @@ jobs: run: rm -rf JunkysGenerator/target - name: Configure SteelMC patch - working-directory: steel/flint-steel + working-directory: flint-steel run: | mkdir -p .cargo cat > .cargo/config.toml << 'EOF' From bdec6262d3f0e7459f011c8a2207ace58fbb7a2d Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Wed, 11 Mar 2026 17:30:29 +0100 Subject: [PATCH 12/18] working on cicd --- .github/workflows/flint.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/flint.yml b/.github/workflows/flint.yml index 1804bd51949..7f51ccc743b 100644 --- a/.github/workflows/flint.yml +++ b/.github/workflows/flint.yml @@ -30,6 +30,13 @@ jobs: repository: FlintTestMC/JunkysGenerator path: JunkysGenerator + - name: Clone FlintBenchmark + uses: actions/checkout@v4 + with: + repository: JunkyDeveloper/FlintBenchmark + ref: walls + path: FlintBenchmark + - name: Setup Rust nightly uses: dtolnay/rust-toolchain@nightly @@ -81,6 +88,13 @@ jobs: TEST_PATH: ${{ github.workspace }}/JunkysGenerator run: cargo test --profile cicd --lib test_run_all_flint_benchmarks -- --nocapture + - name: Run flint-steel tests + working-directory: flint-steel + timeout-minutes: 15 + env: + TEST_PATH: ${{ github.workspace }}/FlintBenchmark/tests + run: cargo test --profile cicd --lib test_run_all_flint_benchmarks -- --nocapture + - name: Upload Flint summary if: always() uses: actions/upload-artifact@v4 From 6e72dd4a12339ec83be72e662c9d104dc80dddae Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Wed, 11 Mar 2026 17:46:58 +0100 Subject: [PATCH 13/18] cicd now happy? --- steel-core/src/behavior/blocks/nether_portal_block.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/steel-core/src/behavior/blocks/nether_portal_block.rs b/steel-core/src/behavior/blocks/nether_portal_block.rs index cc84db7a19b..0d311274827 100644 --- a/steel-core/src/behavior/blocks/nether_portal_block.rs +++ b/steel-core/src/behavior/blocks/nether_portal_block.rs @@ -34,9 +34,9 @@ impl BlockBehaviour for NetherPortalBlock { neighbor_state: BlockStateId, ) -> BlockStateId { if neighbor_state.is_air() - && (state.get_value(&BlockStateProperties::AXIS) == direction.axis() - || direction.axis() == Axis::Y) - && neighbor_state.0 != state.0 + //&& (state.get_value(&BlockStateProperties::AXIS) == direction.axis() + // || direction.axis() == Axis::Y) + //&& neighbor_state.0 != state.0 { return AIR.default_state(); } From 6c5f25bb3d6c9b19743e5dfc1dbb1ac807839c7e Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Wed, 11 Mar 2026 17:55:36 +0100 Subject: [PATCH 14/18] cicd now happy? --- steel-core/src/behavior/blocks/nether_portal_block.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/steel-core/src/behavior/blocks/nether_portal_block.rs b/steel-core/src/behavior/blocks/nether_portal_block.rs index 0d311274827..4c14efcf3c3 100644 --- a/steel-core/src/behavior/blocks/nether_portal_block.rs +++ b/steel-core/src/behavior/blocks/nether_portal_block.rs @@ -5,9 +5,7 @@ use crate::behavior::context::BlockPlaceContext; use crate::world::World; use steel_registry::blocks::BlockRef; use steel_registry::blocks::block_state_ext::BlockStateExt; -use steel_registry::blocks::properties::BlockStateProperties; use steel_registry::vanilla_blocks::AIR; -use steel_utils::math::Axis; use steel_utils::{BlockPos, BlockStateId, Direction}; /// Behavior for the nether portal block. @@ -29,7 +27,7 @@ impl BlockBehaviour for NetherPortalBlock { state: BlockStateId, _world: &World, _pos: BlockPos, - direction: Direction, + _direction: Direction, _neighbor_pos: BlockPos, neighbor_state: BlockStateId, ) -> BlockStateId { From 3d75902f396431a10e63c7735b6ea3e8ec349123 Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Wed, 11 Mar 2026 18:32:23 +0100 Subject: [PATCH 15/18] going home --- steel-core/src/behavior/blocks/fire.rs | 16 +-- .../behavior/blocks/nether_portal_block.rs | 21 +-- steel-core/src/portal/portal_shape.rs | 120 +++++++++++++----- 3 files changed, 100 insertions(+), 57 deletions(-) diff --git a/steel-core/src/behavior/blocks/fire.rs b/steel-core/src/behavior/blocks/fire.rs index 69167dd8976..8631dbeec87 100644 --- a/steel-core/src/behavior/blocks/fire.rs +++ b/steel-core/src/behavior/blocks/fire.rs @@ -1,12 +1,11 @@ //! fire block behavior implementation. use steel_registry::blocks::BlockRef; -use steel_registry::vanilla_blocks::{NETHER_PORTAL, OBSIDIAN}; use steel_utils::{BlockPos, BlockStateId}; use crate::behavior::block::BlockBehaviour; use crate::behavior::context::BlockPlaceContext; -use crate::portal::portal_shape::{PortalFrameConfig, PortalShape}; +use crate::portal::portal_shape::{PortalShape, nether_portal_config}; use crate::world::World; /// Behavior for fire blocks. @@ -37,18 +36,7 @@ impl BlockBehaviour for FireBlock { _old_state: BlockStateId, _moved_by_piston: bool, ) { - if let Some(tester) = PortalShape::find_portal_shape( - world, - pos, - &PortalFrameConfig { - min_width: 2, - max_width: 21, - min_height: 3, - max_height: 21, - frame: OBSIDIAN, - portal: NETHER_PORTAL, - }, - ) { + if let Some(tester) = PortalShape::find_portal_shape(world, pos, &nether_portal_config()) { tester.place_portal_blocks(world); // TODO: Play ignite sound, damage item } diff --git a/steel-core/src/behavior/blocks/nether_portal_block.rs b/steel-core/src/behavior/blocks/nether_portal_block.rs index 4c14efcf3c3..1d01937391c 100644 --- a/steel-core/src/behavior/blocks/nether_portal_block.rs +++ b/steel-core/src/behavior/blocks/nether_portal_block.rs @@ -2,15 +2,17 @@ use crate::behavior::block::BlockBehaviour; use crate::behavior::context::BlockPlaceContext; +use crate::portal::portal_shape::{PortalShape, nether_portal_config}; use crate::world::World; use steel_registry::blocks::BlockRef; use steel_registry::blocks::block_state_ext::BlockStateExt; +use steel_registry::blocks::properties::BlockStateProperties; use steel_registry::vanilla_blocks::AIR; +use steel_utils::math::Axis; use steel_utils::{BlockPos, BlockStateId, Direction}; /// Behavior for the nether portal block. pub struct NetherPortalBlock { - #[allow(dead_code)] block: BlockRef, } impl NetherPortalBlock { @@ -25,16 +27,19 @@ impl BlockBehaviour for NetherPortalBlock { fn update_shape( &self, state: BlockStateId, - _world: &World, - _pos: BlockPos, - _direction: Direction, + world: &World, + pos: BlockPos, + direction: Direction, _neighbor_pos: BlockPos, neighbor_state: BlockStateId, ) -> BlockStateId { - if neighbor_state.is_air() - //&& (state.get_value(&BlockStateProperties::AXIS) == direction.axis() - // || direction.axis() == Axis::Y) - //&& neighbor_state.0 != state.0 + let update_axis = direction.get_axis(); + let axis: Axis = state.get_value(&BlockStateProperties::HORIZONTAL_AXIS); + let wrong_axis = axis != update_axis && update_axis != Axis::Y; + + if !wrong_axis + && neighbor_state.get_block() != self.block + && PortalShape::find_any_shape(world, pos, axis, &nether_portal_config()).is_none() { return AIR.default_state(); } diff --git a/steel-core/src/portal/portal_shape.rs b/steel-core/src/portal/portal_shape.rs index 207853d3c7f..e1aef754ab6 100644 --- a/steel-core/src/portal/portal_shape.rs +++ b/steel-core/src/portal/portal_shape.rs @@ -40,23 +40,63 @@ pub struct PortalFrameConfig { pub portal: BlockRef, } +/// Returns the standard nether portal frame configuration. +pub fn nether_portal_config() -> PortalFrameConfig { + PortalFrameConfig { + min_width: 2, + max_width: 21, + min_height: 3, + max_height: 21, + frame: vanilla_blocks::OBSIDIAN, + portal: vanilla_blocks::NETHER_PORTAL, + } +} + +/// Interior check: air or fire only (used when creating a new portal). +fn is_empty_interior(world: &World, pos: BlockPos, _config: &PortalFrameConfig) -> bool { + let block = world.get_block_state(&pos).get_block(); + block == vanilla_blocks::AIR || block == vanilla_blocks::FIRE +} + +/// Interior check: air, fire, or existing portal blocks (used when validating an existing portal). +fn is_portal_or_empty_interior(world: &World, pos: BlockPos, config: &PortalFrameConfig) -> bool { + let block = world.get_block_state(&pos).get_block(); + block == vanilla_blocks::AIR || block == vanilla_blocks::FIRE || block == config.portal +} + +/// Interior validator function signature. +type InteriorCheck = fn(&World, BlockPos, &PortalFrameConfig) -> bool; + impl PortalShape { /// Tries to find a valid portal shape from a position inside or adjacent to a frame. pub fn find_portal_shape( world: &World, fire_pos: BlockPos, - shape: &PortalFrameConfig, + config: &PortalFrameConfig, + ) -> Option { + Self::try_axis(world, fire_pos, Axis::X, config, is_empty_interior) + .or_else(|| Self::try_axis(world, fire_pos, Axis::Z, config, is_empty_interior)) + } + + /// Finds a portal shape on a specific axis, treating existing portal blocks as valid interior. + /// Used by `update_shape` to check if the portal frame is still complete. + pub fn find_any_shape( + world: &World, + pos: BlockPos, + axis: Axis, + config: &PortalFrameConfig, ) -> Option { - Self::try_axis(world, fire_pos, Axis::X, shape) - .or_else(|| Self::try_axis(world, fire_pos, Axis::Z, shape)) + Self::try_axis(world, pos, axis, config, is_portal_or_empty_interior) } - /// Tries to find a valid portal - /// It doesn't loop over the obsidian, it loops over the air in the portal, to get the size of the portal + + /// Tries to find a valid portal on a single axis. + /// It loops over the interior (not frame blocks) to determine the portal dimensions. fn try_axis( world: &World, pos: BlockPos, axis: Axis, - shape: &PortalFrameConfig, + config: &PortalFrameConfig, + interior_check: InteriorCheck, ) -> Option { // Width direction: portal axis=X means width along Z, axis=Z means width along X let dir: Direction = match axis { @@ -67,30 +107,37 @@ impl PortalShape { // searches the bottom obsidian let mut cur = pos; - for _ in 0..=shape.max_height as i32 { + for _ in 0..=config.max_height as i32 { let next = BlockPos::new(cur.x(), cur.y() - 1, cur.z()); - if Self::is_frame_block(world, next, shape) { + if Self::is_frame_block(world, next, config) { break; } cur = next; } // searches for the left obsidian (-1) because we don't want to be at the obsidian block - let to_left = Self::get_width(world, cur, dir, shape); + let to_left = Self::get_width(world, cur, dir, config, interior_check); cur = cur.relative_n(dir, to_left as i32); - let width = Self::get_width(world, cur, dir.opposite(), shape) + 1; - if width < shape.min_width { + let width = Self::get_width(world, cur, dir.opposite(), config, interior_check) + 1; + if width < config.min_width { return None; } - let height = Self::get_height(world, cur, dir, shape); - if height < shape.min_height { + let height = Self::get_height(world, cur, dir, config, interior_check); + if height < config.min_height { return None; } - // Measure width (walk right from bottom_left) // Validate entire frame - if !Self::validate_frame(world, cur, width, height, dir.opposite(), shape) { + if !Self::validate_frame( + world, + cur, + width, + height, + dir.opposite(), + config, + interior_check, + ) { return None; } @@ -99,7 +146,7 @@ impl PortalShape { bottom_left: cur, width, height, - portal: shape.portal, + portal: config.portal, }) } @@ -108,32 +155,35 @@ impl PortalShape { world: &World, pos: BlockPos, direction: Direction, - shape: &PortalFrameConfig, + config: &PortalFrameConfig, + interior_check: InteriorCheck, ) -> u32 { - for i in 1..shape.max_width { + for i in 1..config.max_width { let next = pos.relative_n(direction, i as i32); - if !Self::is_valid_interior(world, next) && Self::is_frame_block(world, next, shape) { + if !interior_check(world, next, config) && Self::is_frame_block(world, next, config) { return i - 1; } - if !Self::is_frame_block(world, next.below(), shape) { + if !Self::is_frame_block(world, next.below(), config) { return 0; } } 0 } + fn get_height( world: &World, pos: BlockPos, direction: Direction, - shape: &PortalFrameConfig, + config: &PortalFrameConfig, + interior_check: InteriorCheck, ) -> u32 { let mut cur = pos; - for i in 1..shape.max_height { + for i in 1..config.max_height { let next = cur.above(); - if !Self::is_valid_interior(world, next) && Self::is_frame_block(world, next, shape) { + if !interior_check(world, next, config) && Self::is_frame_block(world, next, config) { return i; } - if !Self::is_frame_block(world, next.relative(direction), shape) { + if !Self::is_frame_block(world, next.relative(direction), config) { return 0; } cur = next; @@ -141,13 +191,8 @@ impl PortalShape { 0 } - fn is_frame_block(world: &World, pos: BlockPos, shape: &PortalFrameConfig) -> bool { - world.get_block_state(&pos).get_block() == shape.frame - } - - fn is_valid_interior(world: &World, pos: BlockPos) -> bool { - let block = world.get_block_state(&pos).get_block(); - block == vanilla_blocks::AIR || block == vanilla_blocks::FIRE + fn is_frame_block(world: &World, pos: BlockPos, config: &PortalFrameConfig) -> bool { + world.get_block_state(&pos).get_block() == config.frame } fn validate_frame( @@ -156,12 +201,13 @@ impl PortalShape { width: u32, height: u32, direction: Direction, - shape: &PortalFrameConfig, + config: &PortalFrameConfig, + interior_check: InteriorCheck, ) -> bool { // Check top frame row let top_row = bottom_left.above_n(height as i32); for w in 0..width as i32 { - if !Self::is_frame_block(world, top_row.relative_n(direction, w), shape) { + if !Self::is_frame_block(world, top_row.relative_n(direction, w), config) { return false; } } @@ -170,13 +216,17 @@ impl PortalShape { for h in 0..height as i32 { // Right column let height_pos = bottom_left.above_n(h); - if !Self::is_frame_block(world, height_pos.relative_n(direction, width as i32), shape) { + if !Self::is_frame_block( + world, + height_pos.relative_n(direction, width as i32), + config, + ) { return false; } // Interior blocks for w in 0..width as i32 { - if !Self::is_valid_interior(world, height_pos.relative_n(direction, w)) { + if !interior_check(world, height_pos.relative_n(direction, w), config) { return false; } } From 2657f1336e109f5554179d1615fd4dd6d1f9403d Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Thu, 12 Mar 2026 00:40:57 +0100 Subject: [PATCH 16/18] test --- .github/workflows/flint.yml | 116 ++++++++++++++++++++++++------------ 1 file changed, 78 insertions(+), 38 deletions(-) diff --git a/.github/workflows/flint.yml b/.github/workflows/flint.yml index 7f51ccc743b..74c16462681 100644 --- a/.github/workflows/flint.yml +++ b/.github/workflows/flint.yml @@ -2,9 +2,11 @@ name: Flint Tests on: push: - branches: [main, dev, ignite_portal] + branches: ['master'] + tags: ['**'] pull_request: + env: CARGO_TERM_COLOR: always @@ -24,17 +26,10 @@ jobs: ref: main path: flint-steel - - name: Clone JunkysGenerator - uses: actions/checkout@v4 - with: - repository: FlintTestMC/JunkysGenerator - path: JunkysGenerator - - name: Clone FlintBenchmark uses: actions/checkout@v4 with: repository: JunkyDeveloper/FlintBenchmark - ref: walls path: FlintBenchmark - name: Setup Rust nightly @@ -45,30 +40,6 @@ jobs: with: workspaces: | flint-steel -> target - JunkysGenerator -> target - - - name: Build test generators - working-directory: JunkysGenerator - run: cargo build --release -p create-portal -p destroy-portal - - - name: Generate create-portal tests - working-directory: JunkysGenerator/create-portal - run: cargo run --release -p create-portal - - - name: Generate destroy-portal tests - working-directory: JunkysGenerator/destroy-portal - run: cargo run --release -p destroy-portal - - - name: Check generated tests for create-portal - working-directory: JunkysGenerator/create-portal - run: find ./test -type f -printf '.' | wc -c - - - name: Check generated tests for destroy-portal - working-directory: JunkysGenerator/destroy-portal - run: find ./test -type f -printf '.' | wc -c - - - name: Clean up generator build artifacts - run: rm -rf JunkysGenerator/target - name: Configure SteelMC patch working-directory: flint-steel @@ -81,20 +52,89 @@ jobs: steel-registry = { path = "../SteelMC/steel-registry" } steel-utils = { path = "../SteelMC/steel-utils" } EOF - - name: Run flint-steel tests - working-directory: flint-steel - timeout-minutes: 15 - env: - TEST_PATH: ${{ github.workspace }}/JunkysGenerator - run: cargo test --profile cicd --lib test_run_all_flint_benchmarks -- --nocapture - name: Run flint-steel tests + id: tests working-directory: flint-steel + continue-on-error: true timeout-minutes: 15 env: TEST_PATH: ${{ github.workspace }}/FlintBenchmark/tests run: cargo test --profile cicd --lib test_run_all_flint_benchmarks -- --nocapture + - name: Download baseline + id: baseline + uses: dawidd6/action-download-artifact@v6 + continue-on-error: true + with: + name: flint-baseline + branch: master + workflow: flint.yml + path: baseline + + - name: Check for regressions + id: regression + run: | + SUMMARY="flint-steel/log/flint_summary.json" + BASELINE="baseline/flint_baseline.json" + + if [ ! -f "$SUMMARY" ]; then + echo "::error::No flint_summary.json produced — tests likely failed to compile" + exit 1 + fi + + if [ ! -f "$BASELINE" ]; then + echo "No baseline found — skipping regression check" + exit 0 + fi + + # Find tests that passed in baseline but fail now + REGRESSIONS=$(jq -n \ + --slurpfile base "$BASELINE" \ + --slurpfile curr "$SUMMARY" \ + '($base[0] | map(select(.success == true)) | map(.name)) as $required | + [$curr[0][] | select(.success == false and IN(.name; $required[]))] | + map(.name)') + + if [ "$REGRESSIONS" != "[]" ]; then + echo "::error::Regressions detected: $REGRESSIONS" + echo "$REGRESSIONS" | jq -r '.[]' | while read -r name; do + echo " - $name" + done + exit 1 + fi + + echo "No regressions detected" + + - name: Merge and upload baseline + if: github.ref == 'refs/heads/master' && steps.regression.outcome == 'success' + run: | + SUMMARY="flint-steel/log/flint_summary.json" + BASELINE="baseline/flint_baseline.json" + MERGED="flint_baseline.json" + + if [ -f "$BASELINE" ]; then + # Merge: old baseline successes ∪ current successes + jq -n \ + --slurpfile base "$BASELINE" \ + --slurpfile curr "$SUMMARY" \ + '($base[0] + $curr[0]) | group_by(.name) | map({ + name: .[0].name, + ids: .[-1].ids, + success: (map(.success) | any) + })' > "$MERGED" + else + # First run — current results become the baseline + cp "$SUMMARY" "$MERGED" + fi + + - name: Upload baseline artifact + if: github.ref == 'refs/heads/master' && steps.regression.outcome == 'success' + uses: actions/upload-artifact@v4 + with: + name: flint-baseline + path: flint_baseline.json + - name: Upload Flint summary if: always() uses: actions/upload-artifact@v4 From 56cb6c09101ccfd7a1fd711fee09e77e1e80eaeb Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Thu, 12 Mar 2026 00:43:55 +0100 Subject: [PATCH 17/18] add in general --- steel-core/src/portal/portal_shape.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/steel-core/src/portal/portal_shape.rs b/steel-core/src/portal/portal_shape.rs index e1aef754ab6..e64a03c11e8 100644 --- a/steel-core/src/portal/portal_shape.rs +++ b/steel-core/src/portal/portal_shape.rs @@ -41,6 +41,7 @@ pub struct PortalFrameConfig { } /// Returns the standard nether portal frame configuration. +#[must_use] pub fn nether_portal_config() -> PortalFrameConfig { PortalFrameConfig { min_width: 2, From 35ecbbbe1ce39178412550a5aa990618fdc320b4 Mon Sep 17 00:00:00 2001 From: JunkyDeveloper Date: Thu, 12 Mar 2026 00:48:07 +0100 Subject: [PATCH 18/18] ok... --- .github/workflows/flint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/flint.yml b/.github/workflows/flint.yml index 74c16462681..69c38417df7 100644 --- a/.github/workflows/flint.yml +++ b/.github/workflows/flint.yml @@ -30,6 +30,7 @@ jobs: uses: actions/checkout@v4 with: repository: JunkyDeveloper/FlintBenchmark + ref: walls path: FlintBenchmark - name: Setup Rust nightly