--- layout: ../Site.layout.js --- # Leonardo Calculus Knowledge Representation: Completing the simulation After 1. [Creating the `organisms-kb` knowledgebase of our plant/insect/bird universe](/lispgames/plant-insect-bird-ontology) 1. and adding `thingtype`s for `organism`, `plant`, `insect`, `bird`, 1. Where `{plant, insect, bird}` are `subsumed-by` `organism` 1. [Adding attributes to `organism`s from our simulation universe](/lispgames/LCKR-fleshing-out-organisms-attributes) 1. [Writing that really big Breitenbergian sensor `lispdef` `entity`, `sense2`](/lispgames/LCKR-defining-sensors-sense-using-cl-series) We got pretty close to having a working simulation. I think the jump is similar in size and character to writing that `sense2` `lispdef`, so we should be able to mostly bridge this gap in one article here. ## Setup `Plantworld` software-individual in `demus` agent Let's run through it explicitly here. I am going to mostly ignore how the code gets sent and [the initial minutae are implicit](/lispgames/plant-insect-bird-ontology), though I am using my `eepitch-send` extension. ` (setq inferior-lisp-program "clisp -E ISO-8859-1 -modern")` ` (slime)` ` (setq eepitch-buffer-name "*slime-repl clisp*")` `(require "asdf")` `(merge-pathnames #P"demus/Process/main/" #P"~/leocommunity/Plantworld/")` `(uiop:chdir *) ; Remember * means 'last result' here.` `(merge-pathnames #P"../../../remus/Startup/cl/acleo.leos" **) ; ** meaning second-last-result` `(load *)` `(cle)` We got here: ``` cs-user> (cle) Starting or resuming interaction using the CLE command-loop **************************************************************************** ses.001) ``` ## Loading `organisms-kb` We can actually set up `organisms-kb`'s `mustload` attribute, but that's a story for another day. Manual control, then. `loadk organisms-kb` `setk organisms-kb` `. (symbol-plist 'organisms-kb)` checking what locations there were because its `mustload` is not set up yet ``` (has-phrases nil exit-proc # end-startup-proc # init-startup-proc # latest-rearchived nil attrib-converted nil archivepoint-sequence nil latest-archived-entity nil local-ents nil sections nil dont-display nil leos-use nil onto-amend nil indivinfo nil hostinfo nil overlay-own nil overlay-types nil overlay-on nil profile nil codefiles nil uses-hostcommands nil removed-entities nil mustload nil requires nil namephrase nil purpose nil preferred-directory "Organisms/" contents (seq& (organisms-kb organisms-kb-properties |(location: organisms)| |(location: plants)| |(location: insects)| |(location: birds)| |(location: sensors)| |(location: world)|)) latest-written "2025-07-13/03:04.+12" type kb-index textprops nil read-in-file organisms-kb) ``` `loadk organisms` `loadk plants` `loadk insects` `loadk birds` `. (require :series)` `loadk sensors` `loadk world` # Add `eat`, `propagate` and `senesce` `lispdef`s to `organism` `thingtype` `put eat type lispdef` `put propagate type lispdef` `put senesce type lispdef` `put organisms contents (union (get organisms contents) { eat, propagate, senesce })` `writefil organisms` # `leodef` those Remembering the path to edit lisp in is `#P"~/leocommunity/Plantworld/demus/Organisms/organisms.leo"`. ## `eat` def ``` --------------------------------------------------------- -- eat [: type lispdef] [: latest-rearchived nil] (leodef nil eat (the-organism) (let* ((prey (cadr (get the-organism 'prey-species))) (range (get the-organism 'eating-distance)) (lethality-rate (get the-organism 'lethality-rate)) (starvation-rate (get the-organism 'starvation-rate)) (world (cdadr (get 'world 'contents))) (victim (loop :with ox := (get the-organism 'x-position) :with oy := (get the-organism 'y-position) :for thing :in world :for type := (get thing 'type) :for x := (get thing 'x-position) :for y := (get thing 'y-position) :do (print `(,thing ,type in ,prey & ,(abs (- x ox)) < ,range & ,(abs (- y oy)) < ,range)) :when (and (member type prey) (> range (abs (- x ox))) (> range (abs (- y oy)))) :return thing))) (print 'eat_) (print victim) (princ '?) (cond (victim (when (< (random 100) lethality-rate) (setf (get victim 'type) nil))) (:otherwise (when (< (random 100) starvation-rate) (setf (get the-organism 'type) nil)))))) --------------------------------------------------------- ``` ## `propagate` def ``` -- propagate [: type lispdef] [: latest-rearchived nil] (leodef nil propagate (the-organism) (let ((ox (get the-organism 'x-position)) (oy (get the-organism 'y-position)) (nat (get the-organism 'natality-rate)) (dist (get the-organism 'propagation-distance)) (dir (get the-organism 'current-direction))) (when (< (random 100) nat) (let* ((theta (loop :for compass :in '(e ne n nw w sw s se) :for ang :from 0 :by (/ pi 4) :when (equal compass dir) :return ang)) (new-sym (intern (symbol-name (gensym (symbol-name (get the-organism 'type)))))) (delta-x (* dist (cos theta))) (delta-y (* dist (sin theta))) (new-x (+ ox delta-x)) (new-y (+ oy delta-y))) (setf (symbol-plist new-sym) (copy-list (symbol-plist the-organism)) (get new-sym 'x-position) new-x (get new-sym 'y-position) new-y) (nconc (cadr (get 'world 'contents)) (list new-sym)))))) --------------------------------------------------------- ``` ## `senesce` ``` -- senesce [: type lispdef] [: latest-rearchived nil] (leodef nil senesce (the-organism) (let ((mort (get the-organism 'mortality-rate))) (when (< (random 100) mort) (setf (get the-organism 'type) nil)))) ooooooooooooooooooooooooooooooooooooooooooooooooooooooooo ``` # Putting it together - other 'upper' actions And I guess introducing a bunch of new things: `ssv`, `en`, `random, `-`, `length`, `soact` .. ## Random `.target` `ssv .target (en (random (- (length (get world contents)) 1)) (get world contents))` This line says to choose a target other than the first and set-session-variable `.target` to it. ## Nonbranching sequence of actions `soact` In the special case of non-branching trees, we can use `soact` for composite actions: `soact [propagate .target] [eat .target] [senesce .target]` ## `writefil` and `loadk` the world to clean up `writefil world` `loadk world` Remembering that it's actually `loadk` that cleans up entities with `nil`led type. # Some tests that it's working. ## `sense2` smoke: a plant that turns away from other plants `put lonely-plant type plant` `put lonely-plant x-position 0` `put lonely-plant y-position 0` `put lonely-plant current-direction e` `put lonely-plant opening-angle 23` `put lonely-plant sensor-weights <>` `put lonely-plant sensor-scale 1` `put plant-a type plant` `put plant-a x-position 2` `put plant-a y-position 1` `addmember (get world contents) plant-a` `sense2 lonely-plant` After some surgeory to `sense2` - I made it an action as well as a lisp helper action, and I had accidentally written `values` instead of `list` somewhere - we got (with some verbose `print`s): ``` ses.101) sense2 lonely-plant plant-a (ene 0 2 <= 2 < 3 & 1 <= 1 < 2) "success" (seq& (plant -1)) ((ene -1 2 3 1 2) (nne 0 1 2 2 3) (nnw 0 -1 0 2 3) (sse 0 1 2 -2 -1) (ssw 0 -1 0 -2 -1) (ene 0 2 3 1 2) (ese 0 2 3 -1 0) (wnw 0 -2 -1 1 2) (wsw 0 -2 -1 -1 0)) ses.102) (get lonely-plant current-direction) => n ``` What happened, I think is- 1. `plant-a` is in the sensor area. 1. specifically, `plant-a` is found to be `ene` 1. since `plant` has a weight of `-1`, `e` and `ne` are weighted `-1` 1. Counterclockwise from `e`, `n` is the first 8-compass direction with highest-equal sensor score. Hypothetically, if we add a `plant-b` at `nnw` <-1, 2> `put plant-b type plant` `put plant-b x-position -1` `put plant-b y-position 2` `addmember (get world contents) plant-b` and make `lonely-plant` aware of both plants with 360° vision, `put lonely-plant opening-angle 180` I believe we will turn w instead. `sense2 lonely-plant` `(get lonely-plant current-direction)` ``` ses.114) sense2 lonely-plant plant-a (ene 0 2 <= 2 < 3 & 1 <= 1 < 2) "success" (seq& (plant -1)) ((ene -1 2 3 1 2) (nne 0 1 2 2 3) (nnw 0 -1 0 2 3) (sse 0 1 2 -2 -1) (ssw 0 -1 0 -2 -1) (ene 0 2 3 1 2) (ese 0 2 3 -1 0) (wnw 0 -2 -1 1 2) (wsw 0 -2 -1 -1 0)) plant-b (ene -1 2 <= -1 < 3 & 1 <= 2 < 2) (nne 0 1 <= -1 < 2 & 2 <= 2 < 3) (nnw 0 -1 <= -1 < 0 & 2 <= 2 < 3) "success" (seq& (plant -1)) ((ene -1 2 3 1 2) (nne 0 1 2 2 3) (nnw -1 -1 0 2 3) (sse 0 1 2 -2 -1) (ssw 0 -1 0 -2 -1) (ene 0 2 3 1 2) (ese 0 2 3 -1 0) (wnw 0 -2 -1 1 2) (wsw 0 -2 -1 -1 0)) ses.115) (get lonely-plant current-direction) => w ``` Do the southern directions work at all? `put plant-c type plant` `put plant-c x-position -2` `put plant-c y-position -1` `addmember (get world contents) plant-c` `(get lonely-plant current-direction)` `sense2 lonely-plant` ``` ses.120) (get lonely-plant current-direction) => w ses.121) sense2 lonely-plant plant-a (ene 0 2 <= 2 < 3 & 1 <= 1 < 2) "success" (seq& (plant -1)) ((ene -1 2 3 1 2) (nne 0 1 2 2 3) (nnw 0 -1 0 2 3) (sse 0 1 2 -2 -1) (ssw 0 -1 0 -2 -1) (ene 0 2 3 1 2) (ese 0 2 3 -1 0) (wnw 0 -2 -1 1 2) (wsw 0 -2 -1 -1 0)) plant-b (ene -1 2 <= -1 < 3 & 1 <= 2 < 2) (nne 0 1 <= -1 < 2 & 2 <= 2 < 3) (nnw 0 -1 <= -1 < 0 & 2 <= 2 < 3) "success" (seq& (plant -1)) ((ene -1 2 3 1 2) (nne 0 1 2 2 3) (nnw -1 -1 0 2 3) (sse 0 1 2 -2 -1) (ssw 0 -1 0 -2 -1) (ene 0 2 3 1 2) (ese 0 2 3 -1 0) (wnw 0 -2 -1 1 2) (wsw 0 -2 -1 -1 0)) plant-c (ene -1 2 <= -2 < 3 & 1 <= -1 < 2) (nne 0 1 <= -2 < 2 & 2 <= -1 < 3) (nnw -1 -1 <= -2 < 0 & 2 <= -1 < 3) (sse 0 1 <= -2 < 2 & -2 <= -1 < -1) (ssw 0 -1 <= -2 < 0 & -2 <= -1 < -1) (ene 0 2 <= -2 < 3 & 1 <= -1 < 2) (ese 0 2 <= -2 < 3 & -1 <= -1 < 0) (wnw 0 -2 <= -2 < -1 & 1 <= -1 < 2) (wsw 0 -2 <= -2 < -1 & -1 <= -1 < 0) "success" (seq& (plant -1)) ((ene -1 2 3 1 2) (nne 0 1 2 2 3) (nnw -1 -1 0 2 3) (sse 0 1 2 -2 -1) (ssw 0 -1 0 -2 -1) (ene 0 2 3 1 2) (ese 0 2 3 -1 0) (wnw 0 -2 -1 1 2) (wsw -1 -2 -1 -1 0)) ses.122) (get lonely-plant current-direction) => s ``` Let's try and turn `se` just for fun by adding a plant `ssw`. `put plant-d type plant` `put plant-d x-position -1` `put plant-d y-position -2` `addmember (get world contents) plant-d` `sense2 lonely-plant` `(get lonely-plant current-direction)` ``` ses.130) (get lonely-plant current-direction) => se ``` This makes me somewhat confident in `sense2`. # Can `lonely-plant` `propagate` ? `put lonely-plant propagation-distance 4` `put lonely-plant natality-rate 100` `propagate lonely-plant` `(get world contents)` the answer was yes: ``` ses.189) (get world contents) => ``` ## Can `hungry-bug` `eat` a `plant` ? `put hungry-bug type insect` `put hungry-bug prey-species {plant}` `put hungry-bug eating-distance 5` `put hungry-bug lethality-rate 100` `put hungry-bug starvation-rate 100` `put hungry-bug natality-rate 100` `put hungry-bug mortality-rate 0` `put hungry-bug x-position -6` `put hungry-bug y-position -1` `put hungry-bug sensor-weights < >` `put hungry-bug opening-angle 90` `put hungry-bug current-direction e` `put hungry-bug propagation-distance 3` `put hungry-bug sensor-scale 2` `addmember (get world contents) hungry-bug` `(get world contents)` `sense2 hungry-bug` anyway, it was working. ## `senesce` `put parrot type bird` `put parrot mortality-rate 100` `senesce parrot` `(get parrot type)` ``` ses.272) put parrot type bird put: parrot type bird ses.273) put parrot mortality-rate 100 put: parrot mortality-rate 100 ses.274) senesce parrot ses.275) (get parrot type) => nil ses.276) ``` # Conclusion Well, that was a bit exhausting, but everything got working eventually, and I fixed the `values`-`list` mistake in `sense2` from before. So we achieved a slightly-too-verbose simulation. I dunno about you but I am too exhausted right now to conclude anything else. I'm leaving exploring the implications of the simulation for the future. # Fin. [Talk on the Mastodon as always please](https://gamerplus.org/@screwlisp/114867494240159731).