feat(ludic.weather): spells of weather as a mechanic package
The kinds and their odds are data the game hands in; a port asks whether this machine owns the world, the hour, the night, the odds from a kind, what the story wants of the sky (a front, a hold, a clear night), the front's kind and length, and a seed. Its own dice; a new spell, a clearing, lightning and a wanted front are facts on weather_facts(); its own save section. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a2012a5afe
commit
093ca0d260
12 changed files with 746 additions and 0 deletions
201
packages/ludic.weather/tests/weather_test.ludic
Normal file
201
packages/ludic.weather/tests/weather_test.ludic
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
# weather_test.ludic - spells change on their clock and by the kinds' odds, the edges ease, the
|
||||
# story leans when asked, only the owner runs the spells, and the save reads back
|
||||
import "ludic.weather"
|
||||
import "ludic.base"
|
||||
program WeatherTest {
|
||||
numbers float
|
||||
var owns: bool = true
|
||||
var hours: float = 12.0
|
||||
var bias: int = 0
|
||||
var lean: []float = null
|
||||
|
||||
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(from: int) -> []float {
|
||||
if lean != null { return lean }
|
||||
return weather_kind(from).odds
|
||||
}
|
||||
|
||||
function odds3(a: float, b: float, c: float) -> []float {
|
||||
let w = new []float
|
||||
push(w, a)
|
||||
push(w, b)
|
||||
push(w, c)
|
||||
return w
|
||||
}
|
||||
|
||||
function kind(name: string, wet: bool, thunder: bool, odds: []float) -> WeatherKind {
|
||||
let k = new WeatherKind
|
||||
k.name = name
|
||||
k.clear = not wet
|
||||
k.wet = wet
|
||||
k.thunder = thunder
|
||||
k.odds = odds
|
||||
if wet {
|
||||
k.soak = 1.0
|
||||
k.overcast = 0.8
|
||||
k.cold = 5.0
|
||||
k.wind = 1.0
|
||||
}
|
||||
k.spell_lo = 1.0
|
||||
k.spell_hi = 3.0
|
||||
return k
|
||||
}
|
||||
|
||||
# clear, rain and storm: clear always goes to rain, rain to storm, storm to clear
|
||||
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(ks)
|
||||
let w = new WeatherWorld
|
||||
w.owns_world = fn fake_owns
|
||||
w.hours = fn fake_hours
|
||||
w.is_night = fn fake_night
|
||||
w.bias = fn fake_bias
|
||||
w.seed = fn fake_seed
|
||||
w.odds = fn fake_odds
|
||||
w.front_hours = fn fake_front_hours
|
||||
weather_bind(w)
|
||||
weather_config_wind(0.5, 2.5, 10.0, 19.0)
|
||||
weather_reset()
|
||||
}
|
||||
|
||||
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" {
|
||||
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" {
|
||||
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() >= 1.0 and weather_left() < 3.0)
|
||||
}
|
||||
|
||||
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" {
|
||||
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" {
|
||||
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" {
|
||||
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" {
|
||||
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" {
|
||||
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" {
|
||||
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" {
|
||||
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" {
|
||||
fresh()
|
||||
weather_set(1, 2, 4.25)
|
||||
weather_morning()
|
||||
let fc = weather_forecast()
|
||||
let root = save_tree()
|
||||
save_section(root, "weather", 1, weather_save())
|
||||
let text = save_encode(root)
|
||||
weather_reset()
|
||||
let n = load_section(save_decode(text), "weather")
|
||||
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())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue