You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This preserved product issue is now part of the fresh MIT replacement program. Final implementation owner: atrinik/server. Legacy C/SDL2, packet, global-state, and file-path details below are historical evidence only.
Replacement implementation contract
Preserve radial falloff behavior and all gameplay visibility decisions. Implement deterministic Go light-field calculation over structural surfaces; protocol sends bounded semantic light data and renderer owns the visual fixture/WGSL presentation.
New implementation and tests are independent MIT work unless an exact contribution by an approved MIT provenance grantor is admitted through the recorded file-level MIT grant. Do not copy or mechanically translate other GPL source/tests. Preserve every player-facing, accessibility, disclosure, and performance design decision below.
Required verification
Add deterministic Rust and/or Go tests at the owning boundary plus cross-language protocol fixtures for new fields.
Exercise malformed/stale/reordered inputs and lifecycle failure without partial state.
Use the shared renderer rather than a client/editor fork.
Validate through a wrapper-managed replacement scenario where the feature is interactive.
Preserved product/design specification and historical implementation notes
Outcome
Replace the visibly cross-shaped footprint of torches and other small light sources with a smooth, radial server-authoritative falloff while preserving linked-level propagation, obstruction by opaque cells/floors, overlapping sources, darkness sources, and the client's smooth/discrete lighting modes.
This is a focused visual follow-up to atrinik/atrinik#169. It should improve the existing lighting model rather than introduce a second client-only light system.
Why the current footprint forms a cross
The client is not missing the basic lighting field:
server/src/socket/request.c:draw_client_map2() sends the composite normalized light level for every visible cell and sub-layer through MAP2_MASK_LIGHT_LEVEL / MAP2_MASK_LIGHT_LEVEL_MORE.
client/src/gui/widgets/map.c:map_draw_lighting() projects those samples into the isometric view.
client/src/client/lighting.c bilinearly interpolates the field for ground pixels and samples it for walls/roofs.
The shape originates in server/src/server/light.c before the packet is built:
glow_radius is collapsed through light_mask[] into one of ten legacy intensity masks.
light_mask_width[] caps every source at a support radius of four cells.
light_masks[][] contains a few hard-coded illumination bands.
light_mask_value() rounds Euclidean distance upward to an integer ring, so all positions in that ring receive the same abrupt value.
A handheld torch uses strength 3 (last_sp 3 in arch/items/light/torch/torch.arc). That selects a support radius of only two cells: the center is 160 raw light, cardinal neighbors are 80, diagonals and cardinal distance-two cells are 40, and everything else is zero. On a square sample grid, this leaves the bright core and outer reach concentrated on the cardinal axes. Client interpolation smooths the transitions but cannot reconstruct a radial field that the server did not sample.
Wall sconces use strength 5 and have the same quantized behavior over only three cells.
Proposed design
1. Replace hand-authored masks with an algorithmic radial kernel
Keep MapSpace.light_source_value as the authoritative accumulated illumination, but replace lmask_x, lmask_y, light_mask_width, light_mask_size, light_masks, and ring-index lookup with a bounded source profile and a deterministic falloff function.
For each effective source strength, define independently:
center intensity;
physical support radius; and
falloff curve/profile.
Iterate the bounded (dx, dy, dz) volume, reject points outside the profile radius, and calculate contribution from Euclidean distance squared. Use fixed-point arithmetic or a distance-squared lookup generated from the profile so results are portable and do not require floating-point work in map updates. The curve must be monotonic and reach zero continuously at the boundary rather than stepping through the current powers-of-two rings.
Separating range from center intensity is important: merely changing the values in the existing two-tile torch mask cannot remove the cardinal-axis silhouette. Small sources need enough surrounding samples for the client interpolation to describe a round footprint, without making their center excessively bright.
The existing glow_radius name and values currently conflate range and intensity. Under the repository's greenfield policy, either redefine it as a real radius plus a separate intensity/profile field, updating current content and Python exposure together, or retain it as a strength/profile selector with clearly documented generated radius/intensity values. Do not leave the current misleading implicit mask mapping undocumented.
2. Preserve authoritative 3D visibility
Continue routing each candidate sample through the linked-map resolution and light_path_is_clear() logic added by atrinik/atrinik#169:
propagate across horizontal map boundaries and TILED_UP / TILED_DOWN levels;
stop at opaque intermediate cells;
respect floors as vertical boundaries;
allow an exposed opaque target face to receive light; and
apply negative/darkness sources with the same radial magnitude and obstruction rules.
The distance metric, including the physical cost of one linked vertical level relative to one horizontal tile, should be explicit and covered by tests rather than inherited accidentally from loop indexes.
3. Keep the current MAP contract for the first implementation
No protocol change is required to obtain a materially rounder result. The server already sends the final composite per-cell/per-surface light samples, which preserves hidden source positions, overlaps, occlusion, ambient light, and special vision in one authoritative value. The client should continue to interpolate those samples and must not try to rediscover light sources from visible sprites.
If visual fixtures show that a well-sampled continuous server kernel is still visibly faceted at normal zoom, evaluate a separate follow-up that sends a denser authoritative field (for example, bounded sub-cell/corner samples). Do not send only source position/radius and reconstruct lighting client-side: that would also require enough obstruction and surface data to reproduce the server result and would create two lighting models.
4. Bound update cost and make tuning observable
Profile both incremental adjust_light_source() updates and recalculate_light_sources() on representative dense-light maps before increasing support radii. Precompute one compact kernel per supported profile/vertical metric and reuse it for add/remove operations; keep the current exact additive removal behavior.
Add development diagnostics or a focused test helper that can dump a source's raw and normalized sample grid. This makes symmetry, monotonicity, cutoff, and content tuning reviewable without relying only on subjective screenshots.
Scope
Replace the legacy mask tables/ring lookup in server/src/server/light.c.
Define and document the source strength/radius/intensity contract.
Update affected torch, sconce, lamp, lava, spell-effect, and darkness-source content if the authored contract changes.
Retain light_level_from_raw() unless screenshot tuning demonstrates that the independent perceptual normalization also needs adjustment.
Extend the server lighting unit suite for radial geometry, overlap, removal, negative sources, blockers, floors, linked maps, and boundary crossing.
Add visual before/after evidence from the existing smooth-lighting client path.
Acceptance criteria
An isolated handheld torch and wall sconce produce a centered, monotonic radial pool with no visually dominant cardinal-axis cross in smooth-lighting mode.
Equal-distance unobstructed samples have equal contributions in every horizontal direction; the chosen vertical metric is equally explicit and symmetric.
Contribution never increases with distance and reaches zero at the declared support boundary without a bright terminal ring.
Multiple sources combine deterministically; adding and removing a source restores the exact previous field without drift or underflow.
Negative light sources use the same spatial profile with inverted contribution.
Existing MAP framing and client parsing remain unchanged unless denser-sampling evidence is attached and the protocol change is scoped explicitly.
Dense emissive scenes do not show a material server tick/update regression; before/after source-update and full-recalculation measurements are recorded.
Discrete lighting remains coherent, while smooth lighting shows no new seams on long walls, roofs, map edges, or streamed cells.
Validation plan
Server unit tests: center/cardinal/diagonal values at equal and ordered distances, cutoff boundary, overlap/add-remove identity, negative sources, opaque blockers, floors in both directions, horizontal map edges, and multi-level propagation.
Native validation: ./build.sh atrinik-server, ./build.sh atrinik, focused server.light tests, then ./build.sh check when the runtime is prepared.
Visual matrix: handheld torch, wall sconce, overlapping torches, moving/toggled source, map edge, long wall, one-storey roof, three-storey stack (+1 and +2), and an outdoor facade supplied by an upper map. Capture smooth lighting on/off at the same coordinates.
Performance matrix: incremental add/remove/move and full recalculation on an ordinary dark room plus a dense lava/emissive map; report source count, affected cells, linked levels, and elapsed time.
Out of scope
Colored light, HDR/bloom, animated flicker, or a GPU renderer migration.
Client-authoritative light propagation or shadowing.
Important
This preserved product issue is now part of the fresh MIT replacement program. Final implementation owner:
atrinik/server. Legacy C/SDL2, packet, global-state, and file-path details below are historical evidence only.Replacement implementation contract
Preserve radial falloff behavior and all gameplay visibility decisions. Implement deterministic Go light-field calculation over structural surfaces; protocol sends bounded semantic light data and
rendererowns the visual fixture/WGSL presentation.New implementation and tests are independent MIT work unless an exact contribution by an approved MIT provenance grantor is admitted through the recorded file-level MIT grant. Do not copy or mechanically translate other GPL source/tests. Preserve every player-facing, accessibility, disclosure, and performance design decision below.
Required verification
Preserved product/design specification and historical implementation notes
Outcome
Replace the visibly cross-shaped footprint of torches and other small light sources with a smooth, radial server-authoritative falloff while preserving linked-level propagation, obstruction by opaque cells/floors, overlapping sources, darkness sources, and the client's smooth/discrete lighting modes.
This is a focused visual follow-up to atrinik/atrinik#169. It should improve the existing lighting model rather than introduce a second client-only light system.
Why the current footprint forms a cross
The client is not missing the basic lighting field:
server/src/socket/request.c:draw_client_map2()sends the composite normalized light level for every visible cell and sub-layer throughMAP2_MASK_LIGHT_LEVEL/MAP2_MASK_LIGHT_LEVEL_MORE.client/src/gui/widgets/map.c:map_draw_lighting()projects those samples into the isometric view.client/src/client/lighting.cbilinearly interpolates the field for ground pixels and samples it for walls/roofs.The shape originates in
server/src/server/light.cbefore the packet is built:glow_radiusis collapsed throughlight_mask[]into one of ten legacy intensity masks.light_mask_width[]caps every source at a support radius of four cells.light_masks[][]contains a few hard-coded illumination bands.light_mask_value()rounds Euclidean distance upward to an integer ring, so all positions in that ring receive the same abrupt value.A handheld torch uses strength 3 (
last_sp 3inarch/items/light/torch/torch.arc). That selects a support radius of only two cells: the center is 160 raw light, cardinal neighbors are 80, diagonals and cardinal distance-two cells are 40, and everything else is zero. On a square sample grid, this leaves the bright core and outer reach concentrated on the cardinal axes. Client interpolation smooths the transitions but cannot reconstruct a radial field that the server did not sample.Wall sconces use strength 5 and have the same quantized behavior over only three cells.
Proposed design
1. Replace hand-authored masks with an algorithmic radial kernel
Keep
MapSpace.light_source_valueas the authoritative accumulated illumination, but replacelmask_x,lmask_y,light_mask_width,light_mask_size,light_masks, and ring-index lookup with a bounded source profile and a deterministic falloff function.For each effective source strength, define independently:
Iterate the bounded
(dx, dy, dz)volume, reject points outside the profile radius, and calculate contribution from Euclidean distance squared. Use fixed-point arithmetic or a distance-squared lookup generated from the profile so results are portable and do not require floating-point work in map updates. The curve must be monotonic and reach zero continuously at the boundary rather than stepping through the current powers-of-two rings.Separating range from center intensity is important: merely changing the values in the existing two-tile torch mask cannot remove the cardinal-axis silhouette. Small sources need enough surrounding samples for the client interpolation to describe a round footprint, without making their center excessively bright.
The existing
glow_radiusname and values currently conflate range and intensity. Under the repository's greenfield policy, either redefine it as a real radius plus a separate intensity/profile field, updating current content and Python exposure together, or retain it as a strength/profile selector with clearly documented generated radius/intensity values. Do not leave the current misleading implicit mask mapping undocumented.2. Preserve authoritative 3D visibility
Continue routing each candidate sample through the linked-map resolution and
light_path_is_clear()logic added by atrinik/atrinik#169:TILED_UP/TILED_DOWNlevels;The distance metric, including the physical cost of one linked vertical level relative to one horizontal tile, should be explicit and covered by tests rather than inherited accidentally from loop indexes.
3. Keep the current MAP contract for the first implementation
No protocol change is required to obtain a materially rounder result. The server already sends the final composite per-cell/per-surface light samples, which preserves hidden source positions, overlaps, occlusion, ambient light, and special vision in one authoritative value. The client should continue to interpolate those samples and must not try to rediscover light sources from visible sprites.
If visual fixtures show that a well-sampled continuous server kernel is still visibly faceted at normal zoom, evaluate a separate follow-up that sends a denser authoritative field (for example, bounded sub-cell/corner samples). Do not send only source position/radius and reconstruct lighting client-side: that would also require enough obstruction and surface data to reproduce the server result and would create two lighting models.
4. Bound update cost and make tuning observable
Profile both incremental
adjust_light_source()updates andrecalculate_light_sources()on representative dense-light maps before increasing support radii. Precompute one compact kernel per supported profile/vertical metric and reuse it for add/remove operations; keep the current exact additive removal behavior.Add development diagnostics or a focused test helper that can dump a source's raw and normalized sample grid. This makes symmetry, monotonicity, cutoff, and content tuning reviewable without relying only on subjective screenshots.
Scope
server/src/server/light.c.light_level_from_raw()unless screenshot tuning demonstrates that the independent perceptual normalization also needs adjustment.Acceptance criteria
+1/+2retain the obstruction and propagation behavior established by feat: add multi-level smooth lighting atrinik#169.Validation plan
./build.sh atrinik-server,./build.sh atrinik, focusedserver.lighttests, then./build.sh checkwhen the runtime is prepared.+1and+2), and an outdoor facade supplied by an upper map. Capture smooth lighting on/off at the same coordinates.Out of scope