Skip to content

Latest commit

 

History

History
149 lines (121 loc) · 14.1 KB

File metadata and controls

149 lines (121 loc) · 14.1 KB

ASK, DON'T GUESS — Ambiguity is a stop signal. If types, error handling, vanilla behavior, generated data, or architecture is unclear, ask. No speculative coding.

HONEST BLOCKERS — Don't hide uncertainty behind speculative changes or workarounds. State the real issue and ask the user for missing logs, paths, data, or vanilla references.

VERIFY BEFORE ASKING — Inspect relevant Steel code, generated/extracted data, vanilla source, and tests before asking. If still blocked, say what you checked.

CHECK-IN DISCIPLINE — Project rules override generic "keep going" behavior. Persistence stops when the next step depends on missing project facts, vanilla references, extractor output, or architectural intent. Do not keep retrying builds/tests, invent fallbacks, or hardcode data to make progress. Ask the user a narrow question with what you verified, what is missing, why guessing risks vanilla divergence, and the exact path/log/extractor output/decision needed.

  • Stop and ask after repeated failures in the same category instead of cycling through variants.
  • If data should come from SteelExtractor, update or request the extractor/output; do not transcribe hardcoded registry, worldgen, block, item, or protocol values from memory or guesses.
  • If temporary hardcoded data is explicitly requested for a prototype, label it as prototype-only and keep it out of shared foundations unless the user approves the architectural debt.

Template: "I verified [sources]. The blocker is [missing data/decision]. Guessing would risk [vanilla divergence]. Please provide/confirm [exact file/path/output/decision]."

FOUNDATIONAL INTEGRITY — Don't implement features on missing foundations. No stubs, mocks, fake vanilla data, or hardcoded values (todo!(), const user = {id:1}) unless explicitly prototyping. Partial implementations must not land in shared foundations that downstream code depends on unless the missing scope is documented and approved.

VANILLA FUNCTIONALITY - Keep 1:1 Vanilla functionality for gameplay, protocol, registry, worldgen, and other vanilla-observable mechanics. Verify behavior against local vanilla source or extracted data before implementing those areas. Non-gameplay server foundations (permissions, command UX, admin tooling, configuration, domains) may improve on vanilla when they do not change gameplay outcomes; don't replace Steel architecture with vanilla limitations without user approval. If a faster or more idiomatic Rust solution diverges from vanilla-observable behavior or structure, get explicit permission first and leave a concise comment/doc explaining why. Seeded vanilla RNG is required for worldgen, loot/component determinism, protocol-visible deterministic systems, and save/load-repeatable behavior; incidental live gameplay randomness may use Steel's unseeded runtime RNG when the vanilla probability/range and observable behavior stay compatible.

VANILLA TARGET VERSIONCargo.toml [workspace.package].version is authoritative; its +mc... suffix defines the targeted Minecraft data/source version. minecraft-src/ must be generated by update-minecraft-src.sh and kept current. If it is missing or stale and cannot be updated, stop and state the mismatch.

SKETCHY WORKAROUND PROTOCOL — Halt and ask permission before using:

  • .clone() to appease borrow checker, .unwrap()/.expect() in production, unsafe other than the keyed DowncastType exception below
  • Any/TypeId downcasting in plugin-extensible foundations. It is sound within one linked Rust build, but its identity is not deterministic across builds; use Steel's keyed downcasting foundation instead.
  • Disabling linters, ignoring errors, deprecated deps

Template: "This requires [Hack] which risks [Consequence]. Proceed or solve root cause?"

DOWNCASTING — Exact concrete downcasting uses steel_utils::{Downcast, DowncastType, DowncastTypeKey}.

  • A keyed unsafe impl DowncastType with a unique owner-scoped key and a concise // SAFETY: comment is approved by default and does not require separate unsafe permission. Other unsafe code still follows the workaround protocol above.
  • Keys identify concrete Rust implementations, not Minecraft registry entries or translation keys. Steel-owned types use the steel: namespace; plugins own their namespace.
  • Steel may downcast Steel-owned types and a plugin may downcast its own types. A key does not make a Rust type ABI-stable or permit one owner to reinterpret another owner's private type.
  • Use capability traits for shared behavior, vanilla interfaces, and class-hierarchy checks. Exact downcasting is only for callers that require a specific concrete implementation.

CONSTRUCTIVE DISSENT — Treat user claims as hypotheses. Verify against local code/vanilla, call out mismatches, and challenge XY problems. Before building a complex system, identify the required vanilla-compatible foundations and ask when architecture is unclear.

Registries

  • Generate only what is needed. If Minecraft uses a hardcoded transform, mirror that structure instead of inventing a broader registry system.
  • Design registry code with future modding and ABI compatibility in mind. The current change does not need to implement plugin support, but every value/registry NeoForge can change should have a plausible extension path.
  • Vanilla extracted registry/worldgen data should be compiled by build scripts into typed Rust data, not parsed from JSON at runtime. Prefer generated references like vanilla_blocks::STONE/vanilla_fluids::WATER and registry ref types over Identifier when the referenced vanilla value is known.
  • Do not design for runtime datapack JSON loading. Future plugins should register their own typed blocks/features with their own refs; build scripts only need to generate vanilla data.
  • Avoid raw BlockStateId in generated registry data. Use BlockRef plus explicit properties/default-state resolution so registry ordering can still evolve for plugin support.

Code standard

  • Use vanilla names unless they are misleading or a Rust design is explicitly approved. Document intentional differences on the relevant struct, method, or module.
  • Minimize duplication when it reduces maintenance risk, but a few local lines are usually fine.
  • Keep files focused and reasonably sized. Split a file once it contains distinct responsibilities or becomes difficult to navigate.
  • Plan module structure around expected growth. If an area will contain many implementations, start with a directory and separate modules instead of accumulating them in one large file.
  • Treat foundational systems with extra rigor; shortcuts in shared interfaces like block behavior become expensive once implementations depend on them.
  • No workarounds. Create the right helper/abstraction when the code needs one.
  • Don't add trivial wrapper methods that just alias an existing method. If height() already exists, don't add get_y_size() that returns self.height(). Use the existing method directly.
  • Prefer associated functions on the relevant type over standalone free functions when there's a clear owner.
  • Avoid deep indentation; prefer guard clauses, if let, and let Some(...) = ... else { return }.
  • Use Result for recoverable failures; panic only for impossible or fatal states.
  • Don't multithread something unless you can explain why it needs multithreading.
  • Don't use async unless you need disk or network I/O.
  • // TODO: comments are fine for non-foundational follow-up work. Don't use TODOs to hide missing foundations, skipped vanilla behavior, or incomplete shared interfaces that implementations depend on.
  • Keep comments concise.
  • After fixing something, don't leave a comment that only explains the old bug.
  • Currently this project is in early development, we don't need to provide migrations.

Particle routing

  • In Vanilla, Level.addParticle and playLocalSound calls from shared ticks are client-local, so Steel generally has no server-side work to perform for them. An explanatory comment can still be useful when an omission would otherwise look accidental.
  • Vanilla ServerLevel.sendParticles corresponds to World::send_particles; level and entity events continue to use their existing packets.

Testing

  • Test meaningful behavior, especially advanced systems, edge cases whose expected handling is not obvious, code using unsafe (always use // SAFETY: comments), and behavior that must match vanilla determinism (ItemComponent hashing or worldgen).
  • Don't add tests merely to increase coverage. Avoid tests that only restate constants, field values, type shapes, or other facts the compiler or a direct code inspection already makes obvious.
  • A test should catch a plausible regression or quickly communicate non-obvious behavior. If it would fail only when the implementation has been edited to contradict an equally obvious assertion, omit it.
  • Keep small, focused tests beside their implementation. Move a growing test suite into a dedicated test module file or integration test directory before it makes the implementation file difficult to navigate.
  • After code changes, run the narrowest check/test that exercises the touched code. If you cannot run it, state exactly why.
  • Suppress clippy lints with #[expect(clippy::lint_name, reason = "...")]. False positives and intentional deviations (e.g., function length for readability) are acceptable when explained.

GENERATED CODE — Never modify generated files directly:

  • Generated Rust under these src/generated/ paths is intentionally ignored/untracked. Commit the build script, extractor output, or source asset that produces it, not the generated Rust output.
  • steel-registry/src/generated/ → modify steel-registry/build/
  • steel-core/src/behavior/generated/ → modify steel-core/build/
  • steel-core/src/entity/generated/ → modify steel-core/build/
  • steel-worldgen/src/generated/ → modify steel-worldgen/build/
  • steel-utils/src/generated/ → modify steel-utils/build/
  • Block/item behavior registration is generated from #[block_behavior] / #[item_behavior]; add annotated structs under steel-core/src/behavior/, not manual generated registration.
  • Treat */build_assets/*.json and */test_assets/*.json populated by SteelExtractor as extracted data, not hand-authored source. If extracted JSON data is wrong or missing, update the relevant SteelExtractor extractor, rerun it, and copy only the produced file(s) needed for the change.
  • If extracted JSON data is missing and the extractor path/output is not available, tell the user exactly what data is required; they can provide the extractor path.
  • Generated Rust and extracted JSON may be minified onto one very long line. Line-limited reads like sed -n '1,160p' or head are not enough to understand these files; use rg for the target symbol/key first, then format a temporary copy if more context is needed.
  • To inspect generated Rust, write a disposable formatted copy outside the repo. Read/search the copy; never write formatted output back to src/generated/ or commit it.
  • To inspect extracted JSON, pretty-print a disposable copy outside the repo. If the formatter is unavailable, state that instead of guessing from a truncated one-line view.

SteelExtractor workflow

  • Use the local SteelExtractor checkout when extracted data is needed; default output is run/steel_extractor_output/ inside that checkout.
  • Copy only needed produced files into matching repo-relative paths like steel-registry/build_assets/, steel-core/build/, steel-core/test_assets/, steel-worldgen/build_assets/, or steel-utils/build_assets/.
  • If the checkout, output, or file is missing and cannot be regenerated, ask for the exact path/command/output instead of recreating data by hand.

Build Commands

cargo build          # Build
cargo run            # Run
cargo check          # Fast compile check
cargo test           # Tests
cargo clippy -r --all-targets --all-features  # CI lint
cargo check -p steel-core          # Fast game/worldgen check
cargo fmt --all --check            # CI formatting
typos                               # CI spelling
prek run --all-files                # Full local precommit suite

Uses nightly Rust. Tooling: ast-grep is available for structural code search/rewrites.

Architecture

Steel = Minecraft server in Rust.

Crates: steel (thin wrapper) -> steel-login (initial connection) → steel-core (game logic) → steel-worldgen (worldgen) → steel-protocol (packets) → steel-macros (derives) → steel-registry (generated data) → steel-utils/steel-math (common) → steel-crypto (encryption)

Key Paths

Steel:

Area Path
Entry / networking steel/src/
Game logic steel-core/src/
Player steel-core/src/player/ (networking.rs = packet handlers)
World steel-core/src/world/
Worldgen orchestration steel-core/src/worldgen/
Worldgen algorithms/data steel-worldgen/src/
Chunks steel-core/src/chunk/
Block/item behaviors steel-core/src/behavior/
Block entities steel-core/src/block_entity/
Entities steel-core/src/entity/
Inventory / menus steel-core/src/inventory/
Packets steel-protocol/src/packets/game/
Codegen build scripts steel-core/build/, steel-registry/build/, steel-worldgen/build/, steel-utils/build/

Vanilla (minecraft-src/minecraft/):

Area Path
Source root src/net/minecraft/
Packet handlers src/.../server/network/ServerGamePacketListenerImpl.java
Blocks src/.../world/level/block/
Entities src/.../world/entity/
Player src/.../server/level/ServerPlayer.java
Worldgen region src/.../server/level/WorldGenRegion.java
Worldgen features/structures src/.../world/level/levelgen/

The minecraft-src/ folder should be generated by update-minecraft-src.sh and kept current with the target version from Cargo.toml.

Concurrency Quick Ref

  • steel_utils::locks::SyncMutex<T> / steel_utils::locks::SyncRwLock<T> — aliased parking_lot for easier comprehension
  • Weak<T> — prevent reference cycles
  • If you have related values bundle them over a struct that is put behind a SyncMutex