Light Source Handling
Sources considered
Section titled “Sources considered”For each calculation, the module collects:
- Every placed ambient light on the active scene (
canvas.lighting.objects.children). - Every token on the active scene (
canvas.tokens.placeables). Tokens emit light when their token light configuration has bright or dim radius.
Sources are sorted by luminosity descending before evaluation, so the brightest sources are tested first.
Only sources whose active flag is set contribute. Disabled or hidden light sources are ignored.
Per-source evaluation
Section titled “Per-source evaluation”For each candidate source, the module applies these gates in order. The first failure short-circuits the source:
1. Max-radius gate
Section titled “1. Max-radius gate”If the 3D distance from the token to the source exceeds the larger of dim and bright, the source is skipped. Distance is computed by calculate3DDistance using the token’s resolved center plus elevation.
2. Cone-angle gate
Section titled “2. Cone-angle gate”If the source’s angle is less than 360, the module computes the angle from the source to the token (calculateLightAngle) and compares to the source’s rotation. If the absolute angular difference exceeds angle / 2, the token is outside the cone and the source is skipped.
Sources with angle >= 360 (omnidirectional) skip this gate.
3. Wall collision gate
Section titled “3. Wall collision gate”The module calls CONFIG.Canvas.polygonBackends.sight.testCollision(tokenCenter, lightSourceCenter, { type: 'sight', mode: 'all' }). If the result is non-empty, line of sight is blocked and the source is skipped.
This uses Foundry’s standard sight polygon backend, so any wall whose sight flag blocks vision also blocks this light contribution.
4. Radius classification
Section titled “4. Radius classification”If all gates pass, the source’s contribution is classified:
- Token distance within
brightandbright > 0: upgrade to Bright. - Token distance within
dimanddim > 0: upgrade to at-least Dim.
Higher levels never get downgraded by weaker sources, with one exception in the Negative Lights section.
Global Illumination
Section titled “Global Illumination”When Global Illumination is on (off by default), the module first checks scene-wide lighting before iterating sources:
- Reads
canvas.scene.environment.globalLight.enabledand the scene’sdarknessLevel. - Reads
canvas.scene.environment.globalLight.darkness.max(defaults to 1 when absent). - If global light is enabled and the current darkness is at or below the threshold, the token starts at Bright.
- Before granting Bright, the module checks if the token sits under a light-restricting tile. If so, global illumination is blocked for that token and the start level returns to Dark.
When global illumination grants Bright, individual light sources are only evaluated if Negative Lights Support is on (because a negative light could downgrade the condition). Otherwise the calculation short-circuits at Bright.
Light-restricting tiles
Section titled “Light-restricting tiles”A tile with tile.document.restrictions.light === true blocks global illumination for any eligible token whose bounds intersect the tile and whose elevation is below the tile’s elevation. This lets a GM drop a tile over a room to mark it as not benefiting from scene-wide lighting.
This check only runs as part of the Global Illumination path. Individual light sources are not blocked by tiles; they are blocked by walls.
3D distance
Section titled “3D distance”Token-to-source distance is calculated in pixel space with an elevation component:
gridSize = canvas.grid.sizegridDistance = canvas.scene.grid.distancetokenZ = (token.elevation / gridDistance) * gridSizelightZ = (source.elevation / gridDistance) * gridSizedistance = sqrt(dx² + dy² + (tokenZ - lightZ)²)The position.elevation value comes from the resolved end-of-waypoint elevation when the trigger was a token move, or the token’s current document.elevation otherwise.
Negative lights
Section titled “Negative lights”When Negative Lights Support is on (off by default), a source whose luminosity is below zero darkens instead of illuminating:
- A negative source within its dim radius caps the condition at Dim (downgrade if currently Bright).
- A negative source within its bright radius caps the condition at Dark (downgrade if currently any lit state).
Positive sources still upgrade as normal in the same loop, so the resolved condition is the net result of all sources after sorting.
This setting is experimental and is the only path that allows light sources to lower the condition.
Token light sources
Section titled “Token light sources”Tokens with a configured token light (such as a held torch) are processed identically to ambient lights. The same gates apply (max radius, cone angle, wall collision, 3D distance). A token’s own light does not illuminate itself in any special way; the calculation treats it as any other source.
If a token’s light properties (light.bright, light.dim, light.luminosity, light.angle, light.rotation) change, an all-tokens recalculation runs because the change can affect other tokens around it.