Skip to content

feat: add redstone core engine and basic sources#27

Open
HashimTheArab wants to merge 4 commits into
HashimTheArab:masterfrom
cqdetdev:feat/redstone-core
Open

feat: add redstone core engine and basic sources#27
HashimTheArab wants to merge 4 commits into
HashimTheArab:masterfrom
cqdetdev:feat/redstone-core

Conversation

@HashimTheArab

@HashimTheArab HashimTheArab commented May 10, 2026

Copy link
Copy Markdown
Owner

Note

High Risk
High risk because it replaces core redstone power propagation/interfaces and integrates a new redstone engine into world ticking, block updates, and handler APIs, which can affect many blocks and update ordering/performance.

Overview
Introduces a new graph-based redstone engine (server/world/redstone.go) that compiles redstone-relevant regions, propagates power (including strong power conduction), and applies cancellable block-state/action updates via new world.Redstone* interfaces.

Refactors redstone blocks to use the new engine and adds new components: Button, PressurePlate, and RedstoneLamp; updates Lever, RedstoneTorch, RedstoneWire, RedstoneBlock, and Note to the new RedstonePowerUpdate-style APIs, with extensive new unit tests for power rules, scheduling behavior, and cancellation.

Wires the engine into world lifecycle: world tick now runs a redstone phase, SetBlock/SetLiquid invalidate redstone unless opted out (DisableRedstoneUpdates), scheduled tick queue semantics were tightened, chunk close cleans up redstone state, and Tx gains BlockLoaded/LiquidLoaded plus redstone query helpers. Handler API is extended with optional RedstoneHandler carrying rich RedstoneUpdate metadata while preserving legacy callbacks, and additional redstone-related sounds/events are mapped.

Reviewed by Cursor Bugbot for commit 72496ea. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features

    • Implemented redstone torch burnout mechanic to prevent infinite loops when rapidly toggling torches.
  • Improvements

    • Refactored redstone power propagation system for improved accuracy and consistency across devices.
    • Updated note block redstone behavior and responsiveness.
    • Enhanced redstone signal transmission and device interactions.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 10, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Complete reimplementation of the redstone system: a new graph-based evaluation engine replaces breadth-first wire-network propagation; power source/relayer/consumer interfaces define block behavior; Lever, RedstoneBlock, RedstoneWire, RedstoneTorch, and Note blocks updated to new model; world integration adds engine tick, transaction redstone methods, and loaded-only block/liquid queries.

Changes

Redstone System Engine Overhaul

Layer / File(s) Summary
Engine Core Types & Interfaces
server/world/redstone.go
New RedstoneUpdateCause enum, RedstoneUpdate event payload, and block capability interfaces (RedstonePowerSource, RedstoneStrongPowerSource, RedstonePowerRelayer, RedstonePowerRelayerNeighbourer, RedstonePowerConsumer, RedstonePowerTransitionConsumer, RedstonePowerSounder, RedstonePowerPostUpdater, RedstonePowerAction, RedstoneComparatorReadable).
Engine State & Initialization
server/world/redstone.go
redstoneEngine struct with tick context, dirty positions, cached power/output, evaluation guards, and strong-power suppression; newRedstoneEngine constructor; invalidation bookkeeping for dirty candidates, chunk cleanup, and cached state.
Engine Tick & Graph Compilation
server/world/redstone.go
Main tick control flow clones and sorts dirty candidates, compiles redstone graph from relevant blocks, computes propagated network power, applies sink updates, then source relayer updates; graph compilation entrypoints collect nodes/edges and compute identity hash.
Update Application & Mutation Dispatch
server/world/redstone.go
update method computes clamped old/new power, determines consumer/action impact, constructs RedstoneUpdate event, gates mutation via cancellation, optionally replaces consumer blocks, triggers post-update/sound effects, executes actions; updateGraphSources recomputes source outputs and tracks cancellations.
Power Evaluation & Graph Propagation
server/world/redstone.go
Direct and strong power queries including conductor-mediated propagation; graphPower initializes from conducted strong-power, seeds sources, propagates through weighted relayer edges with signal loss; fallback reachability search traverses relayer topology with depth/loss bounds.
Update Gating, Conductors & Relayer Edges
server/world/redstone.go
redstonePower cycle protection and strong-power suppression; redstoneUpdateAllowed dispatches to handlers and returns cancellation; conductor gating filters non-solid blocks; relayer edge construction builds weighted edges and normalizes neighbor discovery.
Graph Utilities & Helper Functions
server/world/redstone.go
redstoneStepFace maps position deltas to faces; redstoneGraphID computes deterministic hash; redstone relevance classification; deterministic ordering comparators; power clamping and conversion helpers.
Handler & Event Model Update
server/world/handler.go
New RedstoneHandler interface with HandleRedstoneUpdate(ctx, RedstoneUpdate); Handler.HandleRedstoneUpdate(ctx, pos) removed; old Conductor, WeakBlockPowerer, RedstonePowerRelayer, RedstoneUpdater interfaces removed from world/block.go.
Redstone Torch Core Refactor
server/block/redstone_torch.go
Torch converted to inverted-attachment-power model: BreakInfo clears burnout and schedules redstone update; UseOnBlock performs explicit face search for solid support, sets Facing/Lit, conditionally schedules delayed update when attached block is powered; NeighbourUpdateTick breaks on support loss or schedules delayed refresh; light emission tied to Lit state.
Torch Scheduled Tick & Power Emission
server/block/redstone_torch.go
ScheduledTick recomputes Lit from !attachmentPowered, updates block only when state changes, records/prunes burnout; RedstonePower/RedstoneStrongPower/RedstonePowerUpdate implement new interfaces; EncodeBlock selects lit/unlit names and encodes torch_facing_direction; state enumeration maps cube.FaceUp to unknownFace.
Lever Block Refactor
server/block/lever.go
Lever.RedstonePower returns 15 when Powered else 0; RedstoneStrongPower powers attached face only when Powered; BreakInfo schedules redstone update.
RedstoneBlock Refactor
server/block/redstone_block.go
BreakInfo schedules update for exact position; UseOnBlock returns placed(ctx) directly; RedstonePower returns 15; RedstoneStrongPower returns 0.
RedstoneWire Relayer Implementation
server/block/redstone_wire.go
RedstoneWire refactored to relayer-style power propagation: BreakInfo schedules update; UseOnBlock validates support and initializes Power from tx.RedstonePower; NeighbourUpdateTick validates support; RedstonePower/RedstoneSignalLoss/RedstoneRelayerNeighbours/RedstonePowerUpdate implement interfaces; connection logic via powered horizontal faces and neighbor enumeration.
Wire Support & Connection Logic
server/block/redstone_wire.go
redstoneWireSupported/redstoneWireSupportedLoaded check for solid block beneath; redstoneWireBlocksConnectionLoaded tests block face solidity; redstoneWirePowersHorizontalFace(s) determine powered faces with fallback for zero/one connections; redstoneWireHorizontalConnectionPositions compute neighbors accounting for direct/upward/downward steps; redstoneWireDirectConnectionLoaded classify eligibility; redstoneWireNonSolidComponent treat consumers/actions as targets; loaded helpers identify wires and redstone-relevant blocks.
Note Block & Common Helpers
server/block/note.go, server/block/redstone.go
Note.RedstonePowerUpdate derives powered state from power value, updates Powered when state changes, plays note only on powered transition; block/redstone.go imports adjusted for time support; redstonePower and redstoneTicks helpers; wireNetwork BFS algorithm removed.
Removed Redstone State Package
server/world/redstone/state.go
File deleted: removed State struct, burnout tracking, TorchBurnoutStatus/PruneTorchBurnout/RecordTorchToggle/BurnOutTorch/ClearTorchBurnout/WithActiveTorchUpdate/WithUpdateSource/UpdateSource/MarkTorchSelfTriggeredIfActive methods and internal helpers.
World Construction & Config
server/world/conf.go
Config.New() initializes World with redstone: newRedstoneEngine(s.CurrentTick).
World Structure & Block Tracking
server/world/world.go
World.redstone changed from redstone.State to \*redstoneEngine; World.neighbourUpdates added; blockLoaded/blockInChunkLoaded/liquidLoaded return (value, ok) only when chunk is loaded without repairs or broadcasts.
Block & Liquid Update Integration
server/world/world.go
SetOpts.DisableRedstoneUpdates suppresses redstone invalidation; setBlock captures prior block with NBT resolution, calls redstone.forget/invalidateAround for redstone-relevant changes; setLiquid calls invalidateAround in removal/placement paths; closeChunk calls redstone.removeChunk.
Transaction Loaded-Block Queries
server/world/tx.go
Tx.BlockLoaded and Tx.LiquidLoaded return (value, ok) only when chunk is loaded without loading/generating.
Transaction Redstone Scheduling & Power Queries
server/world/tx.go
Tx.ScheduleRedstoneUpdate enqueues redstone update; Tx.RedstonePower/RedstoneDirectPower/RedstoneStrongPower and \*From variants query via engine; RedstoneStoredPowerFrom includes stored relayer output; old low-level RedstonePower(pos, face, accountForDust) and Tx.Redstone() removed.
Transaction Torch Burnout Bookkeeping
server/world/tx.go
Tx.RedstoneTorchBurnoutStatus, PruneRedstoneTorchBurnout, RecordRedstoneTorchToggle, BurnOutRedstoneTorch, ClearRedstoneTorchBurnout delegate to world.redstone engine.
World Tick Integration
server/world/tick.go
Main world tick calls w.redstone.tick(tx, tick).
Scheduled Tick Queue Deduplication
server/world/tick.go
scheduledTickQueue.ticking guard added; furthestTicks index prevents earlier ticks behind later ones; tick executes only queue.active ticks, purges inactive/overdue; schedule deduplicates by furthestTicks with queue.currentTick boundary; fromChunk filters to active; removeChunk clears furthestTicks; add purges inactive appended; active helper determines status.
Torch Burnout State Tracking
server/world/redstone_torch.go
Burnout configuration constants; redstoneTorchBurnout struct stores expiration ticks and burned-out flag; status, pruning, toggle recording, burn-out/clear, lazy initialization, fetch+prune, and expiration filtering methods.
Block & Wire Tests
server/block/redstone_test.go, server/block/redstone_torch_test.go
Tests verify redstone power output, encoding, support requirements, connection discovery, strong power conduction, and burnout behavior.
Redstone Engine Core Tests
server/world/redstone_test.go
Tests for power clamping, deterministic ordering, neighbor ordering, compileRegion includes custom neighbors, graphID behavior, conductor rules.
Redstone Engine Behavior Tests
server/world/redstone_test.go
Tests for burnout state transitions, dirty-position invalidation, cancellation semantics, signal-loss integrity, consumer update recording.
Scheduled Tick Queue Tests
server/world/redstone_test.go
Tests verify latest tick per (pos, block-hash), omit superseded ticks, ignore earlier behind later, clear on chunk removal, allow rescheduling while processing, skip stale behind later; includes test infrastructure for counting/neighbor/relayer blocks and cancellation/signal-loss/solid block registries.
Redstone Benchmarks & World Tests
server/world/redstone_bench_test.go, server/world/world_test.go
Redstone graph ID benchmark with deterministic 256-node input; TestLiquidLoadedUsesWorldBlockRegistry/TestBlockLoadedDoesNotCreateMissingNBTBlockEntity verify custom block resolution and loaded-block reads.

🎯 4 (Complex) | ⏱️ ~75 minutes

🐰 Whiskers twitching with delight,
The redstone flows with graph-like might,
No more burnout loops that twist and tangle,
Just power nodes in perfect angle!
The world ticks forward, clean and bright,
A redstone dream reimagined right! 🔴⚡

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: add redstone core engine and basic sources' clearly and concisely describes the main change: introducing a new redstone engine and updating basic redstone source blocks.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread server/block/redstone.go Outdated
Comment thread server/block/redstone.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/world/tick.go (1)

332-339: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Superseded future ticks still accumulate in queue.ticks.

When the same (pos, block-hash) is pushed farther into the future, furthestTicks[index] is updated but the older future entry stays in queue.ticks until its original due tick. Repeated postponement will grow stale entries in memory, and fromChunk() will also serialize all of them on save. This is especially risky now that redstone can reschedule aggressively. Consider replacing/pruning the older queued entry when advancing furthestTicks, rather than only skipping it at execution time.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/world/tick.go` around lines 332 - 339, The schedule method updates
furthestTicks but leaves outdated entries in the ticks slice; modify
scheduledTickQueue.schedule to prune existing queued entries for the same
scheduledTickIndex before appending the new one: compute index as you already do
(scheduledTickIndex{pos,posHash}), then scan/filter queue.ticks in-place and
remove any scheduledTick whose pos and bhash equal index.hash (and whose t <
resTick or always remove to be safe) to avoid stale duplicates, then set
furthestTicks[index]=resTick and append the new scheduledTick; reference
functions/types: scheduledTickQueue.schedule, furthestTicks, ticks
([]scheduledTick), scheduledTickIndex, scheduledTick, and fromChunk() (so saved
state no longer contains obsolete entries).
🧹 Nitpick comments (4)
server/block/redstone.go (2)

103-103: ⚡ Quick win

Reuse the redstonePower helper for clamping.

Two sites duplicate the clamp logic that was just centralized in redstone_common.go. Using the helper keeps the redstone semantics defined in one place.

♻️ Proposed diff
 func (r RedstoneWire) RedstonePowerUpdate(_ cube.Pos, _ *world.Tx, power int) (world.Block, bool) {
-	power = max(0, min(power, 15))
+	power = redstonePower(power)
 	if r.Power == power {
 		return r, false
 	}
 func (r RedstoneWire) EncodeBlock() (string, map[string]any) {
-	return "minecraft:redstone_wire", map[string]any{"redstone_signal": int32(max(0, min(r.Power, 15)))}
+	return "minecraft:redstone_wire", map[string]any{"redstone_signal": int32(redstonePower(r.Power))}
 }

Also applies to: 140-140

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/block/redstone.go` at line 103, The file duplicates clamp logic (power
= max(0, min(power, 15))) instead of using the centralized helper; replace those
inline clamps with a call to the redstonePower helper (i.e., set power =
redstonePower(power)) so all redstone clamping uses the shared logic defined in
redstone_common.go; update both places in server/block/redstone.go where the
clamp appears to call redstonePower instead.

423-430: ⚖️ Poor tradeoff

Consider type-based identification for non-conductors.

redstoneLampExplicitNonConductor keys off EncodeBlock names like "minecraft:piston"/"minecraft:piston_arm"/"minecraft:observer". If/when those blocks are implemented (or their encoded names ever drift), the lamp's strong-power conduction will silently diverge from intent. A switch b.(type) against concrete types (or a tagging interface like redstoneNonConductor) is more resilient and consistent with how redstoneWireDirectConnectionLoaded discriminates relayers/sources by type.

That said, given several of these blocks (Piston, PistonArm, Observer) are not yet in the codebase, the string approach is understandable as a placeholder.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/block/redstone.go` around lines 423 - 430,
redstoneLampExplicitNonConductor currently matches block identity by EncodeBlock
string names which is brittle; change it to use a type-based check instead:
either add a marker interface (e.g. redstoneNonConductor) and make concrete
types (RedstoneBlock, Piston, StickyPiston, PistonArm, Observer) implement it,
then return true when b implements that interface, or perform a type switch on
b.(type) inside redstoneLampExplicitNonConductor to match those concrete types
directly; this mirrors how redstoneWireDirectConnectionLoaded discriminates and
prevents future name-drift bugs.
server/block/register.go (1)

517-541: 💤 Low value

Optional consolidation: merge the single-element loop with the wood-type loop.

The polished-blackstone group registers exactly the same Button+PressurePlate pair as the wood types — they could share one loop, with the weighted pressure plates left as standalone (since they have no Button equivalent). Purely cosmetic; safe to defer.

♻️ Suggested consolidation
-	for _, typ := range []int{
-		redstoneSourcePolishedBlackstone,
-	} {
-		world.RegisterItem(Button{Type: typ})
-		world.RegisterItem(PressurePlate{Type: typ})
-	}
 	world.RegisterItem(PressurePlate{Type: redstoneSourceLightWeighted})
 	world.RegisterItem(PressurePlate{Type: redstoneSourceHeavyWeighted})
 	for _, typ := range []int{
+		redstoneSourcePolishedBlackstone,
 		redstoneSourceOak,
 		redstoneSourceSpruce,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/block/register.go` around lines 517 - 541, Consolidate the duplicated
single-element loop by adding redstoneSourcePolishedBlackstone into the existing
wood-type slice that registers Button and PressurePlate so both cases use the
same loop that calls world.RegisterItem(Button{Type: typ}) and
world.RegisterItem(PressurePlate{Type: typ}); leave the two weighted pressure
plate registrations (PressurePlate{Type: redstoneSourceLightWeighted} and
PressurePlate{Type: redstoneSourceHeavyWeighted}) as standalone lines because
they have no corresponding Button. This touches the loops referencing
redstoneSourcePolishedBlackstone and the wood-type slice that iterates over
redstoneSourceOak, redstoneSourceSpruce, etc., and preserves existing
RegisterItem calls for Button and PressurePlate.
cmd/blockhash/main.go (1)

264-267: 💤 Low value

Minor: assert receiver was actually split out.

strings.Cut(s, ".") returns (s, "", false) when there is no . in s. Today s is always built as recvName + "." + fieldName.Name, so the contract holds — but if a future refactor passes a bare identifier, the generated code becomes leverAxisHash(<full-expression>) which may compile but hash incorrectly. A small assertion using the third return makes the contract explicit.

♻️ Suggested guard
 	case "Axis":
 		if _, ok := directives["lever_axis"]; ok {
-			receiver, _, _ := strings.Cut(s, ".")
+			receiver, _, found := strings.Cut(s, ".")
+			if !found {
+				log.Fatalf("lever_axis directive on field %q: expected qualified expression", s)
+			}
 			return "leverAxisHash(" + receiver + ")", 1
 		}
 		return "uint64(" + s + ")", 2
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/blockhash/main.go` around lines 264 - 267, Change the strings.Cut call to
capture the boolean and assert it succeeded: use receiver, _, ok :=
strings.Cut(s, ".") and if ok is false panic/log.Fatalf with a clear message
like "expected receiver.field, got %q" (using s) so the contract is explicit
before returning "leverAxisHash(" + receiver + ")". This targets the strings.Cut
usage in the lever_axis handling block.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/block/redstone_torch.go`:
- Around line 154-163: torchFacingDirection currently returns the attachment
face instead of the torch head direction; change torchFacingDirection(face
cube.Face) to return face.Opposite().String() for wall faces (keep FaceDown ->
"top"), update the decoder that parses "torch_facing_direction" back into block
Facing to apply .Opposite() when mapping head-direction strings to cube.Face so
decode/encode are symmetric, update unit tests TestRedstoneTorchRegisteredStates
and TestRedstoneTorchPowerAndEncode to expect the opposite mapping (e.g.,
"torch_facing_direction":"west" -> Facing: cube.FaceEast), and make the same
fixes in torch.go and copper_torch.go to ensure all torch types use the
head-direction encoding consistent with Bedrock.

In `@server/world/redstone.go`:
- Around line 250-285: compileRegion's BFS uses e.redstoneRelayerNeighbours
(which iterates only 6 faces and ignores the Block) so it misses custom relayer
connections (e.g., upward-step wires); change compileRegion to call
e.redstoneRelayerNeighbourPositions(tx, p, b) and enqueue each returned cube.Pos
(after checking blockLoaded/isRedstoneRelevant) so the BFS follows the same
neighbor rules used by compileEdges and powerFrom; after this, remove or make
redstoneRelayerNeighbours delegate to redstoneRelayerNeighbourPositions to avoid
drift, and likewise update invalidateAround to mark dirty positions using
redstoneRelayerNeighbourPositions so custom neighbor relations propagate
dirtiness.

In `@server/world/world.go`:
- Around line 161-170: blockLoaded must not call blockInChunk because that
method lazily creates NBT block entities and triggers ViewBlockUpdate; instead
read the block value directly from the chunk without side effects. Replace the
call to blockInChunk(c, pos) with a non-mutating lookup: compute the block's
local coordinates from pos, read the block ID/state from the chunk's raw storage
or a new chunk method that only returns data (e.g., chunk.BlockAt/BlockValue or
add World.blockInChunkNoSideEffects), and return that without creating entities
or broadcasting updates. Ensure the new lookup path is used only for loaded-only
reads and does not call any code that may allocate NBT or emit ViewBlockUpdate.

---

Outside diff comments:
In `@server/world/tick.go`:
- Around line 332-339: The schedule method updates furthestTicks but leaves
outdated entries in the ticks slice; modify scheduledTickQueue.schedule to prune
existing queued entries for the same scheduledTickIndex before appending the new
one: compute index as you already do (scheduledTickIndex{pos,posHash}), then
scan/filter queue.ticks in-place and remove any scheduledTick whose pos and
bhash equal index.hash (and whose t < resTick or always remove to be safe) to
avoid stale duplicates, then set furthestTicks[index]=resTick and append the new
scheduledTick; reference functions/types: scheduledTickQueue.schedule,
furthestTicks, ticks ([]scheduledTick), scheduledTickIndex, scheduledTick, and
fromChunk() (so saved state no longer contains obsolete entries).

---

Nitpick comments:
In `@cmd/blockhash/main.go`:
- Around line 264-267: Change the strings.Cut call to capture the boolean and
assert it succeeded: use receiver, _, ok := strings.Cut(s, ".") and if ok is
false panic/log.Fatalf with a clear message like "expected receiver.field, got
%q" (using s) so the contract is explicit before returning "leverAxisHash(" +
receiver + ")". This targets the strings.Cut usage in the lever_axis handling
block.

In `@server/block/redstone.go`:
- Line 103: The file duplicates clamp logic (power = max(0, min(power, 15)))
instead of using the centralized helper; replace those inline clamps with a call
to the redstonePower helper (i.e., set power = redstonePower(power)) so all
redstone clamping uses the shared logic defined in redstone_common.go; update
both places in server/block/redstone.go where the clamp appears to call
redstonePower instead.
- Around line 423-430: redstoneLampExplicitNonConductor currently matches block
identity by EncodeBlock string names which is brittle; change it to use a
type-based check instead: either add a marker interface (e.g.
redstoneNonConductor) and make concrete types (RedstoneBlock, Piston,
StickyPiston, PistonArm, Observer) implement it, then return true when b
implements that interface, or perform a type switch on b.(type) inside
redstoneLampExplicitNonConductor to match those concrete types directly; this
mirrors how redstoneWireDirectConnectionLoaded discriminates and prevents future
name-drift bugs.

In `@server/block/register.go`:
- Around line 517-541: Consolidate the duplicated single-element loop by adding
redstoneSourcePolishedBlackstone into the existing wood-type slice that
registers Button and PressurePlate so both cases use the same loop that calls
world.RegisterItem(Button{Type: typ}) and world.RegisterItem(PressurePlate{Type:
typ}); leave the two weighted pressure plate registrations (PressurePlate{Type:
redstoneSourceLightWeighted} and PressurePlate{Type:
redstoneSourceHeavyWeighted}) as standalone lines because they have no
corresponding Button. This touches the loops referencing
redstoneSourcePolishedBlackstone and the wood-type slice that iterates over
redstoneSourceOak, redstoneSourceSpruce, etc., and preserves existing
RegisterItem calls for Button and PressurePlate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 28553d36-e628-401e-a363-dc9405ec5a96

📥 Commits

Reviewing files that changed from the base of the PR and between 7869803 and 72496ea.

📒 Files selected for processing (27)
  • cmd/blockhash/main.go
  • server/block/hash.go
  • server/block/lever.go
  • server/block/note.go
  • server/block/redstone.go
  • server/block/redstone_block.go
  • server/block/redstone_common.go
  • server/block/redstone_sources.go
  • server/block/redstone_sources_test.go
  • server/block/redstone_test.go
  • server/block/redstone_torch.go
  • server/block/redstone_torch_test.go
  • server/block/redstone_wire.go
  • server/block/register.go
  • server/session/world.go
  • server/world/block.go
  • server/world/conf.go
  • server/world/handler.go
  • server/world/redstone.go
  • server/world/redstone/state.go
  • server/world/redstone_bench_test.go
  • server/world/redstone_test.go
  • server/world/sound/block.go
  • server/world/tick.go
  • server/world/tx.go
  • server/world/world.go
  • server/world/world_test.go
💤 Files with no reviewable changes (5)
  • server/block/lever.go
  • server/block/redstone_block.go
  • server/block/redstone_wire.go
  • server/world/block.go
  • server/world/redstone/state.go

Comment on lines +154 to 163
func torchFacingDirection(face cube.Face) string {
switch face {
case cube.FaceDown:
return "top"
case unknownFace:
return "unknown"
default:
return face.String()
}
return 0, false
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Show the torch test and any related decode-side mapping.
fd -t f 'redstone_torch_test.go' --exec cat {}
# Search for any decoding of torch_facing_direction in the repo (e.g. nbt/state parsers).
rg -nP -C3 'torch_facing_direction'
# Also pull the previous (pre-PR) torch encoding for comparison.
git log --oneline -n 5 -- server/block/redstone_torch.go

Repository: HashimTheArab/dragonfly

Length of output: 7222


🏁 Script executed:

# Find where torch_facing_direction is decoded back to Facing
rg -n "torch_facing_direction" --type go -B 5 -A 5 | head -100

# Look for RegisteredStates or block registration that maps properties to block structs
fd -t f 'redstone_torch.go' --exec grep -n "func.*torch\|RegisteredStates\|StateProperties" {} +

# Check full redstone_torch.go file structure
wc -l server/block/redstone_torch.go

Repository: HashimTheArab/dragonfly

Length of output: 5662


🏁 Script executed:

# Get full redstone_torch.go to understand the decoding side
cat server/block/redstone_torch.go

Repository: HashimTheArab/dragonfly

Length of output: 4723


🏁 Script executed:

# Search for where RedstoneTorch states are registered/decoded
rg -n "RegisteredStates\|parseFrom\|newRedstoneTorch" --type go

# Search for where torch_facing_direction is actually parsed back to Facing
rg -n "torch_facing_direction.*west\|torch_facing_direction.*north" --type go -B 3 -A 3

# Look for state parsing logic related to blocks
fd -t f 'state.go\|states.go' --exec grep -l "RedstoneTorch\|redstone_torch" {} \;

Repository: HashimTheArab/dragonfly

Length of output: 49


🏁 Script executed:

# Try broader search for state registration
rg -n "minecraft:redstone_torch\|minecraft:unlit_redstone_torch" --type go -B 3 -A 3

Repository: HashimTheArab/dragonfly

Length of output: 49


🏁 Script executed:

# Find where allRedstoneTorches is used and how blocks are registered
rg -n "allRedstoneTorches" --type go -B 5 -A 5

# Search for init functions or state registration in block package
fd -t f '\.go$' server/block/ --exec grep -l "func init" {} \;

# Look for any files that might handle block state registration
fd -t f '\.go$' server/block/ | head -20

Repository: HashimTheArab/dragonfly

Length of output: 2987


🏁 Script executed:

# Search for RegisteredStates or similar patterns in block package
find server/block -name "*.go" -exec grep -l "RegisteredStates\|register.*block\|func.*states" {} \;

Repository: HashimTheArab/dragonfly

Length of output: 128


🏁 Script executed:

# Read register.go to understand block registration
cat server/block/register.go | head -250

Repository: HashimTheArab/dragonfly

Length of output: 7979


🏁 Script executed:

# Also search for how state parsing works - look for property mapping
rg -n "func.*state\|properties\|StateProperties" server/block/ --type go | head -30

Repository: HashimTheArab/dragonfly

Length of output: 49


🏁 Script executed:

# Check how copper_torch and torch handle encoding and decoding
cat server/block/copper_torch.go | grep -A 20 "func.*copper.*Encode\|allCopperTorches"

# Check torch.go encoding
cat server/block/torch.go | grep -A 20 "func.*Encode\|allTorches"

# Look for cube.Face documentation or comments
rg -n "cube.Face\|Facing" server/block/redstone_torch.go -B 1 -A 1

# Check if there are any comments in the entire torch-related code
rg -n "torch\|Torch" server/block/torch.go | head -20

Repository: HashimTheArab/dragonfly

Length of output: 1257


🏁 Script executed:

# Search for test files that might show how torch_facing_direction should work
find . -name "*torch*test*" -type f -exec cat {} \;

Repository: HashimTheArab/dragonfly

Length of output: 3297


Fix wall torch direction encoding to match Bedrock semantics—torch head direction is opposite to attachment direction.

torchFacingDirection incorrectly encodes the attachment direction instead of the torch head direction. Bedrock's torch_facing_direction represents where the torch head points (e.g., "west" = torch head points west = attached to east block). Per the legacy Minecraft torch data values, a torch attached to FaceEast (Facing=East) should encode as "west" (torch head direction), but currently encodes as "east" (attachment direction).

The helper should use face.Opposite().String() for wall faces, not face.String(). The FaceDown -> "top" case is correct as-is.

However, this bug fix requires coordinated changes across at least three places:

  1. redstone_torch.go – Change torchFacingDirection() to use .Opposite() for wall faces
  2. Decoder logic – Wherever torch_facing_direction is parsed back to Facing (likely in the world package's state registry), apply .Opposite() as well
  3. Test assertionsTestRedstoneTorchRegisteredStates and TestRedstoneTorchPowerAndEncode in redstone_torch_test.go currently expect direct (wrong) mapping. After fixing encoding/decoding, update the test to expect opposite mapping (e.g., "torch_facing_direction": "west" should decode to Facing: FaceEast, not FaceWest)
  4. torch.go and copper_torch.go – Both use the same buggy face.String() pattern and likely have the same issue

Verify the fix against actual Bedrock block states and ensure all three torch types are corrected consistently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/block/redstone_torch.go` around lines 154 - 163, torchFacingDirection
currently returns the attachment face instead of the torch head direction;
change torchFacingDirection(face cube.Face) to return face.Opposite().String()
for wall faces (keep FaceDown -> "top"), update the decoder that parses
"torch_facing_direction" back into block Facing to apply .Opposite() when
mapping head-direction strings to cube.Face so decode/encode are symmetric,
update unit tests TestRedstoneTorchRegisteredStates and
TestRedstoneTorchPowerAndEncode to expect the opposite mapping (e.g.,
"torch_facing_direction":"west" -> Facing: cube.FaceEast), and make the same
fixes in torch.go and copper_torch.go to ensure all torch types use the
head-direction encoding consistent with Bedrock.

Comment thread server/world/redstone.go
Comment thread server/world/world.go Outdated
@HashimTheArab

Copy link
Copy Markdown
Owner Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 72496ea. Configure here.

Comment thread server/block/redstone.go Outdated
// EncodeBlock ...
func (r RedstoneWire) EncodeBlock() (string, map[string]any) {
return "minecraft:redstone_wire", map[string]any{"redstone_signal": int32(max(0, min(r.Power, 15)))}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RedstoneWire lost TrimMaterial and MaterialColour methods

Medium Severity

The new RedstoneWire implementation is missing the TrimMaterial() and MaterialColour() methods that existed on the old RedstoneWire in redstone_wire.go. These methods delegated to item.RedstoneWire{} and were used for smithing trim decoding. Their removal could break trim-related functionality for redstone wire.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 72496ea. Configure here.

Comment thread server/block/redstone_sources.go Outdated
"lever_direction": leverDirection(l.Facing, l.Axis),
"open_bit": boolByte(l.Powered),
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lever EncodeBlock uses uint8 instead of bool for open_bit

Medium Severity

The new Lever.EncodeBlock encodes open_bit as boolByte(l.Powered) (a uint8), whereas the old implementation used l.Powered directly (a bool). In hashProperties, bool and uint8 are both valid but produce the same byte values — however, they are different Go types and take different switch branches. If the registered block states from block_states.nbt store open_bit as bool, the state hash will not match, causing the lever block to fail to resolve its runtime ID.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 72496ea. Configure here.

torch.Lit = torch.inputStrength(pos, tx) == 0
tx.SetBlock(pos, torch, nil)
updateTorchRedstone(pos, tx)
return true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RedstoneTorch attachmentPowered may create feedback loop

Medium Severity

attachmentPowered first calls tx.RedstonePower(attached) to check power at the attached block, then additionally checks if the attached block itself is a RedstonePowerSource and queries all its faces. This double-check means a RedstoneBlock placed where the torch attaches would be counted twice — once through the engine's powerTo and once through the direct RedstonePowerSource check. More critically, this direct RedstonePowerSource query bypasses the engine's cycle-detection (evaluating guard), potentially creating infinite recursion when two torches indirectly power each other's attachment blocks.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 72496ea. Configure here.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
server/block/redstone_torch.go (1)

213-222: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

torchFacingDirection still encodes attachment direction instead of head direction.

This was previously flagged and not addressed. Confirming with Bedrock docs: a torch_facing_direction value of "west" denotes "a torch that is facing West and attached to a block on the East side" — i.e., the property describes the torch head direction, which is the opposite of the attachment direction.

In this codebase t.Facing is documented as "the direction from the torch to the block it is attached to" (the attachment direction), so for wall torches the encoded string should be face.Opposite().String(), not face.String(). The FaceDown → "top" branch happens to be correct because down's opposite is up = "top". As noted on the prior commit, the decoder side and TestRedstoneTorchRegisteredStates/TestRedstoneTorchPowerAndEncode need to flip in lockstep, and the same fix likely applies to torch.go/copper_torch.go.

🔧 Suggested encoding fix (decoder + tests must follow)
 func torchFacingDirection(face cube.Face) string {
 	switch face {
 	case cube.FaceDown:
 		return "top"
 	case unknownFace:
 		return "unknown"
 	default:
-		return face.String()
+		return face.Opposite().String()
 	}
 }

Run the following to locate the decode-side mapping and any other callers that may need a matching .Opposite() flip:

#!/bin/bash
rg -nP -C3 'torch_facing_direction'
rg -nP -C3 '\btorchFacingDirection\s*\('
# Also confirm parity for torch.go and copper_torch.go.
fd -t f -e go 'torch.go|copper_torch.go' --exec rg -nP -C3 'face\.String\(\)' {}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/block/redstone_torch.go` around lines 213 - 222, torchFacingDirection
currently returns the torch attachment direction (face.String()) but Bedrock
expects the torch head direction (the opposite); update torchFacingDirection to
return face.Opposite().String() for wall faces (keep the FaceDown -> "top"
behavior as is since opposite of Down is Up/"top"), and then update any
decode-side mapping and all callers that assume the old encoding (including
torch.go, copper_torch.go, and the decoder mapping for torch_facing_direction)
so encode/decode remain symmetric; also update tests
TestRedstoneTorchRegisteredStates and TestRedstoneTorchPowerAndEncode to expect
the flipped value.
🧹 Nitpick comments (3)
server/block/redstone_wire.go (1)

65-73: ⚖️ Poor tradeoff

Optional: Cache the powered-faces map per query batch.

RedstonePower invokes redstoneWirePowersHorizontalFace for each queried face, and that in turn rebuilds the full powered-horizontal-faces map (4 directions × direct/up-step/down-step probes — each doing several BlockLoaded lookups). If the engine queries multiple faces on the same wire during one evaluation pass, the same map is recomputed every call.

Not a correctness issue, and the current implementation prioritizes clarity, but worth keeping in mind if redstone evaluation shows up as hot in a profile — a per-evaluation cache keyed by (pos, engineEpoch) (or simply computing the map once at the relayer evaluation boundary and passing it through) would eliminate the redundancy.

Also applies to: 140-162

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/block/redstone_wire.go` around lines 65 - 73, RedstonePower repeatedly
calls redstoneWirePowersHorizontalFace and rebuilds the same
powered-horizontal-faces map for each face queried, causing redundant
BlockLoaded checks; to fix, compute and reuse that map per evaluation batch by
adding a short-lived cache keyed by the wire position (pos) and the current
evaluation epoch (or by computing the map once at the relayer/evaluation
boundary and passing it down), then have RedstonePower accept or fetch the
cached map and use it instead of calling redstoneWirePowersHorizontalFace each
time; update callers and helper functions (RedstonePower,
redstoneWirePowersHorizontalFace, and any relayer/evaluation entry point) to
pass or read the cache so the map is computed only once per evaluation.
server/block/redstone_test.go (2)

260-265: ⚡ Quick win

Avoid hardcoding burnout pre-threshold toggles in this integration test.

Using a fixed count (7) couples the test to internal threshold tuning and can create brittle failures unrelated to behavior.

More robust toggle loop
-		for i := 0; i < 7; i++ {
-			if tx.RecordRedstoneTorchToggle(pos) {
-				err = fmt.Errorf("torch burned out after %d toggles, want below threshold", i+1)
-				return
-			}
-		}
+		burnedOut := false
+		for i := 0; i < 64; i++ {
+			if tx.RecordRedstoneTorchToggle(pos) {
+				burnedOut = true
+				break
+			}
+		}
+		if !burnedOut {
+			err = fmt.Errorf("torch did not burn out within expected toggle budget")
+			return
+		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/block/redstone_test.go` around lines 260 - 265, Don't hardcode 7;
instead query the burnout threshold constant and iterate up to threshold-1 so
the test remains stable if tuning changes. Replace the fixed loop bound with the
package constant (e.g. redstone.TorchBurnoutThreshold or the equivalent symbol
used in the codebase) and loop for threshold-1 iterations calling
tx.RecordRedstoneTorchToggle(pos), ensuring you update imports if needed and
keep the existing error check that fails if a toggle returns true before the
threshold.

85-107: ⚡ Quick win

Add a negative-power clamp assertion in wire update coverage.

This test validates upper clamping but misses the lower bound (< 0 -> 0), so a regression there could slip through unnoticed.

Proposed test addition
 func TestRedstoneWirePowerUpdate(t *testing.T) {
 	wire := RedstoneWire{Power: 3}
 	after, changed := wire.RedstonePowerUpdate(cube.Pos{}, nil, 12)
@@
 	if got := after.(RedstoneWire).Power; got != 15 {
 		t.Fatalf("RedstoneWire clamped power = %d, want 15", got)
 	}
+
+	after, changed = wire.RedstonePowerUpdate(cube.Pos{}, nil, -2)
+	if !changed {
+		t.Fatal("RedstoneWire negative clamped update did not report a change")
+	}
+	if got := after.(RedstoneWire).Power; got != 0 {
+		t.Fatalf("RedstoneWire negative clamped power = %d, want 0", got)
+	}
 
 	_, changed = (RedstoneWire{Power: 15}).RedstonePowerUpdate(cube.Pos{}, nil, 15)
 	if changed {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/block/redstone_test.go` around lines 85 - 107, The test
TestRedstoneWirePowerUpdate currently checks upper clamping but not lower
clamping; add an assertion that feeding a negative power into
RedstoneWire.RedstonePowerUpdate clamps to 0. Specifically, call
RedstonePowerUpdate on a RedstoneWire (e.g., Power: 3 or 15) with a negative
desired power (e.g., -5), assert changed is true (unless unchanged expected),
and assert the returned after.(RedstoneWire).Power == 0 to ensure the lower
bound is enforced.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@server/block/redstone_torch.go`:
- Around line 213-222: torchFacingDirection currently returns the torch
attachment direction (face.String()) but Bedrock expects the torch head
direction (the opposite); update torchFacingDirection to return
face.Opposite().String() for wall faces (keep the FaceDown -> "top" behavior as
is since opposite of Down is Up/"top"), and then update any decode-side mapping
and all callers that assume the old encoding (including torch.go,
copper_torch.go, and the decoder mapping for torch_facing_direction) so
encode/decode remain symmetric; also update tests
TestRedstoneTorchRegisteredStates and TestRedstoneTorchPowerAndEncode to expect
the flipped value.

---

Nitpick comments:
In `@server/block/redstone_test.go`:
- Around line 260-265: Don't hardcode 7; instead query the burnout threshold
constant and iterate up to threshold-1 so the test remains stable if tuning
changes. Replace the fixed loop bound with the package constant (e.g.
redstone.TorchBurnoutThreshold or the equivalent symbol used in the codebase)
and loop for threshold-1 iterations calling tx.RecordRedstoneTorchToggle(pos),
ensuring you update imports if needed and keep the existing error check that
fails if a toggle returns true before the threshold.
- Around line 85-107: The test TestRedstoneWirePowerUpdate currently checks
upper clamping but not lower clamping; add an assertion that feeding a negative
power into RedstoneWire.RedstonePowerUpdate clamps to 0. Specifically, call
RedstonePowerUpdate on a RedstoneWire (e.g., Power: 3 or 15) with a negative
desired power (e.g., -5), assert changed is true (unless unchanged expected),
and assert the returned after.(RedstoneWire).Power == 0 to ensure the lower
bound is enforced.

In `@server/block/redstone_wire.go`:
- Around line 65-73: RedstonePower repeatedly calls
redstoneWirePowersHorizontalFace and rebuilds the same powered-horizontal-faces
map for each face queried, causing redundant BlockLoaded checks; to fix, compute
and reuse that map per evaluation batch by adding a short-lived cache keyed by
the wire position (pos) and the current evaluation epoch (or by computing the
map once at the relayer/evaluation boundary and passing it down), then have
RedstonePower accept or fetch the cached map and use it instead of calling
redstoneWirePowersHorizontalFace each time; update callers and helper functions
(RedstonePower, redstoneWirePowersHorizontalFace, and any relayer/evaluation
entry point) to pass or read the cache so the map is computed only once per
evaluation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 45b2ab54-c41f-48cf-9c33-ceb1b79bd5e7

📥 Commits

Reviewing files that changed from the base of the PR and between 72496ea and 2a409ac.

📒 Files selected for processing (13)
  • server/block/lever.go
  • server/block/redstone.go
  • server/block/redstone_block.go
  • server/block/redstone_test.go
  • server/block/redstone_torch.go
  • server/block/redstone_wire.go
  • server/world/redstone.go
  • server/world/redstone_test.go
  • server/world/redstone_torch.go
  • server/world/tick.go
  • server/world/tx.go
  • server/world/world.go
  • server/world/world_test.go
✅ Files skipped from review due to trivial changes (1)
  • server/world/world_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/world/redstone.go
  • server/world/world.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants