Skip to content

fix: detect sculk sensor clocks - #328

Merged
TheMeinerLP merged 3 commits into
mainfrom
fix/sculk-detection
Oct 10, 2026
Merged

TheMeinerLP merged 3 commits into
mainfrom
fix/sculk-detection

Conversation

@TheMeinerLP

Copy link
Copy Markdown
Contributor

Root causes

  1. SkipEventTypeRedstoneClockMiddleware read check.sculkSensor, while config.yml and /arcm feature check sculk use check.sculk. Sculk events were therefore always skipped.
  2. SculkListener passed no state. StaticTrackingService and DynamicTrackingService counted non redstone/hopper events only through an active toggle armed by the state, so nothing was ever counted. Even with a state it would have counted every second event.

Paper event semantics (verified in Paper 26.2 bytecode, VibrationSystem$Listener.handleGameEvent)

  • Returns early if a vibration is already pending or !isValidVibration.
  • Then creates BlockReceiveGameEvent(gameEvent, CraftBlock.at(listener position), sourceEntity) and calls setCancelled(!user.canReceiveVibration(...)) before the occlusion check and before firing. So the event fires pre-cancelled while the sensor is active or cooling down (canReceiveVibration requires phase INACTIVE).
  • It fires for every vibration listener (sensors, calibrated sensors, shriekers, and entity listeners such as warden/allay), and also for vibrations caused by players.
  • getEntity() is GameEvent.Context.sourceEntity(). Block caused vibrations have none: PistonBaseBlock uses Context.of(BlockState) (null entity), and a redstone driven NoteBlock calls playNote(null, ...). A real sculk clock (sensor -> redstone -> piston/note block -> sensor) is therefore not filtered out.

Design

  • SculkListener: @EventHandler(priority = LOWEST, ignoreCancelled = true); only SCULK_SENSOR and CALIBRATED_SCULK_SENSOR (resolved by material name so apiVersion 1.19 stays supported); events with a source entity are ignored; state true.
  • Middleware reads check.sculk.
  • Both tracking services count every SCULK_SENSOR event like REDSTONE_AND_REPEATER and HOPPER.
  • Docs updated (check.sculk known issue removed, command description, one explanation paragraph).

Tests

SculkListenerTest and SkipEventTypeRedstoneClockMiddlewareTest were written first and failed against main (6 of 8 failing: middleware skipped enabled sculk, shrieker and player events forwarded, ignoreCancelled false, no state, clock never reported). Green after the change; ./gradlew build passes. ignoreCancelled is enforced by Bukkit's event bus, not by calling the method, so it is asserted via reflection on the annotation. A limit test confirms a clock is reported after maxCount + 2 events (first event only starts observation and the limit is checked before counting, same as redstone), i.e. every activation counts rather than every second one.

Threshold analysis (report only, defaults unchanged)

From the patched jar: SculkSensorBlock.ACTIVE_TICKS = 30, COOLDOWN_TICKS = 10; CalibratedSculkSensorBlock.getActiveTicks() = 10 (cooldown shared, 10). A sensor only accepts a vibration in phase INACTIVE.

  • Sculk sensor: minimum 30 + 10 = 40 ticks (2 s) per activation, at most 150 activations per clock.endDelay of 300 s (6000 ticks), 151 including one at t=0 with zero travel delay.
  • Calibrated sculk sensor: 10 + 10 = 20 ticks, at most 300 per 300 s.
  • Default clock.maxCount = 150 needs 152 events in the window, so the fastest plain sculk sensor clock (at most ~151 events, less with vibration travel time and redstone delay) does not reach it at defaults. Only calibrated sensors can. Server owners who want plain sensor clocks caught need a larger clock.endDelay or a smaller clock.maxCount.

https://claude.ai/code/session_01D82BKRq4pNe6vLvbaFgR1G

The middleware read check.sculkSensor while config.yml and the feature command use check.sculk, so sculk events were always skipped. The tracking services only counted every second non redstone event and the listener passed no state, so the counter never grew.

Paper fires BlockReceiveGameEvent for every vibration listener and pre-cancelled when the sensor cannot receive the vibration, including vibrations caused by players. The listener now ignores cancelled events, accepts only sculk sensors and calibrated sculk sensors, and ignores vibrations with a source entity. Every accepted activation counts as a trigger.

Claude-Session: https://claude.ai/code/session_01D82BKRq4pNe6vLvbaFgR1G
@TheMeinerLP
TheMeinerLP requested a review from a team as a code owner October 10, 2026 17:08
@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Unit Test Results

 11 files   11 suites   4s ⏱️
 84 tests  84 ✅ 0 💤 0 ❌
144 runs  144 ✅ 0 💤 0 ❌

Results for commit 438a707.

♻️ This comment has been updated with latest results.

@TheMeinerLP
TheMeinerLP merged commit 1b7cd09 into main Oct 10, 2026
15 checks passed
@TheMeinerLP
TheMeinerLP deleted the fix/sculk-detection branch October 10, 2026 17:26
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.

1 participant