screwlisp proposes kittens

Leonardo Calculus Knowledge Representation: Completing the simulation

After

  1. Creating the organisms-kb knowledgebase of our plant/insect/bird universe
  2. and adding thingtypes for organism, plant, insect, bird,
  3. Where {plant, insect, bird} are subsumed-by organism
  4. Adding attributes to organisms from our simulation universe
  5. Writing that really big Breitenbergian sensor lispdef entity, sense2

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, 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 lispdefs 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 nilled 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 prints):

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.
  2. specifically, plant-a is found to be ene
  3. since plant has a weight of -1, e and ne are weighted -1
  4. 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.

screwlisp proposes kittens