wip(0.S2): migrate - a var nothing writes becomes a let; a state is keyed by its module, directory or program, so every program's plan agrees; paths normalized; program states named SceneDemoState / scene_demo_st; the LSP reads state, mut and entry/handler parameters

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 15:31:34 +03:00
parent 3eda72e8c2
commit 5ffe50ed02
463 changed files with 72045 additions and 67421 deletions

View file

@ -5,38 +5,38 @@ function wx_front_kind() -> int {
return WeatherWorld.front()
}
function wx_want_wet(base_st: mut BaseState, weather_st: mut WeatherState) -> void {
if weather_st.wx_kinds[weather_st.wx_state].wet or weather_st.wx_forced { return }
function wx_want_wet() -> void {
if wx_kinds[wx_state].wet or wx_forced { return }
let front = wx_front_kind()
weather_st.wx_next = front
weather_st.wx_forecast = front
weather_st.wx_forced = true
if weather_st.wx_left > 1.5 { weather_st.wx_left = 1.5 }
wx_fact(base_st, weather_st, WEATHER_FRONT, front, weather_st.wx_state)
wx_next = front
wx_forecast = front
wx_forced = true
if wx_left > 1.5 { wx_left = 1.5 }
wx_fact(WEATHER_FRONT, front, wx_state)
}
function wx_hold(weather_st: mut WeatherState) -> void {
if weather_st.wx_left < 1.2 {
weather_st.wx_left = 1.2
weather_st.wx_next = weather_st.wx_state
function wx_hold() -> void {
if wx_left < 1.2 {
wx_left = 1.2
wx_next = wx_state
}
}
function wx_want_clear(weather_st: mut WeatherState) -> void {
let clear = wx_clear_kind(weather_st)
if weather_st.wx_state != clear and not weather_st.wx_forced {
if weather_st.wx_left > 0.5 { weather_st.wx_left = 0.5 }
weather_st.wx_next = clear
function wx_want_clear() -> void {
let clear = wx_clear_kind()
if wx_state != clear and not wx_forced {
if wx_left > 0.5 { wx_left = 0.5 }
wx_next = clear
}
if weather_st.wx_state == clear and weather_st.wx_left < 1.0 {
weather_st.wx_left = 1.0
weather_st.wx_next = clear
if wx_state == clear and wx_left < 1.0 {
wx_left = 1.0
wx_next = clear
}
}
function wx_apply_bias(base_st: mut BaseState, weather_st: mut WeatherState) -> void {
function wx_apply_bias() -> void {
let b = wx_bias()
if b == WEATHER_WANT_WET { wx_want_wet(base_st, weather_st) }
if b == WEATHER_HOLD { wx_hold(weather_st) }
if b == WEATHER_WANT_CLEAR { wx_want_clear(weather_st) }
if b == WEATHER_WANT_WET { wx_want_wet() }
if b == WEATHER_HOLD { wx_hold() }
if b == WEATHER_WANT_CLEAR { wx_want_clear() }
}

View file

@ -1,30 +1,34 @@
# config.ludic - the kinds the game hands in, the wind's daily turn, and the port's defaults
var wx_down_dir: float = 0.0 # the wind overnight and into the morning
var wx_up_dir: float = 3.1415927 # the wind while the slopes are warm
var wx_up_from: float = 10.0
var wx_up_to: float = 19.0
# the kinds, in the game's order; the first `clear` one is where a new trip starts
export function weather_kinds(weather_st: mut WeatherState, kinds: []WeatherKind) -> void { weather_st.wx_kinds = kinds }
export function weather_kinds(kinds: []WeatherKind) -> void { wx_kinds = kinds }
export function weather_kind_count(weather_st: WeatherState) -> int {
if weather_st.wx_kinds == null { return 0 }
return len(weather_st.wx_kinds)
export function weather_kind_count() -> int {
if wx_kinds == null { return 0 }
return len(wx_kinds)
}
export function weather_kind(weather_st: WeatherState, k: int) -> WeatherKind { return weather_st.wx_kinds[k] }
export function weather_kind(k: int) -> WeatherKind { return wx_kinds[k] }
# a valley's wind: down it by night, up it from `up_from` to `up_to` while the slopes are warm
export function weather_config_wind(weather_st: mut WeatherState, down_dir: float, up_dir: float, up_from: float, up_to: float) -> void {
weather_st.wx_down_dir = down_dir
weather_st.wx_up_dir = up_dir
weather_st.wx_up_from = up_from
weather_st.wx_up_to = up_to
export function weather_config_wind(down_dir: float, up_dir: float, up_from: float, up_to: float) -> void {
wx_down_dir = down_dir
wx_up_dir = up_dir
wx_up_from = up_from
wx_up_to = up_to
}
function wx_clear_kind(weather_st: WeatherState) -> int {
for k in 0 .. weather_kind_count(weather_st) { if weather_st.wx_kinds[k].clear { return k } }
function wx_clear_kind() -> int {
for k in 0 .. weather_kind_count() { if wx_kinds[k].clear { return k } }
return 0
}
function wx_first_wet(weather_st: WeatherState) -> int {
for k in 0 .. weather_kind_count(weather_st) { if weather_st.wx_kinds[k].wet { return k } }
function wx_first_wet() -> int {
for k in 0 .. weather_kind_count() { if wx_kinds[k].wet { return k } }
return 0
}

View file

@ -20,11 +20,11 @@ export function weather_draw_from(w: []float, from: int, roll: float) -> int {
return len(w) - 1
}
function wx_pick_next(weather_st: WeatherState, from: int) -> int {
return weather_draw_from(wx_odds(from), from, rng_float(weather_st.wx_dice))
function wx_pick_next(from: int) -> int {
return weather_draw_from(wx_odds(from), from, rng_float(wx_dice))
}
function wx_spell_hours(weather_st: WeatherState, k: int) -> float {
let kd = weather_st.wx_kinds[k]
return rng_span(weather_st.wx_dice, kd.spell_lo, kd.spell_hi)
function wx_spell_hours(k: int) -> float {
let kd = wx_kinds[k]
return rng_span(wx_dice, kd.spell_lo, kd.spell_hi)
}

View file

@ -1,38 +1,38 @@
# queries.ludic - what the world asks the sky
export function weather_state(weather_st: WeatherState) -> int { return weather_st.wx_state }
export function weather_next(weather_st: WeatherState) -> int { return weather_st.wx_next }
export function weather_left(weather_st: WeatherState) -> float { return weather_st.wx_left }
export function weather_amount(weather_st: WeatherState) -> float { return weather_st.wx_amount }
export function weather_forecast(weather_st: WeatherState) -> int { return weather_st.wx_forecast }
export function weather_forced(weather_st: WeatherState) -> bool { return weather_st.wx_forced }
export function weather_wind(weather_st: WeatherState) -> float { return weather_st.wx_wind }
export function weather_dir(weather_st: WeatherState) -> float { return weather_st.wx_dir }
export function weather_ground_wet(weather_st: WeatherState) -> float { return weather_st.wx_ground }
export function weather_flash(weather_st: WeatherState) -> float { return weather_st.wx_flash }
export function weather_state() -> int { return wx_state }
export function weather_next() -> int { return wx_next }
export function weather_left() -> float { return wx_left }
export function weather_amount() -> float { return wx_amount }
export function weather_forecast() -> int { return wx_forecast }
export function weather_forced() -> bool { return wx_forced }
export function weather_wind() -> float { return wx_wind }
export function weather_dir() -> float { return wx_dir }
export function weather_ground_wet() -> float { return wx_ground }
export function weather_flash() -> float { return wx_flash }
# rain or snow is falling, and more than a spit of it
export function weather_wet(weather_st: WeatherState) -> bool {
if weather_kind_count(weather_st) == 0 { return false }
return weather_st.wx_kinds[weather_st.wx_state].wet and weather_st.wx_amount > 0.3
export function weather_wet() -> bool {
if weather_kind_count() == 0 { return false }
return wx_kinds[wx_state].wet and wx_amount > 0.3
}
export function weather_overcast(weather_st: WeatherState) -> float {
if weather_kind_count(weather_st) == 0 { return 0.0 }
return weather_st.wx_kinds[weather_st.wx_state].overcast * weather_st.wx_amount
export function weather_overcast() -> float {
if weather_kind_count() == 0 { return 0.0 }
return wx_kinds[wx_state].overcast * wx_amount
}
export function weather_cold(weather_st: WeatherState) -> float {
if weather_kind_count(weather_st) == 0 { return 0.0 }
return weather_st.wx_kinds[weather_st.wx_state].cold * weather_st.wx_amount
export function weather_cold() -> float {
if weather_kind_count() == 0 { return 0.0 }
return wx_kinds[wx_state].cold * wx_amount
}
export function weather_name(weather_st: WeatherState, k: int) -> string {
if k < 0 or k >= weather_kind_count(weather_st) { return "" }
return weather_st.wx_kinds[k].name
export function weather_name(k: int) -> string {
if k < 0 or k >= weather_kind_count() { return "" }
return wx_kinds[k].name
}
# the half hour after a front goes through, when the air is glass-clear
export function weather_after_front(weather_st: WeatherState) -> bool { return weather_st.wx_clear_t > 0.0 }
export function weather_after_front() -> bool { return wx_clear_t > 0.0 }
# yesterday had rain or snow in it (the valley's fog pools after a wet day)
export function weather_wet_yesterday(weather_st: WeatherState) -> bool { return weather_st.wx_wet_yesterday }
export function weather_wet_yesterday() -> bool { return wx_wet_yesterday }

View file

@ -1,75 +1,75 @@
# run.ludic - the sky moving on. Only the machine that owns the world runs the spells; every
# machine eases the ground's wetness and makes its own lightning out of a storm it is told of.
function wx_turn(base_st: mut BaseState, weather_st: mut WeatherState) -> void {
let was = weather_st.wx_state
weather_st.wx_state = weather_st.wx_next
weather_st.wx_next = wx_pick_next(weather_st, weather_st.wx_state)
weather_st.wx_left = wx_spell_hours(weather_st, weather_st.wx_state)
if weather_st.wx_forced {
weather_st.wx_forced = false
function wx_turn() -> void {
let was = wx_state
wx_state = wx_next
wx_next = wx_pick_next(wx_state)
wx_left = wx_spell_hours(wx_state)
if wx_forced {
wx_forced = false
let least = Math.max(5.0, WeatherWorld.front_hours())
if weather_st.wx_left < least { weather_st.wx_left = least }
if wx_left < least { wx_left = least }
}
if weather_st.wx_state == was { return }
if wx_state == was { return }
# a front going through and leaving clear sky behind it: the light people get up for
if weather_st.wx_kinds[weather_st.wx_state].clear and weather_st.wx_kinds[was].wet and not wx_night() {
weather_st.wx_clear_t = 0.6
wx_fact(base_st, weather_st, WEATHER_CLEARED, weather_st.wx_state, was)
if wx_kinds[wx_state].clear and wx_kinds[was].wet and not wx_night() {
wx_clear_t = 0.6
wx_fact(WEATHER_CLEARED, wx_state, was)
}
wx_fact(base_st, weather_st, WEATHER_BEGAN, weather_st.wx_state, was)
wx_fact(WEATHER_BEGAN, wx_state, was)
}
function wx_wind_tick(weather_st: mut WeatherState, dt: float) -> void {
let kd = weather_st.wx_kinds[weather_st.wx_state]
weather_st.wx_wind = weather_st.wx_wind + (kd.wind - weather_st.wx_wind) * Math.min(1.0, dt * 0.3)
function wx_wind_tick(dt: float) -> void {
let kd = wx_kinds[wx_state]
wx_wind = wx_wind + (kd.wind - wx_wind) * Math.min(1.0, dt * 0.3)
var want = kd.wind_dir
if want < 0.0 {
let h = wx_hours()
want = weather_st.wx_down_dir
if h > weather_st.wx_up_from and h < weather_st.wx_up_to { want = weather_st.wx_up_dir }
want = wx_down_dir
if h > wx_up_from and h < wx_up_to { want = wx_up_dir }
}
let r = dt * 0.12
weather_st.wx_dir = wx_wrap(weather_st.wx_dir + Math.clamp(wx_wrap(want - weather_st.wx_dir), -r, r))
wx_dir = wx_wrap(wx_dir + Math.clamp(wx_wrap(want - wx_dir), -r, r))
}
# the spells: the story's lean, the clock running down, the edges eased, the wind
function wx_spells(base_st: mut BaseState, weather_st: mut WeatherState, dt: float, gh: float) -> void {
wx_apply_bias(base_st, weather_st)
weather_st.wx_left = weather_st.wx_left - gh
if weather_st.wx_left < 0.0 { wx_turn(base_st, weather_st) }
function wx_spells(dt: float, gh: float) -> void {
wx_apply_bias()
wx_left = wx_left - gh
if wx_left < 0.0 { wx_turn() }
var want = 1.0
if weather_st.wx_kinds[weather_st.wx_state].clear { want = 0.0 }
if weather_st.wx_left < 0.5 and weather_st.wx_kinds[weather_st.wx_next].clear { want = 0.0 }
weather_st.wx_amount = weather_st.wx_amount + (want - weather_st.wx_amount) * Math.min(1.0, dt * 0.5)
wx_wind_tick(weather_st, dt)
if weather_st.wx_clear_t > 0.0 { weather_st.wx_clear_t = weather_st.wx_clear_t - gh }
if wx_kinds[wx_state].clear { want = 0.0 }
if wx_left < 0.5 and wx_kinds[wx_next].clear { want = 0.0 }
wx_amount = wx_amount + (want - wx_amount) * Math.min(1.0, dt * 0.5)
wx_wind_tick(dt)
if wx_clear_t > 0.0 { wx_clear_t = wx_clear_t - gh }
}
# soaking is faster than drying: a downpour wets the ground in under a minute, and it takes
# several to give it back
function wx_ground_tick(weather_st: mut WeatherState, dt: float) -> void {
let want = weather_st.wx_kinds[weather_st.wx_state].soak * weather_st.wx_amount
function wx_ground_tick(dt: float) -> void {
let want = wx_kinds[wx_state].soak * wx_amount
var k = dt * 0.10
if want < weather_st.wx_ground { k = dt * 0.020 }
weather_st.wx_ground = weather_st.wx_ground + (want - weather_st.wx_ground) * Math.clamp(k, 0.0, 1.0)
if want < wx_ground { k = dt * 0.020 }
wx_ground = wx_ground + (want - wx_ground) * Math.clamp(k, 0.0, 1.0)
}
function wx_storm_tick(base_st: mut BaseState, weather_st: mut WeatherState, dt: float) -> void {
weather_st.wx_flash = weather_st.wx_flash * Math.max(0.0, 1.0 - dt * 9.0)
if not weather_st.wx_kinds[weather_st.wx_state].thunder or weather_st.wx_amount < 0.5 { return }
weather_st.wx_thunder_t = weather_st.wx_thunder_t - dt
if weather_st.wx_thunder_t < 0.0 {
weather_st.wx_thunder_t = rng_span(weather_st.wx_dice, 9.0, 34.0)
weather_st.wx_flash = 1.0
wx_fact(base_st, weather_st, WEATHER_THUNDER, weather_st.wx_state, weather_st.wx_state)
function wx_storm_tick(dt: float) -> void {
wx_flash = wx_flash * Math.max(0.0, 1.0 - dt * 9.0)
if not wx_kinds[wx_state].thunder or wx_amount < 0.5 { return }
wx_thunder_t = wx_thunder_t - dt
if wx_thunder_t < 0.0 {
wx_thunder_t = rng_span(wx_dice, 9.0, 34.0)
wx_flash = 1.0
wx_fact(WEATHER_THUNDER, wx_state, wx_state)
}
}
# one step: `dt` real seconds and `gh` game hours
export function weather_run(base_st: mut BaseState, weather_st: mut WeatherState, dt: float, gh: float) -> void {
if weather_kind_count(weather_st) == 0 { return }
if wx_owns() { wx_spells(base_st, weather_st, dt, gh) }
wx_ground_tick(weather_st, dt)
wx_storm_tick(base_st, weather_st, dt)
if weather_wet(weather_st) { weather_st.wx_wet_today = true }
export function weather_run(dt: float, gh: float) -> void {
if weather_kind_count() == 0 { return }
if wx_owns() { wx_spells(dt, gh) }
wx_ground_tick(dt)
wx_storm_tick(dt)
if weather_wet() { wx_wet_today = true }
}

View file

@ -15,7 +15,7 @@ export port WeatherWorld {
function wx_owns_alone() -> bool { return true }
function wx_noon() -> float { return 12.0 }
function wx_daylight() -> bool { return false }
function wx_own_odds(weather_st: WeatherState, from: int) -> []float { return weather_st.wx_kinds[from].odds }
function wx_own_odds(from: int) -> []float { return wx_kinds[from].odds }
function wx_no_bias() -> int { return WEATHER_NO_BIAS }
function wx_no_hours() -> float { return 0.0 }
function wx_seed_one() -> int { return 1 }
@ -53,40 +53,34 @@ export property WeatherFact {
from: int = 0
}
export state WeatherState {
wx_down_dir: float = 0.0 # the wind overnight and into the morning
wx_up_dir: float = 3.1415927 # the wind while the slopes are warm
wx_up_from: float = 10.0
wx_up_to: float = 19.0
wx_kinds: []WeatherKind = null
wx_state: int = 0
wx_next: int = 0
wx_left: float = 0.0 # game hours left in this spell
wx_amount: float = 0.0 # 0..1, how far in (eases at the edges)
wx_forecast: int = 0
wx_forced: bool = false # the next spell is a front the story asked for
wx_wind: float = 0.2
wx_dir: float = 0.0 # radians the wind blows toward
wx_ground: float = 0.0 # 0 dry .. 1 soaked
wx_flash: float = 0.0
wx_thunder_t: float = 0.0 # real seconds to the next strike
wx_clear_t: float = 0.0 # game hours of after-the-front light left
wx_wet_today: bool = false
wx_wet_yesterday: bool = false
wx_dice: Rng = null
wx_facts: Queue<WeatherFact> = null
}
var wx_kinds: []WeatherKind = null
var wx_state: int = 0
var wx_next: int = 0
var wx_left: float = 0.0 # game hours left in this spell
var wx_amount: float = 0.0 # 0..1, how far in (eases at the edges)
var wx_forecast: int = 0
var wx_forced: bool = false # the next spell is a front the story asked for
var wx_wind: float = 0.2
var wx_dir: float = 0.0 # radians the wind blows toward
var wx_ground: float = 0.0 # 0 dry .. 1 soaked
var wx_flash: float = 0.0
var wx_thunder_t: float = 0.0 # real seconds to the next strike
var wx_clear_t: float = 0.0 # game hours of after-the-front light left
var wx_wet_today: bool = false
var wx_wet_yesterday: bool = false
var wx_dice: Rng = null
var wx_facts: Queue<WeatherFact> = null
# the facts, oldest first; the game drains them into its notices and sounds
export function weather_facts(base_st: mut BaseState, weather_st: mut WeatherState) -> Queue<WeatherFact> {
if weather_st.wx_facts == null { weather_st.wx_facts = queue_new(base_st, "weather.facts") }
return weather_st.wx_facts
export function weather_facts() -> Queue<WeatherFact> {
if wx_facts == null { wx_facts = queue_new("weather.facts") }
return wx_facts
}
function wx_fact(base_st: mut BaseState, weather_st: mut WeatherState, what: int, kind: int, from: int) -> void {
function wx_fact(what: int, kind: int, from: int) -> void {
let f = new WeatherFact
f.what = what
f.kind = kind
f.from = from
q_push(base_st, weather_facts(base_st, weather_st), f)
q_push(weather_facts(), f)
}

View file

@ -1,43 +1,43 @@
# system.ludic - the weather as a system: PH_SIMULATE, and its own save section
function weather_tick(base_st: mut BaseState, weather_st: mut WeatherState, t: Tick) -> void { weather_run(base_st, weather_st, t.dt, t.hours) }
function weather_tick(t: Tick) -> void { weather_run(t.dt, t.hours) }
export function weather_save(weather_st: WeatherState) -> Val {
export function weather_save() -> Val {
let v = Value.object()
sv_put_int(v, "state", weather_st.wx_state)
sv_put_int(v, "next", weather_st.wx_next)
sv_put_float(v, "left", weather_st.wx_left)
sv_put_int(v, "forecast", weather_st.wx_forecast)
sv_put_bool(v, "forced", weather_st.wx_forced)
sv_put_float(v, "dir", weather_st.wx_dir)
sv_put_float(v, "ground", weather_st.wx_ground)
sv_put_float(v, "clear_t", weather_st.wx_clear_t)
sv_put_bool(v, "wet_today", weather_st.wx_wet_today)
sv_put_bool(v, "wet_yesterday", weather_st.wx_wet_yesterday)
sv_put_int(v, "dice", weather_st.wx_dice.s)
sv_put_int(v, "state", wx_state)
sv_put_int(v, "next", wx_next)
sv_put_float(v, "left", wx_left)
sv_put_int(v, "forecast", wx_forecast)
sv_put_bool(v, "forced", wx_forced)
sv_put_float(v, "dir", wx_dir)
sv_put_float(v, "ground", wx_ground)
sv_put_float(v, "clear_t", wx_clear_t)
sv_put_bool(v, "wet_today", wx_wet_today)
sv_put_bool(v, "wet_yesterday", wx_wet_yesterday)
sv_put_int(v, "dice", wx_dice.s)
return v
}
function wx_kind_ok(weather_st: WeatherState, k: int) -> int {
if k < 0 or k >= weather_kind_count(weather_st) { return wx_clear_kind(weather_st) }
function wx_kind_ok(k: int) -> int {
if k < 0 or k >= weather_kind_count() { return wx_clear_kind() }
return k
}
export function weather_load(base_st: mut BaseState, weather_st: mut WeatherState, v: Val, version: int) -> void {
weather_reset(base_st, weather_st)
weather_st.wx_state = wx_kind_ok(weather_st, sv_int(v, "state", 0))
weather_st.wx_next = wx_kind_ok(weather_st, sv_int(v, "next", 0))
weather_st.wx_left = sv_float(v, "left", 10.0)
weather_st.wx_forecast = wx_kind_ok(weather_st, sv_int(v, "forecast", 0))
weather_st.wx_forced = sv_bool(v, "forced", false)
weather_st.wx_dir = sv_float(v, "dir", 0.0)
weather_st.wx_ground = sv_float(v, "ground", 0.0)
weather_st.wx_clear_t = sv_float(v, "clear_t", 0.0)
weather_st.wx_wet_today = sv_bool(v, "wet_today", false)
weather_st.wx_wet_yesterday = sv_bool(v, "wet_yesterday", false)
weather_st.wx_dice.s = sv_int(v, "dice", weather_st.wx_dice.s)
if weather_kind_count(weather_st) == 0 { return }
if not weather_st.wx_kinds[weather_st.wx_state].clear { weather_st.wx_amount = 1.0 }
weather_st.wx_wind = weather_st.wx_kinds[weather_st.wx_state].wind
export function weather_load(v: Val, version: int) -> void {
weather_reset()
wx_state = wx_kind_ok(sv_int(v, "state", 0))
wx_next = wx_kind_ok(sv_int(v, "next", 0))
wx_left = sv_float(v, "left", 10.0)
wx_forecast = wx_kind_ok(sv_int(v, "forecast", 0))
wx_forced = sv_bool(v, "forced", false)
wx_dir = sv_float(v, "dir", 0.0)
wx_ground = sv_float(v, "ground", 0.0)
wx_clear_t = sv_float(v, "clear_t", 0.0)
wx_wet_today = sv_bool(v, "wet_today", false)
wx_wet_yesterday = sv_bool(v, "wet_yesterday", false)
wx_dice.s = sv_int(v, "dice", wx_dice.s)
if weather_kind_count() == 0 { return }
if not wx_kinds[wx_state].clear { wx_amount = 1.0 }
wx_wind = wx_kinds[wx_state].wind
}
export function weather_system() -> System {

View file

@ -4,22 +4,20 @@ import "ludic.weather"
import "ludic.base"
program WeatherTest {
numbers float
state WeathertestState {
owns: bool = true
hours: float = 12.0
bias: int = 0
lean: []float = null
}
var owns: bool = true
var hours: float = 12.0
var bias: int = 0
var lean: []float = null
function fake_owns(weathertest_st: WeathertestState) -> bool { return weathertest_st.owns }
function fake_hours(weathertest_st: WeathertestState) -> float { return weathertest_st.hours }
function fake_night(weathertest_st: WeathertestState) -> bool { return weathertest_st.hours < 6.0 or weathertest_st.hours > 20.0 }
function fake_bias(weathertest_st: WeathertestState) -> int { return weathertest_st.bias }
function fake_owns() -> bool { return owns }
function fake_hours() -> float { return hours }
function fake_night() -> bool { return hours < 6.0 or hours > 20.0 }
function fake_bias() -> int { return bias }
function fake_seed() -> int { return 7 }
function fake_front_hours() -> float { return 6.0 }
function fake_odds(weather_st: WeatherState, weathertest_st: WeathertestState, from: int) -> []float {
if weathertest_st.lean != null { return weathertest_st.lean }
return weather_kind(weather_st, from).odds
function fake_odds(from: int) -> []float {
if lean != null { return lean }
return weather_kind(from).odds
}
function odds3(a: float, b: float, c: float) -> []float {
@ -59,146 +57,146 @@ program WeatherTest {
}
# clear, rain and storm: clear always goes to rain, rain to storm, storm to clear
function fresh(base_st: mut BaseState, weather_st: mut WeatherState, weathertest_st: mut WeathertestState) -> void {
weathertest_st.owns = true
weathertest_st.hours = 12.0
weathertest_st.bias = WEATHER_NO_BIAS
weathertest_st.lean = null
function fresh() -> void {
owns = true
hours = 12.0
bias = WEATHER_NO_BIAS
lean = null
let ks = new []WeatherKind
push(ks, kind("clear", false, false, odds3(0.0, 1.0, 0.0)))
push(ks, kind("rain", true, false, odds3(0.0, 0.0, 1.0)))
push(ks, kind("storm", true, true, odds3(1.0, 0.0, 0.0)))
weather_kinds(weather_st, ks)
weather_config_wind(weather_st, 0.5, 2.5, 10.0, 19.0)
weather_reset(base_st, weather_st)
weather_kinds(ks)
weather_config_wind(0.5, 2.5, 10.0, 19.0)
weather_reset()
}
function facts(base_st: mut BaseState, weather_st: mut WeatherState) -> []WeatherFact { return q_drain(base_st, weather_facts(base_st, weather_st)) }
function step(base_st: mut BaseState, weather_st: mut WeatherState, dt: float, gh: float, n: int) -> void { for i in 0 .. n { weather_run(base_st, weather_st, dt, gh) } }
function facts() -> []WeatherFact { return q_drain(weather_facts()) }
function step(dt: float, gh: float, n: int) -> void { for i in 0 .. n { weather_run(dt, gh) } }
function count(fs: []WeatherFact, what: int) -> int {
var n = 0
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
return n
}
test "a new trip is clear, dry and forecast clear" (base_st: mut BaseState, weather_st: mut WeatherState, weathertest_st: mut WeathertestState) {
fresh(base_st, weather_st, weathertest_st)
expect_eq(weather_state(weather_st), 0)
expect_eq(weather_forecast(weather_st), 0)
expect(not weather_wet(weather_st))
expect(weather_cold(weather_st) == 0.0)
test "a new trip is clear, dry and forecast clear" {
fresh()
expect_eq(weather_state(), 0)
expect_eq(weather_forecast(), 0)
expect(not weather_wet())
expect(weather_cold() == 0.0)
}
test "a spell that runs out gives way to the next, as a fact" (base_st: mut BaseState, weather_st: mut WeatherState, weathertest_st: mut WeathertestState) {
fresh(base_st, weather_st, weathertest_st)
weather_set(weather_st, 0, 1, 0.1)
step(base_st, weather_st, 0.1, 0.2, 1)
expect_eq(weather_state(weather_st), 1)
expect_eq(weather_next(weather_st), 2)
let fs = facts(base_st, weather_st)
test "a spell that runs out gives way to the next, as a fact" {
fresh()
weather_set(0, 1, 0.1)
step(0.1, 0.2, 1)
expect_eq(weather_state(), 1)
expect_eq(weather_next(), 2)
let fs = facts()
expect_eq(count(fs, WEATHER_BEGAN), 1)
expect(weather_left(weather_st) >= 1.0 and weather_left(weather_st) < 3.0)
expect(weather_left() >= 1.0 and weather_left() < 3.0)
}
test "the amount eases in, and wet follows it" (base_st: mut BaseState, weather_st: mut WeatherState, weathertest_st: mut WeathertestState) {
fresh(base_st, weather_st, weathertest_st)
weather_set(weather_st, 0, 1, 0.01)
step(base_st, weather_st, 0.1, 0.02, 1)
expect(not weather_wet(weather_st))
step(base_st, weather_st, 0.1, 0.0, 200)
expect(weather_wet(weather_st))
expect(weather_cold(weather_st) > 4.0)
expect(weather_ground_wet(weather_st) > 0.5)
test "the amount eases in, and wet follows it" {
fresh()
weather_set(0, 1, 0.01)
step(0.1, 0.02, 1)
expect(not weather_wet())
step(0.1, 0.0, 200)
expect(weather_wet())
expect(weather_cold() > 4.0)
expect(weather_ground_wet() > 0.5)
}
test "a storm throws lightning, and a clearing in daylight is a fact" (base_st: mut BaseState, weather_st: mut WeatherState, weathertest_st: mut WeathertestState) {
fresh(base_st, weather_st, weathertest_st)
weather_set(weather_st, 2, 0, 2.0)
step(base_st, weather_st, 1.0, 0.0, 60)
expect(count(facts(base_st, weather_st), WEATHER_THUNDER) > 0)
weather_set(weather_st, 2, 0, 0.05)
step(base_st, weather_st, 0.1, 0.1, 1)
expect_eq(weather_state(weather_st), 0)
expect(weather_after_front(weather_st))
expect_eq(count(facts(base_st, weather_st), WEATHER_CLEARED), 1)
test "a storm throws lightning, and a clearing in daylight is a fact" {
fresh()
weather_set(2, 0, 2.0)
step(1.0, 0.0, 60)
expect(count(facts(), WEATHER_THUNDER) > 0)
weather_set(2, 0, 0.05)
step(0.1, 0.1, 1)
expect_eq(weather_state(), 0)
expect(weather_after_front())
expect_eq(count(facts(), WEATHER_CLEARED), 1)
}
test "the world's odds overrule the kind's" (base_st: mut BaseState, weather_st: mut WeatherState, weathertest_st: mut WeathertestState) {
fresh(base_st, weather_st, weathertest_st)
weathertest_st.lean = odds3(0.0, 0.0, 1.0)
weather_set(weather_st, 0, 0, 0.01)
step(base_st, weather_st, 0.1, 0.1, 1)
expect_eq(weather_next(weather_st), 2)
test "the world's odds overrule the kind's" {
fresh()
lean = odds3(0.0, 0.0, 1.0)
weather_set(0, 0, 0.01)
step(0.1, 0.1, 1)
expect_eq(weather_next(), 2)
}
test "wanting rain forecasts a front and holds it long enough" (base_st: mut BaseState, weather_st: mut WeatherState, weathertest_st: mut WeathertestState) {
fresh(base_st, weather_st, weathertest_st)
weathertest_st.bias = WEATHER_WANT_WET
step(base_st, weather_st, 0.1, 0.01, 1)
expect(weather_forced(weather_st))
expect_eq(weather_forecast(weather_st), 1)
expect_eq(count(facts(base_st, weather_st), WEATHER_FRONT), 1)
step(base_st, weather_st, 0.1, 2.0, 1)
expect_eq(weather_state(weather_st), 1)
expect(not weather_forced(weather_st))
expect(weather_left(weather_st) >= 6.0)
test "wanting rain forecasts a front and holds it long enough" {
fresh()
bias = WEATHER_WANT_WET
step(0.1, 0.01, 1)
expect(weather_forced())
expect_eq(weather_forecast(), 1)
expect_eq(count(facts(), WEATHER_FRONT), 1)
step(0.1, 2.0, 1)
expect_eq(weather_state(), 1)
expect(not weather_forced())
expect(weather_left() >= 6.0)
}
test "wanting clear sky clears it and keeps it" (base_st: mut BaseState, weather_st: mut WeatherState, weathertest_st: mut WeathertestState) {
fresh(base_st, weather_st, weathertest_st)
weather_set(weather_st, 1, 1, 5.0)
weathertest_st.bias = WEATHER_WANT_CLEAR
step(base_st, weather_st, 0.1, 0.6, 1)
expect_eq(weather_state(weather_st), 0)
step(base_st, weather_st, 0.1, 0.9, 5)
expect_eq(weather_state(weather_st), 0)
test "wanting clear sky clears it and keeps it" {
fresh()
weather_set(1, 1, 5.0)
bias = WEATHER_WANT_CLEAR
step(0.1, 0.6, 1)
expect_eq(weather_state(), 0)
step(0.1, 0.9, 5)
expect_eq(weather_state(), 0)
}
test "a machine that does not own the world runs no spells, but is told them" (base_st: mut BaseState, weather_st: mut WeatherState, weathertest_st: mut WeathertestState) {
fresh(base_st, weather_st, weathertest_st)
weathertest_st.owns = false
weather_set(weather_st, 0, 1, 0.1)
step(base_st, weather_st, 0.1, 5.0, 3)
expect_eq(weather_state(weather_st), 0)
weather_told(base_st, weather_st, 2, 0, 3.0, 1.0, 0.9, 1.0, 2)
expect_eq(weather_state(weather_st), 2)
expect_eq(count(facts(base_st, weather_st), WEATHER_BEGAN), 1)
step(base_st, weather_st, 1.0, 0.0, 60)
expect(count(facts(base_st, weather_st), WEATHER_THUNDER) > 0)
test "a machine that does not own the world runs no spells, but is told them" {
fresh()
owns = false
weather_set(0, 1, 0.1)
step(0.1, 5.0, 3)
expect_eq(weather_state(), 0)
weather_told(2, 0, 3.0, 1.0, 0.9, 1.0, 2)
expect_eq(weather_state(), 2)
expect_eq(count(facts(), WEATHER_BEGAN), 1)
step(1.0, 0.0, 60)
expect(count(facts(), WEATHER_THUNDER) > 0)
}
test "the wind turns with the hours" (base_st: mut BaseState, weather_st: mut WeatherState, weathertest_st: mut WeathertestState) {
fresh(base_st, weather_st, weathertest_st)
weathertest_st.hours = 3.0
step(base_st, weather_st, 1.0, 0.0, 100)
expect(Math.abs(weather_dir(weather_st) - 0.5) < 0.01)
weathertest_st.hours = 12.0
step(base_st, weather_st, 1.0, 0.0, 100)
expect(Math.abs(weather_dir(weather_st) - 2.5) < 0.01)
test "the wind turns with the hours" {
fresh()
hours = 3.0
step(1.0, 0.0, 100)
expect(Math.abs(weather_dir() - 0.5) < 0.01)
hours = 12.0
step(1.0, 0.0, 100)
expect(Math.abs(weather_dir() - 2.5) < 0.01)
}
test "a morning after rain remembers it was wet" (base_st: mut BaseState, weather_st: mut WeatherState, weathertest_st: mut WeathertestState) {
fresh(base_st, weather_st, weathertest_st)
weather_set(weather_st, 1, 1, 5.0)
step(base_st, weather_st, 0.1, 0.0, 1)
weather_morning(weather_st)
expect(weather_wet_yesterday(weather_st))
test "a morning after rain remembers it was wet" {
fresh()
weather_set(1, 1, 5.0)
step(0.1, 0.0, 1)
weather_morning()
expect(weather_wet_yesterday())
}
test "a save section reads back the same sky" (base_st: mut BaseState, weather_st: mut WeatherState, weathertest_st: mut WeathertestState) {
fresh(base_st, weather_st, weathertest_st)
weather_set(weather_st, 1, 2, 4.25)
weather_morning(weather_st)
let fc = weather_forecast(weather_st)
test "a save section reads back the same sky" {
fresh()
weather_set(1, 2, 4.25)
weather_morning()
let fc = weather_forecast()
let root = save_tree()
save_section(root, "weather", 1, weather_save(weather_st))
save_section(root, "weather", 1, weather_save())
let text = save_encode(root)
weather_reset(base_st, weather_st)
weather_reset()
let n = load_section(save_decode(text), "weather")
weather_load(base_st, weather_st, n.data, n.version)
expect_eq(weather_state(weather_st), 1)
expect(weather_left(weather_st) == 4.25)
expect_eq(weather_forecast(weather_st), fc)
expect(weather_wet_yesterday(weather_st))
weather_load(n.data, n.version)
expect_eq(weather_state(), 1)
expect(weather_left() == 4.25)
expect_eq(weather_forecast(), fc)
expect(weather_wet_yesterday())
}
}

View file

@ -1,66 +1,66 @@
# verbs.ludic - the only way the sky changes from outside: a morning, a reset, the host's word
# a new trip: clear sky, a still-ish wind, nothing forecast; the dice from the world's seed
export function weather_reset(base_st: mut BaseState, weather_st: mut WeatherState) -> void {
weather_st.wx_dice = rng_new(WeatherWorld.seed())
let clear = wx_clear_kind(weather_st)
weather_st.wx_state = clear
weather_st.wx_next = clear
weather_st.wx_forecast = clear
weather_st.wx_left = 30.0
weather_st.wx_amount = 0.0
weather_st.wx_forced = false
weather_st.wx_wind = 0.2
weather_st.wx_dir = 0.0
weather_st.wx_ground = 0.0
weather_st.wx_flash = 0.0
weather_st.wx_thunder_t = 0.0
weather_st.wx_clear_t = 0.0
weather_st.wx_wet_today = false
weather_st.wx_wet_yesterday = false
q_clear(base_st, weather_facts(base_st, weather_st))
export function weather_reset() -> void {
wx_dice = rng_new(WeatherWorld.seed())
let clear = wx_clear_kind()
wx_state = clear
wx_next = clear
wx_forecast = clear
wx_left = 30.0
wx_amount = 0.0
wx_forced = false
wx_wind = 0.2
wx_dir = 0.0
wx_ground = 0.0
wx_flash = 0.0
wx_thunder_t = 0.0
wx_clear_t = 0.0
wx_wet_today = false
wx_wet_yesterday = false
q_clear(weather_facts())
}
# a new day's forecast: seven times in ten it is the next spell. A front already on its way
# stays on its way.
export function weather_morning(weather_st: mut WeatherState) -> void {
if weather_kind_count(weather_st) == 0 { return }
weather_st.wx_wet_yesterday = weather_st.wx_wet_today or weather_st.wx_kinds[weather_st.wx_state].wet
weather_st.wx_wet_today = false
if weather_st.wx_forced {
weather_st.wx_forecast = weather_st.wx_next
export function weather_morning() -> void {
if weather_kind_count() == 0 { return }
wx_wet_yesterday = wx_wet_today or wx_kinds[wx_state].wet
wx_wet_today = false
if wx_forced {
wx_forecast = wx_next
return
}
weather_st.wx_forecast = wx_pick_next(weather_st, weather_st.wx_state)
if rng_float(weather_st.wx_dice) < 0.7 { weather_st.wx_next = weather_st.wx_forecast }
wx_forecast = wx_pick_next(wx_state)
if rng_float(wx_dice) < 0.7 { wx_next = wx_forecast }
}
# the sky outright: a spell of `kind` now, `left` hours of it, at full strength when not clear
# (staging, a test, a story beat)
export function weather_set(weather_st: mut WeatherState, kind: int, next: int, left: float) -> void {
weather_st.wx_state = kind
weather_st.wx_next = next
weather_st.wx_left = left
weather_st.wx_amount = 1.0
if weather_st.wx_kinds[kind].clear { weather_st.wx_amount = 0.0 }
export function weather_set(kind: int, next: int, left: float) -> void {
wx_state = kind
wx_next = next
wx_left = left
wx_amount = 1.0
if wx_kinds[kind].clear { wx_amount = 0.0 }
}
# what the machine that owns the world says the sky is; a new spell is a fact here too
export function weather_told(base_st: mut BaseState, weather_st: mut WeatherState, kind: int, next: int, left: float, amount: float, wind: float, dir: float, forecast: int) -> void {
let was = weather_st.wx_state
weather_st.wx_state = kind
weather_st.wx_next = next
weather_st.wx_left = left
weather_st.wx_amount = amount
weather_st.wx_wind = wind
weather_st.wx_dir = dir
weather_st.wx_forecast = forecast
if kind != was { wx_fact(base_st, weather_st, WEATHER_BEGAN, kind, was) }
export function weather_told(kind: int, next: int, left: float, amount: float, wind: float, dir: float, forecast: int) -> void {
let was = wx_state
wx_state = kind
wx_next = next
wx_left = left
wx_amount = amount
wx_wind = wind
wx_dir = dir
wx_forecast = forecast
if kind != was { wx_fact(WEATHER_BEGAN, kind, was) }
}
export function weather_set_wind(weather_st: mut WeatherState, strength: float, dir: float) -> void {
weather_st.wx_wind = strength
weather_st.wx_dir = dir
export function weather_set_wind(strength: float, dir: float) -> void {
wx_wind = strength
wx_dir = dir
}
export function weather_set_ground(weather_st: mut WeatherState, wet: float) -> void { weather_st.wx_ground = wet }
export function weather_set_flash(weather_st: mut WeatherState, f: float) -> void { weather_st.wx_flash = f }
export function weather_set_ground(wet: float) -> void { wx_ground = wet }
export function weather_set_flash(f: float) -> void { wx_flash = f }