screwlisp proposes kittens

Spacetime boxes NicCLIM: an ordeal

All the writing is in the conclusion, which you are welcome to skip down to.

Long story short, I accidentally overshot making discrete 4D spacetime for lispgamedev to 6D, managed to make a simple 2D slice of it where the spatially 26connected wall around was all literally the same wall, which created a number of problems. I guess it was working in the end. Read the conclusion, not up here.

I’ll push the current NicCLIM to itch.io https://lispy-gopher-show.itch.io/nicclim ; I guess this article is your reference on engaging. version 2.5 is deeeeply pre-alpha.

My (emacs eev) setup

#|
• (setq inferior-lisp-program "ecl")
• (slime)
• (setq eepitch-buffer-name "*slime-repl ECL*")
|#
(ql:quickload :mcclim)
(ensure-directories-exist "~/GAME/BOX/")
(uiop:chdir "~/GAME/BOX")
(uiop:chdir "~/GAME/BOX") ; I always have to do this twice.
(compile-file "~/Downloads/nicclim.lisp" :load t)
(in-package :nic)

A spacetime-box in which no time passes

(defparameter *box* (make-instance 'spacetime-box))

(rect-file "FOO" 5 4 '(foo))

(setf (slot-value *box* 'dims) '(5 4 1)

      (slot-value *box* 'times.contentses)
      '((0 . (foo)))
      
      (slot-value *box* 'inward-facets)
      `((
	 ((0 4) (0 3) (0 0))
	 (0 0 0)
	 ,*box*)
	))

then,

(get-source-boxes *box* 0 0)
(alexandria:hash-table-alist *)

gets us

NIC> (get-source-boxes *box* 0 0)
#<hash-table 0x7f3234be73f0>
NIC> (alexandria:hash-table-alist *)
((#<a NICCLIM:SPACETIME-BOX 0x7f3237ca2200>
  ((4 3 0 0) ((0 4) (0 3) (0 0)) (0 0 0)
   #<a NICCLIM:SPACETIME-BOX 0x7f3237ca2200>)
      ((4 2 0 0) ((0 4) (0 3) (0 0)) (0 0 0)
		 ... )))

Admittedly, this creates one hash-table specifying where to source every x-y-z-t point separately. The idea is that this is not normally in memory, and only one of them will be made (and can then be garbage collected). The reason that each x-y-z-t is identified separately is that any particular optimization involves lots of special handling and is simply not going to work for a lot of the x-y-z-t points anyway. (I think).

(get-source-boxes *box* 0 0)
(boxes-to-mapnames *)
(by-mapnames-to-pieces *)

in this step, the hash-table was converted to tagged opportunistically continuous sequences and so looks sane again. Since we don’t deal with neighbors, i.e. this spacetime-box cannot advance time at all, we are basically done here.

(((0 0 0) ((FOO) (FOO) (FOO) (FOO) (FOO)))
 ((0 1 0) ((FOO) (FOO) (FOO) (FOO) (FOO)))
 ((0 2 0) ((FOO) (FOO) (FOO) (FOO) (FOO)))
 ((0 3 0) ((FOO) (FOO) (FOO) (FOO) (FOO))))

Recapitulating

  1. We make an instance of spacetime-box, a class having those slots I guess.
  2. We use nic:rect-file to make a 5×4 tab separated values file named "FOO" here, each point starting as (FOO).
  3. We explicitly setf *box*'s times.contentses, dims and inward-facets slots.

dims reflects the 5×4×1 dimensions of the single FOO Z-level X-Y discrete rectangle area we made.

spacetime-box’ times.contentses uses an association list for time, where each time is a non-negative integer dotted with symbols denoting the files being the Z levels - depth dimension - of the spacetime-box at that time step. *box* only has a single Z level, so it only has one item in the list, FOO, identifying the nic:rect-file a moment ago.

inward-facets is a list of list records like (3d-box-limits offsets spacetime-box). The point is any x-y-z spatial value will find the box it fits into. Somewhat confusingly, it makes sense for the box’s own contents to be listed as a neighbor here so it does not have to be treated separately. Anyway, our *box* only knows where its own contents go (at time 0).

One solid barrier outside a box

It occured to me that if we make a single large spacetime-box, we could then list the same box as every neighbor in order to create an enclosure, further, this could be done with only six neighbors, since the outside spacetime-box is comparatively large.

This did end up causing me some problems, because there ended up being multiple offsets into one file which there was no clear way to choose between. Since this is not a normal problem, I introduced jiggle to allow one to correct the x and y offsets, basically in this one specific scenario by just specificying what it is that you did (i.e. you indicate which offset the true offset was). Normally one file is not accessed by one neighbor from incongruous directions.

(defparameter *outer-box*
  (make-instance 'spacetime-box))

;; (rect-file "BAR" 7 6 '(bar)) ; commented out so I don't accidentally make it again

(setf (slot-value *outer-box* 'times.contentses)
      '((0 . (bar))))

(defparameter *inner-box*
  (make-instance 'spacetime-box))

;; (rect-file "FOO" 5 4 '(foo))

(setf (slot-value *inner-box* 'dims) '(5 4 1)

      (slot-value *inner-box* 'times.contentses)
      '((0 . (foo)))
      )

since *outer-box* is 2 units bigger than *inner-box*, we can fully enclose *inner-box* in the 26-connected sense using just six sides (that are larger than *inner-box*'s sides).

Sorry for the large setf ; we just have to say what each of the six bordering boxes are (just six even though we are 26-connected because the box is big).

(setf 
 (slot-value *inner-box* 'inward-facets)
 `(
   ;; inner-box contains its own contents.
   (
    ((0 4) (0 3) (0 0))
    
    ;; with no offsets (obviously)
    (0 0 0)

    ;; It's in *inner-box*.
    ,*inner-box*)
    
   ;; Top and bottom neighbors
   (
    ;;one-space overhanging ceiling.
    ((-1 5) (-1 4) (1 1))
    ;;offets to get it in terms of *outer-box*'s contents
    (+1 +1 -1)
    ;;this is *outer-box*
    ,*outer-box*
    )
   (
    ;;one-space overhanging flooring.
    ((-1 5) (-1 4) (-1 -1))
    ;;offets to get it in terms of *outer-box*'s contents
    (+1 +1 +1)
    ;;this is *outer-box*
    ,*outer-box*
    )
    
   ;; Sides.
   (
    ;;one-space overhanging walls same z level
    ((-1 -1) (-1 4) (0 0))
    ;;offets to get it in terms of *outer-box*'s contents
    (+1 +1 0)
    ;;this is *outer-box*
    ,*outer-box*
    )
   (
    ;;one-space overhanging walls same z level
    ((5 5) (-1 4) (0 0))
    ;;offets to get it in terms of *outer-box*'s contents
    (+1 +1 0)
    ;;this is *outer-box*
    ,*outer-box*
    )
    
   ;;Remaining walls
   (
    ;;one-space overhanging walls same z level
    ((0 4) (-1 -1) (0 0))
    ;;offets to get it in terms of *outer-box*'s contents
    (+1 +1 0)
    ;;this is *outer-box*
    ,*outer-box*
    )
   (
    ;;one-space overhanging walls same z level
    ((0 4) (4 4) (0 0))
    ;;offets to get it in terms of *outer-box*'s contents
    (+1 +1 0)
    ;;this is *outer-box*
    ,*outer-box*
    )
   ))

and

(get-source-boxes *inner-box* 0 0)
(boxes-to-mapnames *)
(by-mapnames-to-pieces *)

oops, we took 0 units from neighbors.

(((0 0 0) ((FOO) (FOO) (FOO) (FOO) (FOO)))
 ((0 1 0) ((FOO) (FOO) (FOO) (FOO) (FOO)))
 ((0 2 0) ((FOO) (FOO) (FOO) (FOO) (FOO)))
 ((0 3 0) ((FOO) (FOO) (FOO) (FOO) (FOO))))

Okay let’s try gather enough for 1 time step.

(get-source-boxes *inner-box* 0 1)
(boxes-to-mapnames *)
(by-mapnames-to-pieces *)
(jiggle * 'foo 1 1) ;; Allowed me to break-the-rules ;_;
(defparameter *j* *)
(really-sort *j*)
(defparameter *k* *)
(split-by-level *k*)

Okay!

NIC> (split-by-level *k*)
((((0 0 -1) ((BAR) (BAR) (BAR) (BAR) (BAR) (BAR) (BAR)))
  ((0 1 -1) ((BAR) (BAR) (BAR) (BAR) (BAR) (BAR) (BAR)))
  ((0 2 -1) ((BAR) (BAR) (BAR) (BAR) (BAR) (BAR) (BAR)))
  ((0 3 -1) ((BAR) (BAR) (BAR) (BAR) (BAR) (BAR) (BAR)))
  ((0 4 -1) ((BAR) (BAR) (BAR) (BAR) (BAR) (BAR) (BAR)))
  ((0 5 -1) ((BAR) (BAR) (BAR) (BAR) (BAR) (BAR) (BAR))))
 (((0 0 0) ((BAR) (BAR) (BAR) (BAR) (BAR) (BAR) (BAR)))
  ((0 1 0) ((BAR) (FOO) (FOO) (FOO) (FOO) (FOO) (BAR)))
  ((0 2 0) ((BAR) (FOO) (FOO) (FOO) (FOO) (FOO) (BAR)))
  ((0 3 0) ((BAR) (FOO) (FOO) (FOO) (FOO) (FOO) (BAR)))
  ((0 4 0) ((BAR) (FOO) (FOO) (FOO) (FOO) (FOO) (BAR)))
  ((0 5 0) ((BAR) (BAR) (BAR) (BAR) (BAR) (BAR) (BAR))))
 (((0 0 1) ((BAR) (BAR) (BAR) (BAR) (BAR) (BAR) (BAR)))
  ((0 1 1) ((BAR) (BAR) (BAR) (BAR) (BAR) (BAR) (BAR)))
  ((0 2 1) ((BAR) (BAR) (BAR) (BAR) (BAR) (BAR) (BAR)))
  ((0 3 1) ((BAR) (BAR) (BAR) (BAR) (BAR) (BAR) (BAR)))
  ((0 4 1) ((BAR) (BAR) (BAR) (BAR) (BAR) (BAR) (BAR)))
  ((0 5 1) ((BAR) (BAR) (BAR) (BAR) (BAR) (BAR) (BAR)))))
NIC> 

Conclusions

I somewhat regret making two blocks, and putting the second block above, below, left, right, front, back and on all the diagonals of the first block, the unreality of which required regrets like jiggle to manually specify what the correct offset actually was out of the many possibilities. I guess I could have remained principled, and just bitten the bullet and made six different outside spacetime-boxes for each direction.

Also while this indicates my spacetime-boxes are working at least sometimes, this example just recreates a 5×4 rectangular grid (in 4D). Actually, it is in six dimensions rather than four, but one of the dimensions is a small filepath dimension.

The reason it’s six dimensional is that NicCLIM’s spacetime-box mechanism is like this:

The boxes making a universe, I guess, in the lisp image all the time are time × z-level × symbol facets of the underlying space. The symbol denotes a data file which is a tab separated s-expression file construed to be x × y × s-expressions more familiar from NicCLIM. However the symbol could carry other information so I am counting it as a dimension. The time dimension is jagged and intended to stay small, i.e. we are only interested in keeping the uppermost crust of time. However since one neighbor will update before another, both the latest and second-latest times at least must be available.

If you are a long-suffering gamedev (whom I believe to be equivalent to scientists) whom has made it to here, something multiple relatively largescale gamedevs have said to me is that gamedev tools really leave you in the lurch once you leave the second dimension. Whereas mine naturally has three spatial dimensions, time, file storage (!) and an s-expression which could be used, you know, for data and/or code.

In case you missed it, my spacetime-boxes are an homage to Ken Olum’s spacetime volumes used recently in Tufts astrophysics’ cosmic string simulations from the recent episode https://communitymedia.video/w/9kysH4ZwVuP4J4erZozqFT . I wrote how I thought this would work here as a way of getting my head around it; I will try and at least directly adopt Olum’s common lisp supercomputing scheduler later. “The point” of Ken’s stuff is that you can run pieces of a large simulation that information could not possibly have propagated between yet independently, up to the point where information from one could have reached the other. Ken pioneered this in 4D spacetime, it previously having been done in 3D (spatial 2D with time). You can kind of see how mine wants to be like that too, but discrete, and not Ken’s well-documented nonportable steel bank common lisp.

Once you are operating in a spacetime volume, multiplayer is at least kind of already solved, and networking and threading are somewhat specified by using the Common lisp interface manager 2 spec implementation McCLIM.

One oddity viz common federation (and the web at large, I guess) is that the commitment to spacetime creates a very strict notion of adjacency. And you are not meant to go backwards through time (it is anticipated to be discarded). Whereas the mastodon and the web are promiscuously hyperconnected because there is little practical meaning given to physical space, and the time is always now and always always-online.

As to where to go next, I guess I should evolve some artificial life in my excruciatingly hewn out 2D slice of discrete spacetime and reconnect with Ksaj and people over here https://gamerplus.org/@ksaj@infosec.exchange/115186064876865394 (Maybe Dave Ackley?). I noticed that that article refers to Tuft’s physics department as well, which made me wonder if this all cleans up nice enough whether we could come full circle back to Olum’s physics work.

Actually 4d bits of 4space instead of my slices and constants. Using NicCLIM in this in NicCLIM (https://lispy-gopher-show.itch.io/nicclim). I think it is going to be pretty crazy.

A less frazzled article than this one.

Fin.

See you on the Mastodon. I guess I will actually peertube live in about 45 minutes and just weep quietly after some of the ordeals of trying to bang out this code. https://toobnix.org/c/screwtape_channel/videos (though it will go to https://communitymedia.video/c/screwtape_channel/videos after).

screwlisp proposes kittens