The chest in the stronghold square room was placed as a bare block state,
so no chest block entity was ever created for it. Opening a chest and
resolving a double chest both go through `get_block_entity`, so the chest
could neither be opened nor paired with a chest placed next to it.
Use the existing `add_chest` helper, which writes the block state and the
matching block entity with a loot table, as the nether fortress corridors
already do. Vanilla `StrongholdPieces$RoomCrossing` uses
`chests/stronghold_crossing` at the same position.
Implements the full vanilla Minecraft 26.2 mob equipment system, giving
mobs weapons, armor, enchantments, and loot-pickup flags at spawn.
Added:
- Data-driven EQUIPMENT_REGISTRY mapping 13 mob types (zombie, husk,
zombie_villager, drowned, zombified_piglin, skeleton, stray, bogged,
wither_skeleton, piglin, pillager, vindicator) to their weapon and
armor configurations, all verified against decompiled vanilla sources.
- Exact vanilla RegionalDifficulty formula from DifficultyInstance.java:
computes effective_difficulty and special_multiplier from base
difficulty, game time, chunk inhabited time, and moon phase. When
special_multiplier is 0.0 (fresh Normal/Easy worlds), equipment,
enchantments, and loot-pickup flags are suppressed, matching vanilla.
- Vanilla-accurate armor selection: random base tier (0-2) with up to 3
upgrade attempts at 10.87% each, partial armor break chance (10% on
Hard, 25% otherwise). Higher difficulty produces fewer pieces.
- Weighted enchantment selection with exclusive-set conflict resolution
and cost-based level determination. Curated flat pools per equipment
category (melee/trident/bow/crossbow/fishing_rod/head/chest/legs/feet).
- Per-slot equipment drop chances (0.085 default) with looting bonus
(+lootingLevel * 0.01 per vanilla EnchantmentHelper).
- Exact vanilla drop durability formula from Mob.dropCustomDeathLoot:
damage = maxDamage - random(1 + random(max(maxDamage-3, 1))).
- CAN_PICK_UP_LOOT mob flag (bit 3, value 8) set probabilistically at
55% * special_multiplier.
- Guard against empty equipment packets in send_equipment_changes to
prevent client decode crashes.
- Chunk inhabited_time tracking in world tick for difficulty scaling.
Integration: equip_mob_on_spawn is called from the blanket
EntityBase::init_data_tracker impl, so every mob type automatically
participates. New mobs are added by inserting a MobEquipmentDef into
the EQUIPMENT_REGISTRY with their weapon/armor config.
cargo check: passes / cargo clippy (pedantic): 0 warnings / cargo fmt: clean
Implements the three vanilla save-control commands (tracked in #15):
- /save-all saves all online players' data and advancements, then
requests a chunk save from every world's chunk scheduler. It works
even while autosaving is disabled, matching Vanilla.
- /save-off disables periodic autosaving via a new save_enabled flag
on Level; running it again fails with commands.save.alreadyOff.
- /save-on re-enables autosaving; running it again fails with
commands.save.alreadyOn.
* perf(generation): cache computed structure starts
set_structure_references runs for every chunk and, for each nearby structure
candidate, recomputed the structure's placement from scratch. For jigsaw
structures (villages, ancient cities, ...) that means re-running the full
jigsaw expansion for every chunk whose references overlap the structure -- the
same start recomputed many times over.
A structure's placement depends only on its start chunk and the world seed (the
surface-height estimate it uses is position-independent and min_y is constant
per dimension), so memoize it in GlobalStructureCache and reuse it. In the
bench, structure references drop from ~342us to ~105us.
* perf(lighting): use a fast hasher in the generation light engine
The BFS light propagator's visited/shadow_cache/pending_updates maps were
aliased to std HashSet/HashMap (SipHash) despite being named "Fast". They are
probed on every neighbour of every propagated block, so the hash function
dominates. Point the aliases at rustc-hash's FxHash (already a dependency).
Lighting generation drops from ~65ms to ~36ms and full chunk generation from
~103ms to ~68ms in the bench, with identical output.
* perf(lighting): propagate light through storage, not a shadow cache
The BFS light propagator kept a hashed shadow cache of in-flight light values
plus a per-chunk batched write buffer, layered on top of the light storage. The
storage is itself a fast array lookup, so the extra hashing and buffering cost
more than they saved. Read and write it directly and treat it as the single
source of truth.
Lighting generation drops from ~36ms to ~22ms and full chunk generation from
~68ms to ~52ms, output unchanged (all pumpkin-world tests, including the
fixed-seed ancient-city parity test, still pass).
---------
Co-authored-by: Alexander Medvedev <lilalexmed@proton.me>
* fix(lighting): stop sky light updates from looping on unloaded chunks
The sky light propagation could spin forever when a light update happened
next to a chunk that wasn't loaded. Writes to an unloaded chunk are dropped
silently and reads come back as 0, so the "this neighbor is darker than us"
check stayed true on every pass and the same position kept getting queued
again. In practice this hangs a tick thread when a block is broken near the
edge of the loaded area.
Skip neighbors whose chunk isn't loaded in both the increase and decrease
passes, matching how the border of loaded chunks already behaves: light
doesn't bleed into chunks that aren't there yet.
* Use Level::is_chunk_loaded for the unloaded-chunk guard
* fix: prevent capacity overflow crash when flying with elytra or in creative mode
The NoiseBasedCountPlacementModifier::get_count() can return negative i32
values when foliage noise sampling produces negative results at certain
world coordinates. The previous code cast this negative i32 directly to
usize, causing an integer wrap to ~18 quintillion, which triggered a
capacity overflow panic in Vec allocation during chunk feature generation.
This aligns with vanilla Minecraft behavior which also clamps the count
to a minimum of 0 before using it.
Closes#2345
* Hi
* sin/cos util
* 4 nieghbor check
* formating
* air helper
* carver wrapper
* fmt
* clippy
* clipp2
* remove local y
* out of bound guard
* fluid tick
* surface rule
* wire up
* math correct
* start fix
* bring back xoroshiro oops
* scheduler guard
* simplify test
* clean test
* comment
* crash fix
* clippy
* clippy
* i hate you codex
* sin cos
* im stupid f32
* refactor: less manual BlockMetadata impls
* refactor: added BlockId type
BlockId is a wrapper for u16: it is valid for any u16 that is the id of a Block.
- Changed the Block.id field type to BlockId
- added named BlockId constants
- BlockMetadata::ids() now returns Box<[BlockId]>
- adjusted pumpkin-macros to use BlockId constants
- fixed some methods that were comparing blockstate ids or item ids (u16) against block ids (previously u16)
TODO: check if unsafe blocks can be removed; The compiler might understand that BlockId is always a valid index into mappings::TYPE_FROM_RAW_ID
* refactor: added BlockStateId type
A BlockStateId is a safe wrapper around the numerical index of a BlockState in pumpkin-data. They help avoiding validity checks (outside of IO and, currently, plugins), and make it easier for other contributors to reason about what they're comparing; BlockStateIds, BlockIds or Item ids (still u16).
pumpkin-data::BlockStateId replaces RawBlockState and BlockStateId from pumpkin-world.
- added the BlockStateId wrapper type
- made (almost; plugins) every function interacting with block states or block state ids use the wrapper type
- changed codegen logic to create/work with BlockStateIds
- made ChunkPalette parsing check BlockStateId validity (pumpkin-world::chunk::format::ChunkSectionBlockStates)
TODO: check if unsafe blocks can be removed; The compiler might understand that BlockStateId is always a valid index into mappings::BLOCK_ID_FROM_STATE_ID and mappings::STATE_FROM_STATE_ID
* refactor: imports
changed every use pumpkin_world::BlockStateId to pumpkin_data::BlockStateId
* refactor: Block- & BlockStateId
finishing touches;
- rebased on latest mater
- ensure there are no bound checks on Block(State)Id conversions, making them extremely cheap
- this required unsafe std::hint::assert_unchecked annotations because the compiler is (occasionally) stupid
- on debug builds the bound checks still exist because of ub_checks (see rust unstable book for the feature of the same name)
- added Safety notes to hopefully prevent anyone from enabling the creation of invalid Block(State)Ids in the future
- fixed a benchmark
BlockPalette::liquid_block_count() returned the inverse of what it
should: the Homogeneous arm returned 0 when the section was liquid and
the full VOLUME (4096) when it was not, and the Heterogeneous arm summed
the counts of non-liquid blocks (filtering on !is_liquid).
Flip both arms to actually count liquid blocks, mirroring the correct
sibling non_air_block_count(): Homogeneous yields VOLUME when the single
block is liquid (0 otherwise) and Heterogeneous filters on is_liquid.
This value is sent to clients as the fluid count in chunk data
(MC 26.1+), so the inversion reported wrong fluid counts to players.
changed return value from `String` to `Box<str>` which is smaller and makes more sense since we don't mutate packet data
String — 24 bytes (ptr + len + capacity)
Box<str> — 16 bytes (ptr + len)
* Fix chunk save/load corruption and generation stage progression
- Fix serde rename mismatch: ChunkSectionNBT now uses PascalCase
to match serialization, restoring block_states and biomes on load.
- Correct generation stage order: Biomes before StructureStart,
add missing Carvers stage.
- Add stage guards in Cache::advance to avoid re-running completed
stages, preventing assertion panics.
- Set ProtoChunk::stage from saved ChunkStatus in from_chunk_data,
ensuring loaded chunks start at correct stage.
* using propper asserts instead of silent skips
* using StagedChunkEnum::Empty instead of None