Kailye logo


Trajectoires Kailyé,
seconde partie

Dernière mise à jour: 31 Decembre 2009 


9 - Other Kailye functions related to trajectories

9.1 - Trajectory requests and errors

All the previous syntaxes were for the case where a simulator is telling another (or the viewer) what is the planned trajectory of the object. However we can also have several requests, warnings or errors for each of the above trajectory types.

Sending a Ktrajectory=... with in the format line a data field containing ,request,... (without ~), is an invite for the receiver to send a trajectory with values for this field. Of course there is not a table column for these fields (this is an exception to the ~ rule, as a field containing ,request,... can really be understood as a data field, but empty). Eventually the request field can contain paddings and formats, indicating that the reply must comply to them, for instance: ,request.100c,... but in never contains data.

If we want to transmit data anyway, then we need to use ,suggest.paddingformat,... in place of ,request,.... This time, the data fields contain values, but this syntax tell that these values are a suggestion, asking some kind of approval from the receiver. In this case paddings and formats are mandatory, as data is sent.

The receiver of a ,suggest.paddingformat,... trajectory may reply using a ?Ktrajectory=... with in the format line the relevant data fields containing ,ok, as an mark of agreement. In this case, no data and paddings are present, and no data is sent.

The receiver may on the contrary reply with just ,disagree,... to tell that the state of the simulation does not allow with the proposed trajectory. In this case, no padding or format are used, and no data is sent. We can make provision of a mean to tell a cause of refusal, such as: ,disagree.errorCode,...).

If the receiver proposes another trajectory instead, the fields will contain ,instead.paddingformat,... with an alternate data into the fields. Paddings and formats are mandatory here, but they may be others than suggested in the first place.


Even without preliminary request or suggestion, any data fields may also contain ,unsupported, to tell that the sender does not support one of the fields functions (the fields starting with ~, such as ~NURB, etc.). Such a data field is empty.

If a calculus attempt was made, but an error resulted, the data fields may contain instead: ,error.errorCode,.... Such a data field is empty.


This error reporting method is different of the Kerror=place1,code1;place2code2;... which is intended to report errors in the Kailye message itself, its transmission or its structure. Placing error.errorCode in a data field of the format line allows to report errors which are specific to the trajectories and their interpretation or rendering, data field per data field. Such kind of errors, when not simply being mistakes in software writing, will most frequently point at simulators which implement functions which are not implemented by others or by the WEM browser. Causes of this situation may lie into a wild but legitimate attempt to create a new function, into a bad implementation, or into a deliberate attempt to jeopardize the standard or corrupt the system. From this the need for the WEM browser and volunteer simulators to send these error reports to a centralized WEM management system, together with the messages which triggered them.


9.2 - Inflexion point request: the Kinflex command

The main Kailye command Kinflex=request (in the Command Field) tells that the purpose of the message is to warn that the current trajectory is not valid beyond a certain point called an inflexion point, due to changed conditions into the simulated world. The most common case is an impeding collision, but also scripts may act unexpectedly on the inflected object. The Kinflex command results of the normal running of the simulation, it is not an error warning.

In a Kinflex message, the first object in the resource list of the Kailye message is the inflected object. Other objects may be the inflecting objects, which are the direct cause of an impeding collision. For instance the first object is a character, and the others are walls that the character is encountering, or other characters in a collision course. In this case the host simulator of the character needs to know them to compute a new trajectory. But the inflexion cause can be different, with no inflecting objects, such as an action of a script. In this case there are no inflecting objects In the resource list.

This situation assumes that the inflected object is monitored by the monitor simulator, for instance a landscape. Let us call here this simulator the landscape simulator. The inflecting objects are hosted, handled or monitored by the same landscape simulator. The inflected object will generally be hosted by another simulator, for instance a character hosted by a Character Hosting Company. Let us call the later the character simulator. These highlighted names will not fit to all the situations, but they convey the idea of something wanting to follow a trajectory in a context, while this context disturbs this trajectory according to obstacles or script actions.

-The character simulator will propose a trajectory, according to the previous syntaxes. However the character simulator is blind, as it does not know the landscape itself.

-The landscape simulator which receives this trajectory, may detect a collision of the character with an object of its own (or some other action, such as a script). In this situation, it will send a message to the character simulator, with a Kinflex=request command. This message contains the character as the first resource field (and eventually the inflecting objects, at need with their trajectories, as this will often be required for the character simulator to compute a new trajectory).


But the inflected object will be accompanied with an inflexion key request. This request is a trajectory containing only the format line, and using the request, , suggest, etc. syntax for trajectory requests:


But most often the landscape simulator may go forward and extrapolate a new position, rotation, etc. value for the next keys. This may help the character simulator to compute a new trajectory. In this case, it sends this proposed trajectory as:



Now the character simulator can compute a new trajectory of its own, and send it in a reply message with a Kinflex=new command. The syntax is as with any other trajectory, just one Resource Block is needed for the character's URI, with of course the trajectory's format line and values.


Usually the character simulator will accept the proposed trajectory, and things should normally stop here.


If errors occur here, then the other syntaxes of the trajectory requests can be used to deal with them.


But the character simulator may send a new trajectory which disagrees with the one proposed by the landscape simulator, for reasons proper to the normal running of the character, and which do not result of an error condition. In this situation, the character simulator sends a new message with the command:

Kinflex=monitoring-denied with the new trajectory. Same syntax as with Kinflex=new.

Of course this is for some specific situations. To keep with the above example, it would happen if, in a game, the character has a magical power to go through walls (ignore walls bounding boxes), that the landscape simulator does not know. This is the way to tell the landscape simulator of this magical power. But the landscape simulator may know that the character is entering into a private or unauthorized area, so in this case the Kinflex=monitoring-denied message is ignored, and the landscape simulator has the last word with a Kinflex=monitoring-enforced message which sends the character on an avoidance trajectory.

Other simulators or the viewer, which are not hosting the character, will just blindly execute any definitive kinflex trajectory, without any dialogue (unless they have to report some error condition).


9.3 – Access and privacy management.

In facts, managing access to private or restricted areas is done with the Kmetaconn command. It must be done preventively if the character is on a trajectory approaching a boundary. The default value of any zone is no access, until an authorisation is actually received, in order to avoid the character to enter into a private zone simply because the access denial was delayed or missing. Of course, authorisations must be requested some seconds before the character may reach a new zone, so that, if the authorisation is granted, the character can enter without experiencing an useless and frustrating freeze at the boundary. In any case, unauthorized zones are translated into invisible bounding boxes that the character is not allowed to enter in, even if he has the magical power to ignore ordinary wall's bounding boxes. It is when the character actually tries to pass such a boundary, that he gets a Kinflex=monitoring-enforced message.

It is advisable that the invisible blocks are clothed with some visible geometry, for instance to hide a private area from sight. It is very advisable to make this geometry coincide with something familiar, such as a house, or trees, haze... to avoid things like the ugly ban lines of Second Life. (which anyway were necessary only from the forced roleplay of promiscuity of incompatible projects).

And of course the character must not be able to hear chat, sounds, etc. and all the more not to move his viewing point into the forbidden zone. This is called "caming" in Second Life, and it is probably the total lack of protection against camming which caused the haphazardous sex rating system which appeared In 2009, where users have for only choice to live in a monastery or to live in a public sex place, without any notion of public/private places as in the real society.

Eventually a character may try to use a Kinflex=monitoring-denied for purposes of attacking or disturbing another. This can be in a normal roleplay, but it can also be with an evil intent. For this reason, such kind of activity must be logged for a short time as an evidence, in case the attacked user wants to fill some complain. In Second Life this was recorded as "hits and bumps", and used as evidence in abuse reports. In a Kailye system this can be useful for many other activities, such as repeated attempts to enter a private zone.



9.4 - Changing trajectories

A general paradigm in Kailye trajectories is that the host simulator will store all the keys it receives from the monitoring simulator into a trajectory buffer. This allows it for receiving successive parts of a trajectory into different messages, all along the life of the object. Of course into this buffer, all the keys of these different messages will be arranged into time order (time being the index). This does not arise any issue so long as the series is coherent, but if two different trajectories are received for the same time range, the object will exhibit a strange jitter behaviour, while it jumps from a key from one trajectory to a key of the other. The first caution is obviously that only one simulator, the one which monitors the object will send trajectories about this object, toward other simulators or toward the viewer. In this way it is to the monitor simulator to ensure it will send a coherent suite, even if the messages are not in time order. If the object is just handled or hosted, only the handling or hosting simulator is involved, so there is not such a risk.

When an inflexion key is added, new suites of keys will have to be added, and the ancient will need to be erased, starting from the inflexion key, under again penalty of seeing the object alternating between the two trajectories. This erasing will be commanded by the Kinflex command, and be performed by the receiver simulator or viewer.

When the object changes of monitor simulator, the keys from the first simulator may similarly be erased, starting from the time value indicated in Kmonitoring=accept,timeStamp. Of course the new monitoring simulator must start to send coherent values starting from this timeStamp, under again penalty of seeing the object jumping if the two trajectories don't join properly. So, when an object enters a new monitoring simulator, it must mandatorily send its last actuated trajectory, so that the new monitoring simulator will know it, and be able to send a kinflex command of its own, if relevant.


9.5 - Adding trajectories

Usually, when an inflexion key is accepted, all the values after it in the trajectory buffer are erased and replaced by the new values. However there is another way to add elements in the trajectory buffer: new keys are simply inserted among the previous, in time order, without attempt to erase them. Usually this will result in an havoc, but it can be useful into some special but very interesting cases: For instance a gust of wind pushes a sail ship. This can be simulated very easily with adding some acceleration key values into a second derivative trajectory. This creates the effect of a force applied to the ship. The only difficulty is that just inserting the new keys among the ancient will not result into a correct addition: a smart recalculation of a new set of keys must be done into the trajectory buffer.

To obtain this additive functioning, the syntax is:


In the example of the ship, the landscape simulator will send a gust of wind, resulting in forces applied on the ship, and its path and speed modified into a realistic way. This can also work with the surf simulation seen above, where the surfer rides on the surface of a moving NURB wave. For this we just need to compute the slope to get the forces applied on the surfer. This is a bit special, though, and requires some programming.

We can use the addition of derivative trajectories containing check-keys, but this is delicate. If we add acceleration keys in a trajectory containing position check-keys, this will force the simulator into the servomechanism mode, where it will try to cancel our additions. But on the contrary adding a position check-key will force a stray trajectory to a controlled place or condition.

Another example of adding trajectories will be a dancer in a ball room. His trajectory is moving and rotating around an average point, as he executes the dance. However any Second Life user knows that the dancer can still move around the dance place. This can be achieved in Kailye trajectories with adding the two position trajectories. Other methods are possible, thought.


9.6 - Computing Inflexion keys

It is convenient to use an existing key as an inflexion key. However this is not always possible, as there can be no close enough key, or the last key before the inflexion may be already into the past. So the inflexion key will often have to be interpolated between two existing keys. To avoid a trajectory jump or discrepancies, all the simulators must use the same interpolator algorithm, to get the same results down to the rounding errors. The same interpolator algorithm will also have to be used by the WEM wiewer when it interpolates its frames between keys.

That the algorithms used are the same into all implementations is a stringent requirement into any large project, the kind of things which must be obvious to all programmers. However we know too well that «alternative implementations» often «appear» from the simple desire of some to show different of the others. The Kailye standardisation committee will have to choose which will be the default algorithm, but if anybody else wants to have «his» choice, he will have to specify it into the format line:


without any obligation for the others to implement it too.

The Kinflex command obviously applies to positions, rotations, etc. But it can also be used to any other kind of trajectory. We hardly see how a colour change may encounter an obstacle, but it can be modified by the action of a character or a script. So that a colour changer will need a Kinflex message to be started, or any other state modification.

Numerous other things, such as commanding machines, moving or editing objects when building, etc. will be done with Kinflex messages.


9.7 - Random values in the format line

This notion of parameter/trajectory is so versatile that we can use it for many other purposes, such as requesting random values series under the form of a trajectory, or receiving the result. This will appear when a simulator needs to use a special random generator with specific properties, not just a general device implemented into all computers. In this case the random generator will generally be an Internet resource. After a Konnect command establishes the link, the simulator can do its request, and receive the results.

The syntax into the format line uses the same system as for trajectory requests and receipts:



as an example for a dice:



Of course no values are sent into the request, only the format line.


We can easily guess that random behaviours are very useful for games, science simulations, etc. But depending on applications, we need several very different and incompatible properties, some being impossible to implement on the local computer:

-Cryptography needs special high quality pure random sources with an even distribution.

-Science simulations also need high randomness, but with many kinds of special outputs: even, gauss law, Poisson law, etc. This can be specified into the format line.

-Pure analog random generators were found to be influenced by telekinesis, to the contrary of pure pseudo-random generators. This is an interesting feature for some games, social, science studies, etc. but this also may make them considered as a cheat in such uses. However an analog random generator is an hardware device, generally not found on a computer. So it will generally be an internet resource, into some kind of temple, as seen here

-For the same reason, we may want to connect to special data flux or experiments such as the Princeton Basket Observer, provided that it sends its results in real time, under the form of a Kailye trajectory.

-But the most specific use of random series in virtual world will be the automatic creation of complex landscapes or event patterns. For instance a whole forest can be generated using a random generator and some lines of program, instead of coding by hand and downloading thousands of different objects. But this needs several cautions. First, the same forest must be generated independently into the simulator and into all the viewers. It is obvious that all the users must see the same thing, and not one character going through an open space while another sees him going through a tree trunk. Random events in time arise the same issue: we must not have a surfer riding a wave while another sees him floating in the sky. So what we need is purely deterministic pseudo-random generators, which always give the same series of results when several of them are running simultaneously, provided that they use the same seed numbers. For this of course they need to use the same algorithm, but still actual implementations may have different rounding methods, leading to discrepancies increasing with the number of drawings. So deterministic random generators must be certified for this peculiar purpose, to give the same results whatever the number of drawings. An interesting example was the defunct randu(), which used a bit shift into a large number, so that there was no rounding issues, and the resulting series would reproduce absolutely exactly up to an infinite number of drawings, without any special care. Alas randu() was found biased, so that we have to find another algorithm having the same property, but without bias (and without rounding, as, generally, in a given language, the programmer cannot change the rounding method). Another caution is that an even random source may not be fit for this purpose: trees in a wood tends to grow into free places away of the others, coral flowers on a branch are at the same distance of each other, even stars do not have a standard random distribution. And when two trees happen to be close, their detailed branching is affected by each other. So what we need is not an even distribution (of points on a surface, or events in time) but results with a correlation law, such as an average distance between trees, an average time between waves, or the best place to find a star is besides another. On the other hand, we do not need for this purpose a high degree of randomness as with cryptography (although the human eye is very good at spotting spurious repeating patterns).


This makes that the request for a random number, or a series of random numbers, will have to cope with many algorithms and output formats. Simulator designers will probably implement many, but we shall still need random servers on the Web, implementing all the requested cases with a constant and defined quality. A random request in a trajectory will have to use a hierarchical naming:




where type can take several values: standard, pseudo, analog, deterministic, custom...

outputFunction and range can take many values useful for different applications. It is interesting to note that the existence of random centers will allow to tailor the desired algorithms, output law and output range as desired.

For instance range can be a character string telling the range of the results: "0.0-1.0" tells a floating point number from zero to 1 (default behaviour), while "1-6" tells an integer ranging from 1 to 6 (a dice). We can similarly output series of letters, random text, music notes, and, as indicated with the example of the forest, sets of points on a map, with a correlation function. We can even create random images and 3D textures, although theses results are way too heavy to be passed on the Kailye link. They must be passed as URLs instead.


9.8 - Animations

Animations and their inconveniences were precisely the kind of things we tried to avoid with the use of trajectories. However, when we have 100 persons walking together with the same step 2 times per seconds, it is obvious that we shall have to pass 200 times per seconds exactly the same values. So, in many cases, using animations (predefined series of moves) will crunch the Kailye data rate by a large factor, which will drastically change the result on the viewer, from a frozen laggy scene to a fluid and pleasant experience. The same issue occurs with things like machines with alternating movements (rotating movements are easily solved with only one value in a derivative trajectory. In many cases alternating movements could be passed in the same way, if we consider them as rotating movement projected onto only one dimension.)

Animations are indispensable and massively used into Second Life, for basic movements, repeating movements, non-repeating movements, transitions. Main applications are walk, dance, sitting, sex, fight, etc. Many others are possible into the realm of professional uses, for work simulations, science simulations, technology demonstrations, etc.

However, to avoid the general inconveniences of animations (not all users see the same thing) we need to integrate them into the general pattern of the Kailye trajectories, in order to keep the uniqueness of the result for all the different viewers. In this way, all the users still keep the same view at the same time, unless the Internet connexion is severed.

Some animations can be bulky, so we must be able to pass them as files. But even in this case, they will have to keep with a Kailye format, as, when used, animations are simply dumped into the trajectory buffer. The animation files will have to be stored with the same formats than other Kailye messages, although in a different place.


So all the syntaxes of the animations will be the same as with the trajectories, with just three small differences:


This example is for a simple position animation, using chars for position values and some paddings.

The differences with trajectories are:

-the resource parameter is called ?Kanimation instead of ?Ktrajectory

-the animation has a name, which can be either an human readable name (enclosed between $$), or any computer-friendly ID. We need to use the scoping system of the KID command.

-the time value (here a mita m, meaning 0 to 100) is unsigned, and it has no padding: this means that the animation will always start at a zero value (in a relative time), up to a maximum positive value.

The last value of time keys will usually be the duration the animation will last, for fixed speed animations. But when the animation is intended to be played at different speeds, it must end with a 1 time key, as in X3D interpolators. This is a variable speed animation. To ensure this, a decimal format is used, the .m, which can include the value 1. This difference is because some systems will mandatorily require them. For instance X3D interpolators always use them, so that any attempt to translate a Kailye animation into a X3D interpolator will produce unpredictable results if the 1 key is missing, or if superior values are present. On the other hand, Second Life animations use absolute time, with time keys equally spaced (framed animations) (Kailye keys don't need to be equally spaced). On a Kailye simulator, either must work, be it scaled or not, framed or not, without producing an error. But sending them to other systems will need to discriminate the three cases and send the appropriate type. It may help if we add some syntax:

?Kanimation.scalable tells that the animation ends with 1 and may be scaled.

?Kanimation.framed.frameRate tells that the animation has fixed time frames, with the number of frames per second.


The principle of running animations is that, when we get a starting time, they are placed into the trajectory buffer, starting with the start time, as with an inflexion key. This is enough to start the animation when the time comes. Of course relative animation times are computed into absolute trajectory times.


So when a computer receives an animation file, it needs to load it into the trajectory buffer. For this, two methods are possible:


This is a part of a standard Kailye message about the object, and describes the animation as a set of values, with the same format as with a trajectory. It will be stored under the name scope$nameOfTheAnimation$, until it is used.


with, as a Resource Block:


This forces the download of the animation under the form of a file, from the sender computer, or from any kind of Internet resource abroad. This method of using URLs can be widely used for bulky animations or user created animations. The same method can also be used to pass large trajectories as files. As the animation file may also contain other data, comments, other animations, etc. the parser will search for the string: ?Kanimation=~scope$nameOfTheAnimation$, and read until it finds an end of line. If it does not find this name, it loads all the readable data.

scope$nameOfTheAnimation$ can also be common animations coming in standard with all simulators and viewers, and pre-loaded into the memory.


The second thing of course is to start the animation:


witch starts the animation at the specified time. Keys are placed into the trajectory buffer, and other keys deleted beyond this time. We can use ?Kanimation.start.addition if we want a relative movement, such as a dance around a mobile point (the dancer changes his place on the dance pad)

We can also start it at once, as soon as loaded:


However in this case we will get a "Second life style" functioning, where some users will see the animation running and others not yet started. So it is better to use load.start only when this is not a real inconvenience.


When the animation is cyclic:


In fact, the load, start and cycle keywords can be used into any combination which makes sense.

We can specify a number of cycles:



When we want to stop the animation:


If no time is specified, the animation stops as soon as the message is received. This calls for the same remarks as with start.


stops the animation at the end of the current cycle. This is useful to things like stop walking, so that we always stop in the same «standard» position, and any kind of other animation just has to start from this position.


When we want to scale an animation:


The effect of this is to multiply all the time keys by 10. If it is a variable speed animation, this will set the last 1 time key to a value of 10. But this still works with fixed speed animation: if the last key was 5 (seconds) it will now be 50, without resulting in a Kailye error. Similarly omitting to apply a ~scale to a variable speed animation will not produce an error, but will just make it run in one second. There is no need for protection or error reporting in a pure WEM-Kailye system, it is just up to the designer to see if the result is what he expects. Problems however may arise when we output the wrong type to another system which does not support it. Only solution here is that this alien system returns some kind of ,unsupported, Kailye error, so that the sender can cast its animation into another format. This kind of issues is why we need standards, and why we must not block the situation with useless rigid choices.


When the animation is not loaded or not found, the receiver must send an error message:


errorCode can be notLoaded or URLerrorXXX. (In the case of an URL, XXX will be the HTML error code, such as the (in)famous error 404)


9.9 - Joining animations

A last but not least issue is to smoothly joint animations together (or normal trajectories with animations). For instance if the character is facing south, and the animation wants him to start facing north, then the scene shows the character turning instantly. This error is common in Second Life, where it is well tolerated however, the witnesses understanding the intent of the movement before checking if it is physically possible. However some high qualities applications such as machinimas (movies in a virtual world) need to strictly avoid this.

We can easily design short animations playing the role of smooth joints from a main animation to another. The monitor simulator may manage to send these smooth joints in due times. For instance a character has walking cycles, but when the cycle ends, a transitional short animation makes him stand upright. In a second time, a turning animation makes him turn from south facing to north facing, with cycling leg movements. At last when he stands upright again, facing north, the new animation can start.

This syntax will make the character (or object) start the animation when the currently running has stopped:



But in a general way, joining a free trajectory with an animation, or two animations together, cannot be done simply with a trajectory, as the end and start position are random. This needs a software, and thus cannot be covered by the Kailye language. This software will preferably have to run on the host simulator, to cope with the varied body shapes and functioning. If it runs on the monitor,, then it will create... a Kailye trajectory. A possible pure Kailye fix can be found into the trajectory buffer: when inserting the first animation key (the character looking north), if we have a last key where the character is still looking south, some time before the first animation key, then we shall see the character rotating. The result is still not very realistic (pure rotation of the whole body without legs movements) but it is better than an instant jump. In this way, inserting discontinuous animations into a continuous trajectory process can bring slightly better results than only joining discontinuous animations. But it is still needed that the character simulator is able to automatically create all kinds of joint animations on the fly. But, even if it is not, at least we have the same results for all the viewers.

At last, getting out of an animation will usually left the character into the last position, and from there give back the control to standard animations like the walk. A joint script may be of some help here too. A typical Second Life sequence for a sit pose contains a sitting down joint animation, a cyclic sit pose with some movements, and a get up joint animation triggered by the user, lefting him standing and ready to walk or turn. Unrealistic joints are reduced to a minimum.


10 - Algorithms

Things like interpolators, random generators and NURB systems arise a common issue: since there are many possible kinds of algorithms, output formats, quality levels, etc. we need to use the hierarchical naming to specify which is used. And it is too soon to know all the advantages and drawbacks of each of them, so we cannot specify in this version zero of the Kailye language which will be used, and what will be the data format. But as soon as the version 1, a large enough set of algorithms will have to be specified, and mandatorily implemented into all the simulators and the WEM viewer, under the form of modules:

-ONE trajectory interpolator for each kind of movement, rotation, etc.

-One NURB line and one NURB surface, each with the different control methods, orders, etc. The specification will also have to describe the computation algorithm and the data structure in the format line, preferably using structures and names.

-Besides the basic all-purpose random generator present in all computers, one fully deterministic random generator, giving exactly the same series of results whatever the implementation, down to the lesser digit, after an infinite number of iterations (this requires to avoid rounding numbers). It will be able to issue random drawing of elements, repartitions of points with several correlation functions, pseudo-cyclic events, etc.

-One system to compute the servomechanism which appears when check-keys are used. It will have to account with the global correction method, but also with the correction-debt system, so as to give departures (from the mathematically exact result) of the same order of magnitude than rounding errors, on all computers and with all languages.

-Integrators for speed or acceleration trajectories arise a special issue, as the progress in this domain will lead to increase accuracy (in a domain where many small numbers are added to a much larger one, increasing the effect of rounding errors). If a solution is found, it will have to be added to the standard. Otherwise check-keys will have to be used.

These algorithms must become an important and stable part of the WEM-Kailye specification as soon as the version 1. In each case, several may be adopted in the standard only if each has specific advantages, and none can be rejected without losing some rendering possibilities. Example: two different NURB equations with only slight difference of positions of the surface are too much, and will arise issues. We need to cull the less efficient, or the less standard. But if one proves much better for body shapes and the other better for mechanical shapes, then the two are really needed.


Next part: WEM commands into the Kailye language