Jump to content

[1.3] kOS Scriptable Autopilot System v1.1.3.0


erendrake

Recommended Posts

I agree in principle, but not with that specific example. That can be a dangerous check that fails to stop the burn.

It has two problems:

1: If X is too small, you can blow past X faster than the script responds and then when periapsis and apoapsis swap positions continued burning won't make the gap smaller, but larger.

2: If It takes too long to execute the burn so that you aren't precise enough in executing it centered on apoapsis, then its possible to never get apoapsis and periapsis within X, and yet its still time to stop burning because continued burning won't make it any better. The check I prefer is to stop the burn when you've rotated periapsis to within 90 degrees of your current position - to stop the burn as the apoapsis and periapsis are starting to swap positions and you're halfway between then. To see how to calculate that, google the phrase "True Anomaly".

I'm well aware of the difficulties in a versatile circularization algorithm , but it's pretty easy and safe to save the apoapsis in a variable when you start the burn, and stop burning when your periapsis > saved apoapsis -500. No, it won't be exactly circular, but if all you want to do is get your periapsis out of the atmosphere, it's great.

If you want a very circular orbit, you control thrust and pitch to keep your apoapsis just in front of you, and complete the burn at very low thrust with apoapsis about 10 seconds in front of you. This method can easily get you within 100m of circular, so long as your code is aware of your ship's maximum acceleration and the amount of ∆v required to finish the burn. It's essentially equivalent to making a circularization burn, then waiting until apoapsis and making another one to get a bit closer to circular.

Link to comment
Share on other sites

Is it possible to check whether a parameter has been passed? Like:

if paramVar = NaN
{
set paramVar to 1.
}

Generally speaking is it possible to check for NaN? Undefined? etc..

No.

It would have been possible before by adding a very simple "isNaN()" function to kOS that just is a wrapper around the C# isNaN call underneath.

But not anymore. A change was made that I'm not happy with but I reluctantly accept, that now does make it literally impossible for you to catch the problem in your script no matter what additional support gets implemented in kOS.

That change is that the moment any value becomes NaN or Infinity, even just temporarily during calculations of an expression, then kOS deliberately kills your script right there immediately. This is when you see the error message that says "tried to push ____ onto the stack". (where ____ is 'NaN' or 'Infinity'). Because of this change it would be pointless now to implement an isNaN check because by the time you get to the point where your script can run that check, kOS has already killed your script.

This change was made to make it impossible for the mod to ever pass a value of NaN or Infinity into any of KSP's API calls regardless of what the kOS script does, because now the kOS script cannot even "store" a value of NaN or Infinity in any value anywhere. KSP's API doesn't protect itself from callers giving it bogus data so KSP can crash when it gets a value like that.

I'm a bit unhappy about the solution, but I can understand that it's the simplest quickest way to implement it. I think the slightly more complex solution of passing arguments through a sanity check *only at* the moments they go into a KSP API method would have been nicer, but admit that it's a more time consuming solution to write.

Edited by Steven Mading
Link to comment
Share on other sites

Is this true?

I don't like this at all, since KSP keeps feeding us NaNs and infinities.

I'd rather not have this make my scripts terminate...

P.S: all comparisons to NaNs fail:

A variable containing a NaN would be neither > 0 nor <= 0.

Link to comment
Share on other sites

Is this true?

I don't like this at all, since KSP keeps feeding us NaNs and infinities.

I can't think of a case where it does that. And in fact the new change would make it impossible to do that. It's now impossible for the program stack to even hold a NaN or Infinity in it at all, so it can't feed one to your script anymore (to get it passed into one of the script's variables it would have had to put it on kOS's stack for a moment and then popped it back off again.)

More than likely one of your own math expressions is doing it. It's either trying to to trigonometry on invalid input (i.e. what's the arccos of x where abs(x) > 1? It's NaN.), or you're trying to divide a nonzero number by zero (which results in Infinity). The frustration can be that if you have math in a LOCK expression used when flying the ship (i.e. a bit of math called when calculating STEERING or THROTTLE), these calculations can be happening at any random time in your script, so you can't even do something like this:


lock throttle to x/maxthrust.
// And then later in the code you try to protect against maxthrust being zero and giving an infinity throttle this way:
if doStage {
stage.
if maxthrust = 0 { unlock throttle. }. // turn throttle off until the next stage so it doesn't try to divide by maxthrust of zero.
}.

In the new design you can't even protect against it that way. It steers and flies constantly "in the background", interrupting your script execution at "random" times (not really random, but at any rate its outside your control) to go fly the ship a moment, then going back to continuing execution of your script where it left off. This sets up a race condition where if this interruption just happens to occur in between your call to "stage." and your call to "unlock throttle", then your script hasn't gotten around to decoupling the throttle from the infinity value you get from dividing by zero, and your script gets killed. Because it's impossible to mark a section of the script as an "atomic section", you are helpless to do anything about this. It's impossible to write script code that can be guaranteed to catch the condition between when it gets set and when it gets used by the fly by wire system.

Therefore the only way to catch it is to NEVER, even for one brief instant of time, let it happen. To do something like the above I now have to do it this way:


set myMaxThrust to maxthrust.
lock throttle to x/myMaxThrust.
...
// And then later in the code you try to protect against maxthrust being zero and giving an infinity throttle this way:
if doStage {
stage.
if maxthrust = 0 {
set myMaxThrust to 0.01. // Use a very small but not quite zero value, so throttle is big but not infinity.
} else {
set myMaxThrust to maxthrust.
}.

I too don't like it, but it's because its less elegant programming to check all your possible error conditions ahead of time before trying something than it is to unconditionally try to do it, but then check for an error condition afterward. In other words this commonly used bit of logic in programs:

if (try to open the file) failed then (print error condition here)

Is a lot more elegant than this:

if (check for filename existing) then

if (check for proper permissions) then

if (check for disk mounted okay) then

...etc...

then (try to open file).

It is currently POSSIBLE to stop any NaN or Infinity from happening before it kills your script, but only by checking every case where a calculation could come out that way, ahead of time, before it happens. It's possible, but not very nice.

P.S: all comparisons to NaNs fail:

A variable containing a NaN would be neither > 0 nor <= 0.

That's actually correct according to how the IEEE specifications for the common floating point representations say NaN numbers are meant to work. You are never meant to be able to do anything at all with NaN other than check "is it NaN?", and that check cannot be performed directly like so: "if num == NAN", because there's more than one way to represent NaN in binary form and such a check would therefore be a mess to implement. Instead all languages in which a program may need to deal with NaN values because the language doesn't mask them off should provide a feature that lets you check with a function or macro call, like so: "if isNan(num)" because then the underlying fact that there's more than one NaN representation can be dealt with by whatever the implementation of that call does.

Link to comment
Share on other sites

Even if KSP returns a NaN sometimes it doesn't like when you send them back. Trying to, for example, operate with a vector which has a NaN or Infinity component causes KSP to crash with a black screen. I agree that the current solution is too restrictive but it was the safer way to solve this situation. If we allow NaNs back we'll have to add validations everywhere to avoid KSP from crashing.

Other option I was considering is to add a config option to disable this "safe mode" and allow NaNs and Infinities back, and give the script writers a couple of functions to check for this special values. Along this we should add validations in most critical places to avoid a crash but it would be a best effort kind of thing, so you shouldn't rely on kOS catching the NaNs you missed.

Link to comment
Share on other sites

Even if KSP returns a NaN sometimes it doesn't like when you send them back.

Does there exist the danger, with the current kOS code, that through no fault of the script programmer, KSP decides to RETURN a NaN into a variable, and then this causes kOS to crash the script because it stores that NaN on the stack? i.e. imagine trying to read a value from the game engine, like maneuver node ETA, or current target's distance, and so on, and then KSP gives you a NaN for it, and the script crashes before the player has any control over it?

Link to comment
Share on other sites

It shouldn't, at least I have never experienced it but there's an issue in the tracker about a NaN crash even after the we added the validations. I think the parts that could be compromised are those who deal with KSP directly (like the steering helper when you lock to a steering) that are executed in the OnFlyByWire update.

Link to comment
Share on other sites

Nope. I have seen this request a few times now, what were you thinking about doing with them?

I'd like to point towards periapsis or apoapsis for creating a circularisation maneuver node. Depending on certain conditions it would be one or the other. I think I can do this with an IF statement and locking periapsis or apoapsis to a variable, but I was just curious.

Can we have a simple mp3 player :) for alarms?

Edited by Cpt. Kipard
Link to comment
Share on other sites

I'd like to point towards periapsis or apoapsis for creating a circularisation maneuver node. Depending on certain conditions it would be one or the other. I think I can do this with an IF statement and locking periapsis or apoapsis to a variable, but I was just curious.

You can sort of accomplish that with LOCKs. A LOCK says "return whatever is in the expression" and "the expression" could just be another variable and nothing more. LOCK X TO Y has a very similar effect to making X a reference variable pointing at Y, with the exception that theres no iterator math you can do on it (add/subtract from X to "move" where it's pointing, which since the ordering of memory under the hood in kOSscript is not defined in any public standard, would be a Bad Thing to try to do.)

Can we have a simple mp3 player :) for alarms?

Or in keeping with the theme of old-school tech: The ability to print a BEL character and make the terminal window beep.

Edited by Steven Mading
Link to comment
Share on other sites

Or in keeping with the theme of old-school tech: The ability to print a BEL character and make the terminal window beep.

Sounds good to me. Can we have it configurable in some way? Like wave frequency and simple sequencing? Midi playback also sounds good.

Link to comment
Share on other sites

I approve of this as a start. maybe allow midi's later :)

I wasn't entirely serious because to a certain extent adding control characters like BEL might require adding the ability to make number-to-character converting (i.e. print chr(7)) and also require adding more sophistication to the terminal interpreter and so on. Might be a bit of a mess.

Link to comment
Share on other sites

I'm having some issues with a script and I'm not sure if I have some syntax or logic error or something, or if there's some weird bug with KOS. I'm using the new pre-release v0.12.P3.

I know exactly which part is causing the problem, but I don't understand why it is causing the problem.

This part, for some reason, is causing the throttle to be unlocked right away, even though it shouldn't even be executed.

Snippet from stager.txt


IF fuelGood = FALSE OR airGood = FALSE
{
print "no good".
IF airGood = FALSE
{
LOCK THROTTLE TO 0.
//print "why are you unlocking stuff!".

DECLARE enginesOff.

If I place a print statement there, nothing is printed, but if I comment out the

LOCK THROTTLE TO 0.

then it works as expected.

test.txt


lock throttle to 1.
lock steering to UP + R(0,0,180).

until 1 {
print "test".
run stager.
wait 1.
}.

stager.txt


LIST PARTS IN prts.
SET lastStage TO 0.
SET lastCoupler to 0.

FOR p IN prts {
IF p:STAGE > lastStage {
SET lastStage TO p:STAGE.
}.
RUN decouplers(p:NAME).
IF return = TRUE {
IF p:STAGE > lastCoupler {
SET lastCoupler TO p:STAGE.
}.
}.
}.

SET fuelGood TO FALSE.
SET airGood TO TRUE.

FOR p IN prts {
IF p:STAGE >= lastCoupler {
FOR res IN p:RESOURCES {
IF res:NAME = "liquidfuel" OR res:NAME = "solidfuel" OR res:NAME = "oxidizer" {
IF res:AMOUNT > 0 {
SET fuelGood TO TRUE.
}.
}.
IF res:NAME = "intakeair" {
IF res:AMOUNT = 0 {
SET airGood TO FALSE.
}.
}.
}.
}.
}.

IF fuelGood = FALSE OR airGood = FALSE
{
print "no good".
IF airGood = FALSE
{
LOCK THROTTLE TO 0.
//print "why are you unlocking stuff!".

DECLARE enginesOff.
UNTIL enginesOff = TRUE
{
SET enginesOff TO TRUE.
LIST ENGINES IN engs.
FOR eng IN engs
{
IF eng:IGNITION = TRUE
{
IF eng:THRUST > 10
{
SET enginesOff TO FALSE.
}.
}.
}.
}.
}.
STAGE.
IF airGood = FALSE
{
UNLOCK THROTTLE.
}.

SET doubleStage TO TRUE.

LIST ENGINES IN engs.
FOR eng IN engs
{
IF eng:IGNITION = TRUE
{
SET doubleStage TO FALSE.
}.
}.

IF doubleStage = TRUE
{
WAIT 1.
STAGE.
}.
}.

Link to comment
Share on other sites

Try locking throttle to a variable instead of 0, then setting that variable to whatever.

In other news, I've spent waaaaay too much time refining my ascent autopilot script. You know you're off the deep end when you consider correcting for how fast the ship rotates.

Link to comment
Share on other sites

@Xty, Your test script looks weird to me. What's the "UNTIL 1" for? Why make a loop that is designed to quit unconditionally before it even runs?

Also, if you are trying to use UNTIL 1 or UNTIL 0 like you are, you should be aware of this bug with the UNTIL statement:

https://github.com/KSP-KOS/KOS/issues/36

The fix is already implemented in a pull request but that hasn't made it into the v0.12.P3 release. I'm not sure if that's the problem here but it's something you should know if you're using that in your script.

Link to comment
Share on other sites

Locking it to a variable makes no difference.. infact, locking it to a non 0 number or a non zero variable still makes it unlock for some reason. And that piece of code shouldn't even be being executed. I don't understand it.

Link to comment
Share on other sites

The UNTIL 1 just makes it run forever, I thought it used to be until 0 that did that but apparently no, its until 1.. Perhaps thats a new bug, but I don't think thats what is causing the issue, I tried UNTIL FALSE {} instead but same problem.

Edited by Xty
Link to comment
Share on other sites

The UNTIL 1 just makes it run forever, I thought it used to be until 0 that did that but apparently no, its until 1.. Perhaps thats a new bug, but I don't think thats what is causing the issue, I tried UNTIL FALSE {} instead but same problem.

If you've taught yourself that UNTIL 1 is the way you loop forever, then you've accidentally taught yourself to write code that is dependent on a bug remaining unfixed. "UNTIL 1" looping forever is a bug. "1" should be interpreted as 'true', so UNTIL 1 *should* mean the same thing as UNTIL TRUE.

I don't think this is causing the problem you're having. I just wanted to warn you about it because I expect the fix for UNTIL 1 will be coming out soon and what you wrote is dependent on the bug continuing to exist.

Edited by Steven Mading
Link to comment
Share on other sites

It's not that I taught myself UNTIL 1 is loop forever, it's that UNTIL 0 didn't work so I switched it. But thank you. Anyways that not my real issue, that test script is just for testing if it works, because it wasn't working.

Link to comment
Share on other sites

@Xty: The problem is that locks declared in a parent program are not available in a child program. There's an explanation for why adding the "unlock throttle" line in the child program causes the throttle to unlock but that is more suited to the development thread, so I won't enter into details here.

Try changing the "unlock throttle" for "set throttle to 0" which has the same effect and shouldn't have this problem.

Link to comment
Share on other sites

Guest
This topic is now closed to further replies.
×
×
  • Create New...