ludic/examples/library/light_tiers.ludic
Orkuncakilkaya bf36bc8a8f refactor(runtime,packages,examples): named constants, package enums, idiom sweep
- runtime: HEADLESS_FRAME_PATH, STICK_DEADZONE / STICK_LEFT_X/Y, key and
  byte codes as char literals throughout (`k == 'w'`, `fill(rt_map, ' ', …)`)
- ludic.gameplay/stats: drop the duplicate `stat_field` (it answered "atk"
  for every build stat); Stats.base uses stats_field_name
- ludic.shooter: compare aim modes and fire patterns with AimMode.* and
  WeaponPattern.* instead of raw ints; STICK_RIGHT_X/Y
- ludic.npcai: DecisionMade / brain_set_state use AiState.*
- examples/games/menu.ludic uses Font.load / Ui.* with FONT_PATH and
  BACKDROP named; strings.ludic header says what it prints
- whole tree: `x = x + 1` → `x += 1` (single-term right-hand sides only),
  `0 - x` → `-x`, ASCII codes → char literals; every .ludic and every
  ```ludic fence reformatted with the fixed formatter (whitespace only)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 01:12:26 +03:00

91 lines
4.5 KiB
Text

# light_tiers.ludic — the lighting render-quality tiers from #49, all riding on
# the same deterministic accumulation core (integer + Q16.16 fixed):
#
# * Light.spot — a cone / flashlight (direction + spread degrees)
# * Light.falloff — the brightness-ramp exponent (linear → quadratic → …)
# * Light.soft — soft shadows: an occluder edge fades through a penumbra
# * Light.gel — a colour cookie: the light gels centre → rim
# * Light.normal — normal-mapped surfaces shade by facing (N·L), not distance
# * Light.time_of_day — a day/night ambient ramp from one 0..1 value
#
# Additive blocks light a *black* scene (so brightness is the light itself, not a
# saturated white); the day/night block modulates a *white* scene (ambient is
# multiplicative). Deterministic, so:
# bin/ludic examples/library/light_tiers.ludic -> 1 1 1 1 1 1 1 1 1
program LightTiers {
property Marker { n: int = 0 } # a component makes this a game (links the runtime)
model M { Marker }
function dark() -> void { Screen.clear(0) }
function white() -> void { Screen.fill_rectangle(0, 0, 64, 64, 16777215) }
function bit(b: bool) -> int { if b { return 1 }; return 0 }
function lum(x: int, y: int) -> int { return Screen.pixel(x, y) & 255 }
entry {
# --- 1. spot / cone: aimed right (0°), a narrow 25° cone -----------------
# a pixel to the right is inside the cone (lit); one straight up is outside.
dark()
Light.spot(32, 32, 24, 16777215, 1.0, 0, 25)
print(bit(lum(44, 32) > 0)) # 1 — right of centre, inside the cone
print(bit(lum(32, 14) == 0)) # 1 — above centre, outside the cone (dark)
# --- 2. falloff exponent: quadratic dims the mid-radius vs linear ---------
dark()
Light.falloff(1)
Light.point(32, 32, 24, 16777215, 1.0)
let lin = lum(44, 32) # linear brightness a little off-centre
dark()
Light.falloff(2)
Light.point(32, 32, 24, 16777215, 1.0)
let quad = lum(44, 32) # quadratic: same spot, dimmer
Light.falloff(1) # reset for later blocks
print(bit(quad < lin)) # 1 — a steeper curve is darker mid-radius
# --- 3. soft shadows: a penumbra pixel is partly lit where a hard edge cuts
dark()
Light.clear_occluders()
Light.occlude(38, 22, 3, 12) # a pillar right of the torch
Light.point(32, 32, 28, 16777215, 1.0)
let hard = lum(46, 34) # hard shadow at this pixel → fully dark
dark()
Light.soft(5) # 5px penumbra
Light.point(32, 32, 28, 16777215, 1.0)
let soft = lum(46, 34) # same pixel, now inside the penumbra
Light.soft(0) # back to hard shadows
Light.clear_occluders()
print(bit(hard == 0)) # 1 — a hard shadow cuts the ray fully
print(bit(soft > 0)) # 1 — the penumbra lets partial light through
# --- 4. gel / colour cookie: rim tints toward the outer colour -----------
dark()
Light.gel(255) # rim gels to pure blue (0x0000FF)
Light.point(32, 32, 24, 16711680, 1.0) # inner is pure red (0xFF0000)
let center_b = Screen.pixel(34, 32) & 255 # blue near the centre → low
let rim_b = Screen.pixel(50, 32) & 255 # blue near the rim → high
Light.clear_gel()
print(bit(rim_b > center_b)) # 1 — the gel tints the rim bluer than the core
# --- 5. normal mapping: a face tilted toward the light is brighter --------
dark()
Light.clear_normals()
Light.normal(18, 30, 8, 8, 0.7, 0.0) # this patch faces right (toward the light)
Light.normal(46, 30, 8, 8, 0.0 - 0.7, 0.0) # this patch faces left (away)
Light.point(32, 34, 44, 16777215, 1.0) # a light centred between the patches
let toward = lum(22, 34) # patch facing the light
let away = lum(48, 34) # patch facing away
Light.clear_normals()
print(bit(toward > away)) # 1 — N·L brightens the facing surface
# --- 6. time of day: midnight is dark, noon is bright (ambient modulate) --
white()
Light.time_of_day(0.0) # midnight
let night = lum(10, 10)
white()
Light.time_of_day(0.5) # noon
let noon = lum(10, 10)
print(bit(noon > night)) # 1 — daylight lifts the ambient
print(bit(night < 60)) # 1 — midnight stays dim
quit()
}
}