v50 Steam/Premium information for editors
  • v50 information can now be added to pages in the main namespace. v0.47 information can still be found in the DF2014 namespace. See here for more details on the new versioning policy.
  • Use this page to report any issues related to the migration.
This notice may be cached—the current version can be found here.

Difference between revisions of "40d:Macro design"

From Dwarf Fortress Wiki
Jump to navigation Jump to search
(the term is not "back peddle", but "backpedal")
m
 
(18 intermediate revisions by 7 users not shown)
Line 1: Line 1:
Playing Dwarf Fortress means lots of typing. Macros should likewise mean a lot of planing, hence a design. This page is to go over the design issues when creating a macro and how to avoid problems and slow/massive code. The design of the operation/structure that you creating a macro for, this page will not help you. You should be most familiar with it though, as you've probably typed it in hundreds of times before bothering to turn it into a macro.
+
{{quality|superior}}{{av}}
 +
Playing Dwarf Fortress means lots of typing. Macros can reduce this typing, but they must be planned and designed carefully. This page discusses how to design a macro and avoid common problems. The specific operation or structure for which you are creating a macro is beyond the scope of this page.
  
 +
== Macro delay ==
 +
Forum and wiki participants have observed that some macros can take hours to run. This happens because there is a built-in delay between each macro command; since the game as shipped includes no preset macros, this delay is set to a value that allows the human player to see each move and debug any mistakes.
  
=Macro Delay=
+
The solution is to change MACRO_MS in [[init.txt]] from 150 to 0. Most macros on any system commonly run better with some delay between commands{{verify}}; a MACRO_MS:1 seems to be a good run-time setting.
Its been seen on the forums, and on this wiki where macros take a long time, and in some instances hours to run. The stated solution is to change an 'erroneous' leftover tag MACRO_MS in \data\init\init.txt from 150 to 0. The game as shipped includes no preset macros so the default init is set to a value that the human player can see each move as they debug any mistakes in the first macro they create. Most macros on any system commonly run better with some delay between commands; a MACRO_MS:1 seems to be a good run-time setting.
 
  
=Planing=
+
== Planning ==
Design is all about planning ahead of time to achieve a better result than simply 'winging it'. There are thousands of ways to do the exact same thing, but with a design, we would like to pick the best or near-best solution. In the case of DF's macros we want to see our macro run in the fewest steps possible, getting the most out of each command.  
+
There are thousands of ways to do anything, but we would like to pick the best or near-best solution. In the case of DF's macros we want to see our macro run in the fewest steps possible, getting the most out of each command.  
  
Start by planning out your design as series of steps to create the final result. Then code each step individuality, while taking note of the before/after steps. Like the complete macro, each step has many possible different coding solutions. Try to pick one that is simple (short) but also goes best with the step before it, and will end, where its advantageous for the next step. Try to keep a step to a single type of operation, this will save the time of continuously switching between modes.
+
Start by planning out your design as a series of steps to create the final result. Then code each step individually, while taking note of the before and after steps. Like the complete macro, each step has many possible different coding solutions. Try to pick one that is simple (short) but also goes best with the step before it, and will end somewhere that's advantageous for the next step. Try to keep a step to a single type of operation; this will save the time of continuously switching between modes.
  
:Example: Fields (Boxes\area-selections) have four corners. Your code traverses from one corner to the opposite corner to define the field. It doesn't matter which corner you start at. So pick a start and stop point of you field, that's good for the next or previous (or both) step(s). This saves a lot of time spent walking the cursor around.
+
:Example: Fields (boxes or area-selections) have four corners. Your code traverses from one corner to the opposite corner to define the field. It doesn't matter which corner you start at. So pick a start and stop point of your field that's convenient for the next and previous steps. This saves a lot of time spent walking the cursor around.
  
=Repeats=
+
== Repeats ==
Command repetition is only a coding short hand. The following scripts are identical, in the time it takes them to execute.
+
The macro syntax allows commands to be repeated using a numeric parameter. The following scripts take the same time to execute.
<pre>
 
[MACRO:CURSOR_UP:20]
 
</pre><pre>
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
[MACRO:CURSOR_UP:1]
 
</pre>
 
  
While it's much easier to read the condensed version, but to DF, its the same in every other way.
+
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
 +
[MACRO:CURSOR_UP:1]
  
=Movement=
+
[MACRO:CURSOR_UP:'''20''']
Most of the time a macro takes to run is moving the cursor around. This can be sped up through the use of the "_FAST" versions of the move commands and by moving diagonally
+
 
 +
The condensed version is more readable, but these work the same way.
 +
 
 +
== Movement ==
 +
Most of the time a macro takes to run is spent moving the cursor around. This can be sped up through the use of the "_FAST" versions of the move commands and by moving diagonally.
 +
 
 +
=== Fast movement ===
 +
When you hold the {{key|Shift}} key in the game UI and press an arrow key, the cursor jumps ten squares instead of one. Similarly, each cursor command has a "_FAST" version that jumps ten squares in the same time a normal movement command takes to travel on square.
  
==Standard Movement==
 
There is a "_FAST" version of every cursor command. "_FAST" movement jumps ten squares in the same time a normal movement command takes to travel on square. This the same as holding the {{key|Shift}} key the the game UI.
 
 
The following code,
 
The following code,
<pre>
+
 
[MACRO:CURSOR_UP:33]
+
[MACRO:CURSOR_UP:33]
</pre>
+
 
 
is the same as 33 moving commands however,
 
is the same as 33 moving commands however,
<pre>
 
[MACRO:CURSOR_UP_FAST:3]
 
[MACRO:CURSOR_UP:3]
 
</pre>
 
is the exact same action in only 4 commands
 
  
 +
[MACRO:CURSOR_UP_FAST:3]
 +
[MACRO:CURSOR_UP:3]
 +
 +
is exactly the same action in only 4 commands.
  
==Backpedaling==
+
=== Backpedaling ===
 
When the 1's digit is higher than 5, backing the cursor up can achieve faster run times.
 
When the 1's digit is higher than 5, backing the cursor up can achieve faster run times.
<pre>
 
[MACRO:CURSOR_UP:38]
 
  
Goes to
+
[MACRO:CURSOR_UP:38]
  
[MACRO:CURSOR_UP_FAST:3]
+
Can be sped up with _FAST commands:
[MACRO:CURSOR_UP:8]
 
  
Brings 38 commands down to just 11, however this can be better optimized by backing up.
+
[MACRO:CURSOR_UP_FAST:3]
 +
[MACRO:CURSOR_UP:8]
  
[MACRO:CURSOR_UP_FAST:4]
+
This brings 38 commands down to just 11. However this can be better optimized by moving ''past'' the intended destination and backing up.
[MACRO:CURSOR_Down:2]
 
</pre>
 
  
Is the exact same cursor position change but in only 6 moves (nearly half). This can have side effects however (covered below) and must be done carefully.
+
[MACRO:CURSOR_UP_FAST:4]
 +
[MACRO:CURSOR_Down:2]
  
 +
Is exactly the same cursor position change but in only 6 moves. This can have [[#Edge collision|side effects]] however and must be done carefully.
  
==Diagonal Movement==
+
=== Diagonal movement ===
Most people using modern computers are spoiled with their Tetris(TM) shaped, arrow key block. The number pad has arrows too, and on a select few keyboards the {{key|1}} {{key|3}} {{key|7}} {{key|9}} keys have arrows as well. It doesn't matter if your keyboard has arrows on it or not to DF, you can move with the {{key|1}} {{key|3}} {{key|7}} {{key|9}} keys so we can also code diagonal movement in our macros too.
+
Most people using modern keyboards are familiar with the T-shaped arrow key block. The numeric keypad has arrows too, and on some keyboards the {{key|1}} {{key|3}} {{key|7}} {{key|9}} keys have arrows as well. Regardless of whether your numeric keypad has arrows on it, you can move with the {{key|1}} {{key|3}} {{key|7}} {{key|9}} keys. Diagonal movement is also possible in macros, and it can be used to reduce the number of commands required.
  
<pre>
+
[MACRO:CURSOR_UP:8]
[MACRO:CURSOR_UP:8]
+
[MACRO:CURSOR_RIGHT:8]
[MACRO:CURSOR_RIGHT:8]
 
  
 
can be replaced by
 
can be replaced by
  
[MACRO:CURSOR_UPRIGHT:8]
+
[MACRO:CURSOR_UPRIGHT:8]
</pre>
 
  
 
This dropped the command count from 16 to 8. It also works for non-squares too.
 
This dropped the command count from 16 to 8. It also works for non-squares too.
  
<pre>
+
[MACRO:CURSOR_UP:12]
[MACRO:CURSOR_UP:12]
+
[MACRO:CURSOR_RIGHT:16]
[MACRO:CURSOR_RIGHT:16]
 
  
 
can be replaced by
 
can be replaced by
  
[MACRO:CURSOR_UPRIGHT:12]
+
[MACRO:CURSOR_UPRIGHT:12]
[MACRO:CURSOR_RIGHT:4]
+
[MACRO:CURSOR_RIGHT:4]
</pre>
 
  
 
This saved us 12 unneeded commands.
 
This saved us 12 unneeded commands.
  
The "_FAST" works in the game and in your scripts on diagonals too. Further bringing down the the previous example command count to just,
+
The "_FAST" works in the game and in your scripts on diagonals too. Further bringing down the previous example command count to just,
<pre>
+
 
[MACRO:CURSOR_UPRIGHT_FAST:1]
+
[MACRO:CURSOR_UPRIGHT_FAST:1]
[MACRO:CURSOR_UPRIGHT:2]
+
[MACRO:CURSOR_UPRIGHT:2]
[MACRO:CURSOR_RIGHT:4]
+
[MACRO:CURSOR_RIGHT:4]
</pre>
 
  
 
So from 38 commands to just 7, with a little planning.
 
So from 38 commands to just 7, with a little planning.
  
Diagonal movement commands are written as Vertical then Horizontal with no separating "_"
+
Diagonal movement commands are written as '''vertical''' then '''horizontal''' with no separating "_"
 +
 
 +
=== Final cursor placement ===
 +
When designing scripts, it's a good idea to plan where to leave off. The two common ideas are:
 +
* Back where the script started, which is less disorienting to the user. This is best for macros that are intended to be used once.
 +
* In position for an instant repeat of the macro, for macros that are likely to be used several times in a row.
 +
If your macro makes a 3 square wide section of hallway to the left, then put the cursor in a position that allows for a repeat call of the macro to add a second section.
  
=Cursor Return=
 
When designing your script its a good idea to plan, where to leave off. The two common ideas are,
 
*Back where the script started, which is less disorienting to the user.
 
*In position for an instant repeat of the macro, if that makes sense for this macro.
 
:If your macro makes a 3 square wide section of hallway to the left, then put the end the cursor in position to allow for a repeat call of the macro to add a second section.
 
 
Both are better than just stopping the cursor wherever it happens to be.
 
Both are better than just stopping the cursor wherever it happens to be.
  
  
 +
= Troubleshooting =
 +
 +
== Off-by-one errors ==
 +
When your script doesn't run as planned, check if you went too far on a movement action. This can happen if you confuse the number of squares ''designated'' with the number of squares ''moved''.
 +
 +
For example, to make a '''5x5''' box, use 4's instead of 5's:
  
 +
[MACRO:SELECT:1]
 +
[MACRO:CURSOR_LEFT:'''4'''] 
 +
[MACRO:CURSOR_UP:'''4''']
 +
[MACRO:SELECT:1]
  
=Problems to avoid=
+
The cursor's initial position always counts as 1 "extra" square, since you're defining movement, not a size.
  
==A step to far==
+
#    1 square ''designated'', '''0''' squares ''moved''
When your script doesn't run as planned, check if you went to far on a movement action. To make a 5x5 box the code is
+
<pre>
+
##    2 squares ''designated'', '''1''' square ''moved''
//written for ease of reading
+
[MACRO:SELECT:1]
+
###   3 squares ''designated'', '''2''' squares ''moved''
[MACRO:CURSOR_LEFT:4]    
 
[MACRO:CURSOR_UP:4]
 
[MACRO:SELECT:1]
 
</pre>
 
  
Those are 4's not 5's. The cursors current position is always 1, since it's really movement your defining not a size. Its an easy concept but also easy to forget/mess up.
+
== Edge collision ==
 +
When the cursor hits an edge of the map, it stops there, no matter how far your script wants to travel that way. This is per command, and if you're not expecting to hit an edge, this will corrupt that execution of the script. To avoid this, the script-runner must ensure that the script has enough room to run when it is started. The script-designer can help by clearly documenting how much space is needed.
  
==Edge Collision==
+
Scripts can also run into trouble with [[#Backpedaling|backpedaling optimizations]]. Backpedaling can extend your script's running area beyond the area where it is making changes. If the script-runner only leaves enough space for the changes themselves, there is a risk that any _FAST jumps outside that area will be shortened by hitting an edge, and any subsequent backpedaling operations will then start from the wrong position. Therefore, it is recommend to only use backpedaling that does not go outside of your script's work area.
Edge Collision can be helpful in several situations. When you hit a edge you stop there, no matter how far your script wants to travel that way. This is per command and if your not expecting to hit a wall, it will corrupt that exeicution of the script. There is no way to avoid this. And in most cases, it's the script-runner's fault not the script-designer's.
 
However as a designer you can run into trouble with backpedaling optimizations as mentioned above. Backpedaling can jump you out of the working area of your script and then return, as long as there is enough extra space around your script area to do so. If an edge happens to be there, instant problem.
 
  
:If your scrip is made inside a 17x4 rectangle, getting across it with the following code,
+
For example, if your script operates inside a 17x4 rectangle, getting across it with the following code,
<pre>
 
[MACRO:CURSOR_RIGHT_FAST:2]
 
[MACRO:CURSOR_LEFT:3]
 
</pre>
 
is fine, as long as an edge wasn't 19 squares away.
 
  
It's recommend to only use backpedaling that does not go outside of your script's work area.
+
[MACRO:CURSOR_RIGHT_FAST:2]
 +
[MACRO:CURSOR_LEFT:3]
  
With backpedaling movements, that are larger than 10 spaces, a simple change of order of operations can fix this issue. Here is the above example, with an order changes
+
will place the cursor incorrectly if an edge is 19 squares away.
  
<pre>
+
For script areas of 6, 7, 8, or 9, it may be wise to avoid backpedaling entirely, since any _FAST operation would move the cursor outside that range. But with a script area that small, speed probably isn't as much of an issue.
[MACRO:CURSOR_RIGHT_FAST:2]
 
[MACRO:CURSOR_LEFT:3]
 
  
is really just
+
For areas larger than 10 spaces, reordering the commands can keep the cursor in-range while still using backpedaling optimizations. The above example is equivalent to this:
  
[MACRO:CURSOR_RIGHT_FAST:1]
+
[MACRO:CURSOR_RIGHT_FAST:1]
[MACRO:CURSOR_RIGHT_FAST:1]
+
[MACRO:CURSOR_RIGHT_FAST:1]
[MACRO:CURSOR_LEFT:3]
+
[MACRO:CURSOR_LEFT:3]
  
so we can reorder it to
+
Which we can reorder to:
  
[MACRO:CURSOR_RIGHT_FAST:1]
+
[MACRO:CURSOR_RIGHT_FAST:1]
[MACRO:CURSOR_LEFT:3]
+
[MACRO:CURSOR_LEFT:3]
[MACRO:CURSOR_RIGHT_FAST:1]
+
[MACRO:CURSOR_RIGHT_FAST:1]
</pre>
 
  
This is the same run time/#commands, yet the cursor says inside the script area.  
+
This is the same run time and number of commands, yet the cursor says inside the script area. The backpedaling "LEFT:3" command now backs up in the space the first "_FAST" command had already traveled. (Note that if it had been placed ''first'', the cursor would have backpedaled out the other side.)
:For script areas of 6, 7, 8, or 9 your just out of luck and I would suggest not backpedaling, but with a script area so small, speed isn't that much of an issue.
 
  
:The backpedaling "LEFT:3" command, travels over the same area the previous "_FAST" command had covered. If it had been placed first, the cursor would have backpedaled out the other side.
+
{{Category|Interface}}

Latest revision as of 20:16, 23 June 2017

This article is about an older version of DF.

Playing Dwarf Fortress means lots of typing. Macros can reduce this typing, but they must be planned and designed carefully. This page discusses how to design a macro and avoid common problems. The specific operation or structure for which you are creating a macro is beyond the scope of this page.

Macro delay[edit]

Forum and wiki participants have observed that some macros can take hours to run. This happens because there is a built-in delay between each macro command; since the game as shipped includes no preset macros, this delay is set to a value that allows the human player to see each move and debug any mistakes.

The solution is to change MACRO_MS in init.txt from 150 to 0. Most macros on any system commonly run better with some delay between commands[Verify]; a MACRO_MS:1 seems to be a good run-time setting.

Planning[edit]

There are thousands of ways to do anything, but we would like to pick the best or near-best solution. In the case of DF's macros we want to see our macro run in the fewest steps possible, getting the most out of each command.

Start by planning out your design as a series of steps to create the final result. Then code each step individually, while taking note of the before and after steps. Like the complete macro, each step has many possible different coding solutions. Try to pick one that is simple (short) but also goes best with the step before it, and will end somewhere that's advantageous for the next step. Try to keep a step to a single type of operation; this will save the time of continuously switching between modes.

Example: Fields (boxes or area-selections) have four corners. Your code traverses from one corner to the opposite corner to define the field. It doesn't matter which corner you start at. So pick a start and stop point of your field that's convenient for the next and previous steps. This saves a lot of time spent walking the cursor around.

Repeats[edit]

The macro syntax allows commands to be repeated using a numeric parameter. The following scripts take the same time to execute.

[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:1]
[MACRO:CURSOR_UP:20]

The condensed version is more readable, but these work the same way.

Movement[edit]

Most of the time a macro takes to run is spent moving the cursor around. This can be sped up through the use of the "_FAST" versions of the move commands and by moving diagonally.

Fast movement[edit]

When you hold the Shift key in the game UI and press an arrow key, the cursor jumps ten squares instead of one. Similarly, each cursor command has a "_FAST" version that jumps ten squares in the same time a normal movement command takes to travel on square.

The following code,

[MACRO:CURSOR_UP:33]

is the same as 33 moving commands however,

[MACRO:CURSOR_UP_FAST:3]
[MACRO:CURSOR_UP:3]

is exactly the same action in only 4 commands.

Backpedaling[edit]

When the 1's digit is higher than 5, backing the cursor up can achieve faster run times.

[MACRO:CURSOR_UP:38]

Can be sped up with _FAST commands:

[MACRO:CURSOR_UP_FAST:3]
[MACRO:CURSOR_UP:8]

This brings 38 commands down to just 11. However this can be better optimized by moving past the intended destination and backing up.

[MACRO:CURSOR_UP_FAST:4]
[MACRO:CURSOR_Down:2]

Is exactly the same cursor position change but in only 6 moves. This can have side effects however and must be done carefully.

Diagonal movement[edit]

Most people using modern keyboards are familiar with the T-shaped arrow key block. The numeric keypad has arrows too, and on some keyboards the 1 3 7 9 keys have arrows as well. Regardless of whether your numeric keypad has arrows on it, you can move with the 1 3 7 9 keys. Diagonal movement is also possible in macros, and it can be used to reduce the number of commands required.

[MACRO:CURSOR_UP:8]
[MACRO:CURSOR_RIGHT:8]

can be replaced by

[MACRO:CURSOR_UPRIGHT:8]

This dropped the command count from 16 to 8. It also works for non-squares too.

[MACRO:CURSOR_UP:12]
[MACRO:CURSOR_RIGHT:16]

can be replaced by

[MACRO:CURSOR_UPRIGHT:12]
[MACRO:CURSOR_RIGHT:4]

This saved us 12 unneeded commands.

The "_FAST" works in the game and in your scripts on diagonals too. Further bringing down the previous example command count to just,

[MACRO:CURSOR_UPRIGHT_FAST:1]
[MACRO:CURSOR_UPRIGHT:2]
[MACRO:CURSOR_RIGHT:4]

So from 38 commands to just 7, with a little planning.

Diagonal movement commands are written as vertical then horizontal with no separating "_"

Final cursor placement[edit]

When designing scripts, it's a good idea to plan where to leave off. The two common ideas are:

  • Back where the script started, which is less disorienting to the user. This is best for macros that are intended to be used once.
  • In position for an instant repeat of the macro, for macros that are likely to be used several times in a row.

If your macro makes a 3 square wide section of hallway to the left, then put the cursor in a position that allows for a repeat call of the macro to add a second section.

Both are better than just stopping the cursor wherever it happens to be.


Troubleshooting[edit]

Off-by-one errors[edit]

When your script doesn't run as planned, check if you went too far on a movement action. This can happen if you confuse the number of squares designated with the number of squares moved.

For example, to make a 5x5 box, use 4's instead of 5's:

[MACRO:SELECT:1]
[MACRO:CURSOR_LEFT:4]   
[MACRO:CURSOR_UP:4]
[MACRO:SELECT:1]

The cursor's initial position always counts as 1 "extra" square, since you're defining movement, not a size.

#     1 square designated, 0 squares moved

##    2 squares designated, 1 square moved

###   3 squares designated, 2 squares moved

Edge collision[edit]

When the cursor hits an edge of the map, it stops there, no matter how far your script wants to travel that way. This is per command, and if you're not expecting to hit an edge, this will corrupt that execution of the script. To avoid this, the script-runner must ensure that the script has enough room to run when it is started. The script-designer can help by clearly documenting how much space is needed.

Scripts can also run into trouble with backpedaling optimizations. Backpedaling can extend your script's running area beyond the area where it is making changes. If the script-runner only leaves enough space for the changes themselves, there is a risk that any _FAST jumps outside that area will be shortened by hitting an edge, and any subsequent backpedaling operations will then start from the wrong position. Therefore, it is recommend to only use backpedaling that does not go outside of your script's work area.

For example, if your script operates inside a 17x4 rectangle, getting across it with the following code,

[MACRO:CURSOR_RIGHT_FAST:2]
[MACRO:CURSOR_LEFT:3]

will place the cursor incorrectly if an edge is 19 squares away.

For script areas of 6, 7, 8, or 9, it may be wise to avoid backpedaling entirely, since any _FAST operation would move the cursor outside that range. But with a script area that small, speed probably isn't as much of an issue.

For areas larger than 10 spaces, reordering the commands can keep the cursor in-range while still using backpedaling optimizations. The above example is equivalent to this:

[MACRO:CURSOR_RIGHT_FAST:1]
[MACRO:CURSOR_RIGHT_FAST:1]
[MACRO:CURSOR_LEFT:3]

Which we can reorder to:

[MACRO:CURSOR_RIGHT_FAST:1]
[MACRO:CURSOR_LEFT:3]
[MACRO:CURSOR_RIGHT_FAST:1]

This is the same run time and number of commands, yet the cursor says inside the script area. The backpedaling "LEFT:3" command now backs up in the space the first "_FAST" command had already traveled. (Note that if it had been placed first, the cursor would have backpedaled out the other side.)