---
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 #<function :lambda nil nil> end-startup-proc
#<function :lambda nil nil> init-startup-proc #<function :lambda nil nil>
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 <<plant -1>>`
`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)
=> <world plant-a plant-b plant-c plant-d PLANT40431 PLANT40547 PLANT40589 PLANT40636>
```
## 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 <<insect -1> <plant 1> <bird -2>>`
`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).