From 0e2d2a6fab76b7ac77a135477ea7dde7ed8e0402 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Mon, 28 Sep 2026 16:06:56 +0300 Subject: [PATCH] fix(ludic.wildlife): a newcomer takes the record of one of its species removed for good - its row, its key and so the game's drawing, every other field as new Each morning's repopulation made a new WildAnimal (and the game a new drawing, actor and skeleton, keyed by an ever-rising key) for every animal that had gone. A legend's record is never taken, nor one still in a crowd. reuse_test: a hundred removes and makes are one record. Co-Authored-By: Claude Opus 5.5 --- packages/ludic.wildlife/animal.ludic | 2 +- packages/ludic.wildlife/index.ludic | 1 + packages/ludic.wildlife/reuse.ludic | 85 +++++++++++++++++++ packages/ludic.wildlife/store.ludic | 8 +- .../ludic.wildlife/tests/reuse_test.ludic | 67 +++++++++++++++ 5 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 packages/ludic.wildlife/reuse.ludic create mode 100644 packages/ludic.wildlife/tests/reuse_test.ludic diff --git a/packages/ludic.wildlife/animal.ludic b/packages/ludic.wildlife/animal.ludic index 654f169c..ff3fb125 100644 --- a/packages/ludic.wildlife/animal.ludic +++ b/packages/ludic.wildlife/animal.ludic @@ -17,7 +17,7 @@ export const WILD_WARY: int = 13 # turned away, a step or two of distance t export const WILD_LAND: int = 14 # a bird gliding down to a spot export property WildAnimal { - key: int = 0 # unique for the run, never reused: the game's drawing is found by it + key: int = 0 # unique among the living; a reused record keeps it, and the game its drawing nid: int = 0 # its id across machines (the port's next_id) sp: int = 0 legend: bool = false diff --git a/packages/ludic.wildlife/index.ludic b/packages/ludic.wildlife/index.ludic index 2b5347db..cab30c54 100644 --- a/packages/ludic.wildlife/index.ludic +++ b/packages/ludic.wildlife/index.ludic @@ -10,6 +10,7 @@ import "places.ludic" import "port.ludic" import "facts.ludic" import "table.ludic" +import "reuse.ludic" import "store.ludic" import "near.ludic" import "guest.ludic" diff --git a/packages/ludic.wildlife/reuse.ludic b/packages/ludic.wildlife/reuse.ludic new file mode 100644 index 00000000..4403c545 --- /dev/null +++ b/packages/ludic.wildlife/reuse.ludic @@ -0,0 +1,85 @@ +# wildlife/reuse.ludic - a newcomer takes the record of one of its species removed for good: the +# row, the key and so the game's drawing come with it, and every other field is put back as new +function wl_reusable(w: WildTable, sp: int) -> WildAnimal { + let tb = w.tab + for r in 0 .. tb_len(tb) { + let a = tb.rec[r] + if a.sp == sp and not a.alive and not a.shown and not a.legend and a.crowd_id < 0 { return a } + } + return null +} +# every field but the key and the row as a new record has it +function wl_blank(a: WildAnimal) -> void { + a.nid = 0 + a.sp = 0 + a.legend = false + a.variant = 0 + a.scale = 1.0 + a.alive = true + a.shown = true + a.pinned = false + a.x = 0.0 + a.y = 0.0 + a.z = 0.0 + a.yaw = 0.0 + a.speed = 0.0 + a.phase = 0.0 + a.fly_h = 0.0 + a.bank = 0.0 + a.state = 0 + a.timer = 0.0 + a.rg = -1 + a.home_x = 0.0 + a.home_z = 0.0 + a.tx = 0.0 + a.tz = 0.0 + a.wx = 0.0 + a.wz = 0.0 + a.way_t = 0.0 + a.crowd = false + a.crowd_id = -1 + a.goal_x = 0.0 + a.goal_z = 0.0 + a.goal_speed = 0.0 + a.thirst = 0.0 + a.hunger = 0.0 + a.scat_t = 0.0 + a.track_d = 0.0 + a.spot = 0 + a.spot_x = 0.0 + a.spot_z = 0.0 + a.spot_water = false + a.lure = 0 + a.lure_kind = 0 + a.lure_x = 0.0 + a.lure_z = 0.0 + a.lure_owner = -1 + a.calm = 0.0 + a.fed = 0 + a.alert = 0.0 + a.sus_x = 0.0 + a.sus_z = 0.0 + a.spook = 0.0 + a.seen = 0.0 +} +# its old id out of the map, and every field as new +function wl_forget(w: WildTable, a: WildAnimal) -> void { + if a.nid != 0 { imap_del(w.nids, a.nid) } + wl_blank(a) +} +# a record of its own, with the next key: only when none of its species waits to be reused +@alloc_ok("an animal record past the most of its species ever at once; a removed one is reused") +function wl_record(wildlife_st: mut WildlifeState) -> WildAnimal { + let a = new WildAnimal + wildlife_st.wl_keys += 1 + a.key = wildlife_st.wl_keys + return a +} +# a reused record's row said again from its fields: where it stands, alive, and its new id +function wl_refile_one(w: WildTable, a: WildAnimal) -> void { + let r = wl_row(a) + if r < 0 { return } + tb_set_xz(w.tab, WA_X, WA_Z, r, a.x, a.z) + wildlife_set_alive(a, true) + if a.nid != 0 { imap_put(w.nids, a.nid, a.ent) } +} diff --git a/packages/ludic.wildlife/store.ludic b/packages/ludic.wildlife/store.ludic index c6fc6e7d..105924ce 100644 --- a/packages/ludic.wildlife/store.ludic +++ b/packages/ludic.wildlife/store.ludic @@ -29,9 +29,9 @@ export function wildlife_seed(wildlife_st: WildlifeState, seed: int) -> void { r export function wildlife_make(wildlife_st: mut WildlifeState, sp: int, x: float, z: float, legend: bool, yaw: float, variant: int, scale: float, nid: int) -> WildAnimal { let s = wildlife_species(wildlife_st, sp) if s == null { return null } - let a = new WildAnimal - wildlife_st.wl_keys += 1 - a.key = wildlife_st.wl_keys + var a = wl_reusable(wildlife_st.wl, sp) + let again = a != null + if again { wl_forget(wildlife_st.wl, a) } else { a = wl_record(wildlife_st) } a.nid = nid a.sp = sp a.x = x @@ -46,7 +46,7 @@ export function wildlife_make(wildlife_st: mut WildlifeState, sp: int, x: float, a.scale = scale a.legend = legend a.state = WILD_IDLE - wl_file(wildlife_st.wl, a) + if again { wl_refile_one(wildlife_st.wl, a) } else { wl_file(wildlife_st.wl, a) } WildlifeWorld.born(a) return a } diff --git a/packages/ludic.wildlife/tests/reuse_test.ludic b/packages/ludic.wildlife/tests/reuse_test.ludic new file mode 100644 index 00000000..dbe0eef3 --- /dev/null +++ b/packages/ludic.wildlife/tests/reuse_test.ludic @@ -0,0 +1,67 @@ +# reuse_test.ludic - a newcomer of a species takes the record of one removed for good: the same key +# (and so the game's drawing), fields as new, its row living again; a legend's is never taken +import "ludic.wildlife" +import "ludic.base" +import "fake" +program WildReuseTest { + numbers float + bind WildlifeWorld { + ground: fn fk_ground + water: fn fk_water + forest: fn fk_forest + sight: fn fk_sight + push: fn fk_push + pushed_x: fn fk_pushed_x + pushed_z: fn fk_pushed_z + players: fn fk_players + player_on: fn fk_player_on + player_x: fn fk_x + player_y: fn fk_y + player_z: fn fk_z + hidden: fn fk_is_hidden + noise: fn fk_noise_of + scent: fn fk_scent_of + stillness: fn fk_still_of + wind: fn fk_wind_of + wind_dir: fn fk_wind_to + winterness: fn fk_winter_of + about: fn fk_about_of + per_range: fn fk_one + lures: fn fk_count + lure: fn fk_lure + present: fn fk_present + born: fn fk_on_born + spot: fn fk_spot + } + + + test "a removed animal's record is taken by the next of its species, as new, and nothing more is made" (wildlife_st: mut WildlifeState, wildlife_tests_fake_st: mut WildlifeTestsFakeState) { + fk_setup(wildlife_st, wildlife_tests_fake_st) + let a = wildlife_new(wildlife_st, 0, 10.0, 10.0, false) + expect(a != null) + let key = a.key + a.fed = 3 + a.spook = 50.0 + wildlife_remove(a) + let b = wildlife_new(wildlife_st, 0, 40.0, 40.0, false) + expect(b == a) + expect_eq(b.key, key) + expect(b.alive and b.shown) + expect(b.fed == 0 and b.spook == 0.0) + expect(wildlife_nearest(wildlife_st, 0, 40.0, 40.0, 5.0) == b) + if b.nid != 0 { expect(wildlife_by_nid(wildlife_st, b.nid) == b) } + for k in 0 .. 100 { + wildlife_remove(b) + expect(wildlife_new(wildlife_st, 0, float(k), 0.0, false) == b) + } + } + test "a legend's record is never taken, and another species' neither" (wildlife_st: mut WildlifeState, wildlife_tests_fake_st: mut WildlifeTestsFakeState) { + fk_setup(wildlife_st, wildlife_tests_fake_st) + let l = wildlife_new(wildlife_st, 0, 10.0, 10.0, true) + wildlife_remove(l) + expect(wildlife_new(wildlife_st, 0, 20.0, 20.0, false) != l) + let c = wildlife_new(wildlife_st, 0, 30.0, 30.0, false) + wildlife_remove(c) + expect(wildlife_new(wildlife_st, 1, 30.0, 30.0, false) != c) + } +}