---
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).