• se-lang: a 3-in-1 language to abstract SpaceEngine Modding

    After the reproduction of Sean Raymond's Ultimate Engineered Solar System, others works of the same author were considered, notably his though about Isaac Asimov's Kalgash system. The problem is: i don't want to write a python script for each problem.

    So, let's dive into a shiny new project, se-lang, an ASP or JSON-based DSL to describe a system in SpaceEngine, with the associated python model and compiler.

    The need

    It's quite simple: i want to reproduce the four Kalgash systems described by Sean Raymond. Also, i want to be able to encode past (and future !) works, notably the Ultimate Engineered Solar System.

    Few necessary features:

    • define objects to put into the system, their childs, for any nested level (e.g. a moon of a moon of a moon of a giant of a star of a black hole)
    • define meta-objects, like rings of objects, and their prograde direction
    • compilation toward SpaceEngine's modding language.
    • language extension: i want to be able to add easily new terms and routines, to simplify my life (e.g. shortcuts to earth, blue giants and black holes).

    The Kalgash 2 system, with a ring of suns orbiting a black hole, and a red dwarf between them. The Kalgash planet is orbiting the red dwarf

    Internal model

    The model is basically the way the data is represented in the core of selang (in fact, there is some postprocessing that is done, notably for special objects like rings). It's quite simple, and form the base of the API. Follows an example that reproduce a sun-earth-moon system.

    from selang import ref, orbit, Model, model_to_se
    
    model = Model(
        'My little system',
        {  # structure of the system
            'sun': (1, orbit(1, eccentricity=0.04)),
            1: ('moon', orbit(0.05)),
        },
        {  # non explicit objects
            1: ref('earth', earth_mass=1.2),  # super earth
        }
    )
    
    model_to_se(model, '~/games/SpaceEngine/')
    

    Note that this code, once ran (you need to install selang first), will push into the given directory the SpaceEngine scripts necessary to see the system appears in-game.

    The internal model interface is not the only way to achieve our goals. Two other languages can also be targeted.

    ASP-based language

    Answer Set Programming

    I will not talk too much about it. It's a logic programming language, like prolog, but even more logical. We will not use the full capabilities of the language ; in fact, we will stick to a lisp-like syntax, ignoring much of the language interest and features.

    If you are familiar with ASP, you will probably get the interest of using it.

    Kalgash systems encoding — a first take

    The kalgash system 4 is quite straightforward:

    • a black hole, orbited by, in order
    • a ring of 8 suns (1 AU)
    • a planet (2.5 AU)
    • a ring of 8 suns (5 AU)

    A simple way to explain that would be:

    system(black_hole, "Kalgash")
    orbit(black_hole, ring(8, sun), 1).
    orbit(black_hole, earch, "2.5").
    orbit(black_hole, ring(8, sun), 5).
    

    The ASP to SE compiler

    I code in python the CLI around the compiler and the compiler itself. The compiler, if told so, will put the wanted systems into SpaceEngine installation directory as expected by the software.

    In the end, all you have to do is to write the system encoding, run the compiler, and launch SpaceEngine.

    Everything is explained in the README.

    JSON-based language

    A more standard language is JSON. It can also be used to implement what we need. Bonus: no external ASP solver needed, python's stdlib contains an (unforgiving) parser for JSON. However, JSON is just a text format, so you have to say goodbye to all ASP capabilities regarding variables, answer set and logical relations.

    The JSON to SE compiler

    The JSON compiler is not really different from the ASP one… just two functions, to extract the data, really changes.

    Kalgash systems encoding — with JSON

    The Kalgash 4 system is quite easy to define.

    {
        "name": "Kalgash",
        "UID": "UID42",
        "type": "black hole",
        "childs": [
            {
                "type": ["ring", 8, "sun"],
                "retrograde": true,
                "distance": 1,
            },
            {
                "type": "earth",
                "distance": 2.5
            },
            {
                "type": ["ring", 8, "sun"],
                "distance": 5
            }
        ]
    }
    

    As you can see, JSON is much more verbose and structured than ASP.

    There is a feature of JSON-based language that is not available for the ASP-based one: If the root object of JSON file is a list, each dict element of that list is understood as a single system. It is possible to emulate that in ASP, but it quickly become cumbersome for really different systems. However, the ASP way would allow one to define a squeletton system, and the differences among the multiple instances.

    The 5 Kalgash systems

    All encodings are available on the github repository in both ASP and JSON formats.

    Kalgash 2

    In ASP:

    system(black_hole,"Kalgash2").
    orbit(black_hole,ring(sun,8),20).
    orbit(black_hole,orbit(red_dwarf,earth,"0.2"),10).
    

    In JSON:

    {
        "name": "Kalgash 2",
        "UID": "Kalgash",
        "type": "black hole",
        "childs": [
            {
                    "type": "red dwarf",
                    "distance": 10,
                    "child": {
                        "type": "earth",
                        "distance": 0.2
                    }
            },
            {
                    "type": ["ring", 8, "sun"],
                    "distance": 20
            }
        ]
    }
    

    Kalgash 3

    In ASP:

    system(black_hole,"Kalgash3").
    orbit(black_hole,ring(sun,12,orbit(1,earth,1)),20).
    

    In JSON:

    {
        "name": "Kalgash 3",
        "UID": "Kalgash",
        "type": "black hole",
        "childs": {
                "type": ["ring", 12, "sun"],
                "distance": 20,
                "childof": {
                    "1": {
                        "type": "earth",
                        "distance": 1
                    }
                }
            }
    }
    

    Kalgash 4

    In ASP:

    system(black_hole,"Kalgash4").
    orbit(black_hole,ring(sun,8),1,retrograde).
    orbit(black_hole,earth,"2.5").
    orbit(black_hole,ring(sun,8),"4").
    

    In JSON:

    {
        "name": "Kalgash 4",
        "UID": "Kalgash",
        "type": "black hole",
        "childs": [
            {
                "type": ["ring", 8, "sun"],
                "retrograde": true,
                "distance": 1
            },
            {
                "type": "earth",
                "distance": 2.5
            },
            {
                "type": ["ring", 8, "sun"],
                "distance": 5
            }
        ]
    }
    

    The Kalgash 4 system, with two rings of suns orbiting a black hole, and the Kalgash planet between them.

    Kalgash 5

    In ASP:

    system(black_hole,"Kalgash5").
    orbit(black_hole,ring(sun,8),1,retrograde).
    orbit(black_hole,earth,"2.5").
    orbit(black_hole,ring(blue_giant,8),20).
    

    In JSON:

    {
        "name": "Kalgash 5",
        "UID": "Kalgash",
        "type": "black hole",
        "childs": [
            {
                "type": ["ring", 8, "sun"],
                "retrograde": true,
                "distance": 1,
                "childof": {
                    "1": {
                        "type": "earth",
                        "distance": 0.2
                    }
                }
            },
            {
                "type": "earth",
                "distance": 2.5
            },
            {
                "type": ["ring", 8, "blue_giant"],
                "distance": 20
            }
        ]
    }
    

    The Kalgash 5 system, as seen from the ground, with stars raising above a volcano.

    The Ultimate Engineered Solar System

    In its simplest form, UESS is made of 6 rings of 42 Earth-like. Let 0.02 AU be the distance between them:

    system(sun,"UESS","UESS").
    value(1,r;"1.02",p;"1.04",r;"1.06",p;"1.08",r;"1.10",p).
    shortcut(r,retrograde).
    shortcut(p,prograde).
    orbit(sun,ring(earth,42),D,P):- value(D,S) ; shortcut(S,P).
    

    ASP interest here is to allow some kind of automation: in JSON, we would have to write all relations. Note however that ASP do not handle well the floats values, nor ordering, and consequently the dumb enumeration of distances is necessary… unless you put some python in your ASP. Which is a feature of the Potassco ASP implementation.

    #script(python)
    import itertools
    def values():
        return [
            (str(value / 100), 'retrograde' if retro else 'prograde')
            for value, retro in zip(range(100, 112, 2), itertools.cycle((True, False)))
        ]
    #end.
    values(D,P):- (D,P)=@values().
    

    (i have to recognize: this is typically a bad use-case of both ASP and python, since python do not have a simple range for floats and ASP do not handle them… I could have cheated a little by choosing a ring gap of 1 AU to hide this corner cases)

    Finally, for this precise case, using the API is much more easier, and enables much more trivial extensions:

    import itertools
    from selang import ref, orbit, ring, model, model_to_se, as_model
    
    def orbits():
        return (
            (str(value / 100), retro, 42, 'earth')
            for value, retro in zip(range(100, 112, 2), itertools.cycle((True, False)))
        )
    
    hierarchy = (
        (1, ring(nb_earth, earth_type), orbit(dist, retrograde=retro))
        for dist, retro, nb_earth, earth_type in orbits()
    )
    objects = {
        1: 'sun'
    }
    
    
    model = as_model('The Ultimate Engineered Solar System', hierarchy, objects)
    model_to_se(model, '~/games/space_engine/SpaceEngine/')
    

    Conclusions

    For Kalgash

    The Kalgash system under SpaceEngine is a mitigated success. I didn't show it much, but unfortunately the lighting is buggy in that kind of system: the rings do not do their job when too far away, even those made of giant blue. It's sometimes ridiculous, because when on ground, you see directly 3 stars, each of them being more shiny than the sun alone, but the direct environment is plunged in the dark. Consequence is: we got some night, on every system. But to me it clearly seems like a SpaceEngine-related bug rather than anything else.

    The good news is: the next version of SpaceEngine, 0.9.9.0, will change that. At least, we can hope so, since the complete lighting system will be rewritten.

    For SE

    Concerning se-lang by itself, it's a great success: i was able to modelize of lot of different systems, and i end up with a complete 3-languages support for SpaceEngine system generation, each of the languages having its interests and downsides.

    Concerning the ASP-based language: ASP is not an ideal language for DSLs, for various reasons, but it's fun (at least for me) and powerful (at least some times). There is however some drawbacks that makes it not suitable for a systematic use. If you are interested into ASP, dive into this puzzles solving (or go to my (french) tutorial). If you are interested into ASP for DSLs, look up one of my last project, biseau, which is using ASP as a DSL to draw graphs (that's very cool).

    Concerning the JSON-based language, it's much more verbose than ASP, but is also much more standard and easy to setup (you just need the python standard library). It is also more readable.

    Finally, the best language for complex jobs is probably using the API that enable the model writing in Python, but for most tasks, ASP and JSON are enough.

    For future work, see the README. There is a consequent amount of work to be done. I will do it, one step at a time.

    In the end, we have now three ways to express complex systems in Space Engine. Let's start the modding party !