Skip to content

Add light propagation between sub-levels, world#415

Closed
izekblz wants to merge 9 commits into
ryanhcode:mainfrom
izekblz:main
Closed

Add light propagation between sub-levels, world#415
izekblz wants to merge 9 commits into
ryanhcode:mainfrom
izekblz:main

Conversation

@izekblz
Copy link
Copy Markdown

@izekblz izekblz commented Apr 22, 2026

Overview

Implements bidirectional light propagation across all boundaries: world ↔ sub-level, sub-level ↔ world and sub-level ↔ sub-level. Also fixes compatibility with dynamic lighting mods and adds backward compatibility shims for dependent mods (I believe this was already fixed? Although still with Override usage. This is singled out in a separate commit anyways)

Those features are by no means ready-to-ship and are better viewed as an experimental build. However, after testing i did not find any major visual bugs present.

Light Propagation

World → Sub-level (server-side):

ServerSubLevelLightInjector scans world blocks near each sub-level's visual bounding box for light emitters

Transforms world positions into plot-local coordinates and injects emission values directly into the plot's BlockLightEngine via setStoredLevel + enqueueIncrease

Rescans on world light changes, sub-level creation/split, and every 8 blocks of movement (so the brightest light still isn't missed, with brightest being 16 i believe and 8 being half of that)

Per-source NPE handling for plot sections without allocated DataLayers

Sub-level → World (client-side):

VirtualLightManager collects emitting blocks from sub-level plots, transforms to world space, and spreads virtual light with manhattan distance falloff

Injected into vanilla via BlockLightSectionStorage mixin and Sodium via LevelSlice mixin

Server-side implementation would have to work with ThreadedLevelLightEngine and i don't have a clue how to do that

Sub-level → Sub-level (server-side):

fullRescan iterates nearby sub-levels' plot chunks, reads block states for emitters, transforms positions to world space via Pose3d.transformPosition, and injects into the receiving sub-level's plot light engine

LevelChunkMixin detects when a block's light emission changes on a sub-level plot (comparing old vs new state) and marks nearby sub-levels for rescan

Moving sub-levels mark nearby sub-levels dirty so they pick up emitters at updated positions

Old injected positions cleared unconditionally during reinject (plot-local coords can't be validated against world state)

Performance

Light updates sent via ClientboundLightUpdatePacket, no full chunk resends and avoiding client-side state resets

Dynamic Light Compatibility

EntityRendererMixin: Changed @overwrite to @Inject + @reDIrect for getPackedLightCoords to preserve other mods' injections into getBlockLightLevel

EntitySubLevelUtil: Added deprecated shims for getEyePositionInterpolated and getTrackingSubLevel to prevent NoSuchMethodError with Simulated/Create Aeronautics

Adding dynamic light compatibility to sub-levels is way beyond my possibilities, so no dynlight for that

Performance Impact

spark_profiles.zip

Tested with Spark profiler comparing original mod vs this PR:

  • Server thread usage increase by Sable: ~4%p (7.01% -> 11.24%)
  • MSPT increase: ~1.5% (8.09 -> 8.22)
  • TPS: unchanged (~19.5)

The overhead comes from scanning nearby sub-levels' plot chunks for emitters during rescan. This is bounded by the number of nearby sub-levels and their chunk count.

Visual Comparison

Before:
2026-04-22_20 47 21

After:
2026-04-22_20 45 50

Before:
2026-04-22_22 55 13

After:
2026-04-22_22 54 13

Video Preview:
https://youtu.be/swGskrNbTl8

Release

Built binaries are available in the forked repo

Note

Commit history is slightly messed, first 3 commits are duplicated

izekblz added 9 commits April 22, 2026 18:01
…o preserve injections from dynamic light mods (e.g. SodiumDynamicLights). Add backward compatibility shims for EntitySubLevelUtil.getEyePositionInterpolated and getTrackingSubLevel to prevent NoSuchMethodError with Simulated/Create Aeronautics.
…s and other sub-levels. Uses a virtual light spread system with manhattan distance falloff on the client side, injected into both vanilla and Sodium light pipelines.
…minate nearby sub-level blocks. Scans for light emitters near each sub-level's visual position on the server, transforms their positions into plot-local coordinates, and injects them into the plot's light engine for proper propagation. Rescans on light changes, sub-level creation/split, and every 8 blocks of movement.
…o preserve injections from dynamic light mods (e.g. SodiumDynamicLights). Add backward compatibility shims for EntitySubLevelUtil.getEyePositionInterpolated and getTrackingSubLevel to prevent NoSuchMethodError with Simulated/Create Aeronautics.
…s and other sub-levels. Uses a virtual light spread system with manhattan distance falloff on the client side, injected into both vanilla and Sodium light pipelines.
…minate nearby sub-level blocks. Scans for light emitters near each sub-level's visual position on the server, transforms their positions into plot-local coordinates, and injects them into the plot's light engine for proper propagation. Rescans on light changes, sub-level creation/split, and every 8 blocks of movement.
Light-emitting blocks on one sub-level now illuminate nearby other sub-levels.
Emitters are discovered by scanning other sub-levels' plot chunks, transforming
positions to world space, and injecting them into the receiving sub-level's plot
light engine.

Detection: LevelChunkMixin tracks when a block's light emission changes on a
sub-level plot and notifies nearby sub-levels to rescan. Only triggers when
emission actually changes (oldState vs newState), avoiding unnecessary work from
non-light block updates.

Movement: When any sub-level moves, nearby sub-levels are marked for rescan so
they pick up emitters at updated world-space positions.

Cleanup fix: Old injected positions are cleared unconditionally during reinject,
since they are plot-local coordinates that cannot be validated against world state.

Performance: Light updates now use ClientboundLightUpdatePacket instead of full
chunk resends (ClientboundLevelChunkWithLightPacket), avoiding client-side chunk
state resets that caused visual glitches with other mods (e.g. Create ropes) and
reducing network overhead.
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 22, 2026

CLA assistant check
All committers have signed the CLA.

@IThundxr
Copy link
Copy Markdown
Collaborator

Has this been written with any help from LLM's or gAI tools?

@izekblz
Copy link
Copy Markdown
Author

izekblz commented Apr 22, 2026

Has this been written with any help from LLM's or gAI tools?

Initial architecture, starting implementation and bug-fixing\tuning\testing done by me, grunt work of rewriting injects/samplers/etc to spec and code commenting was done using gAI

@IThundxr
Copy link
Copy Markdown
Collaborator

After discussion with the others, this is not the correct solution for lighting between the normal world and sublevels, also for future reference the CLA mentions the following:

The submitted code of your PR must be your own, original work, that you have the right to contribute.

Any code written by a LLM or gAI model is not considered your own, original work.

@IThundxr IThundxr closed this Apr 22, 2026
@izekblz
Copy link
Copy Markdown
Author

izekblz commented Apr 23, 2026

@IThundxr Sorry for necroposting, but is there a vision for the correct solution among the development team, or is it just a case of "shouldn't be like this"? I mainly did this because lack of lighting is a dealbreaker for me, and i obviously can continue playing with my implementation, but i'd be open to try my hand at another approach if you guys have one, or a concept of it :)

Currently I didn't find any hint at this functionality in the codebase, and issue mentioning this is unanswered, so I couldn't figure the intended way out.

Thank you for the feedback!

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.

3 participants