Liberation Circuit

Copyright 2016 Linley Henzell

Version: alpha 1

1. Introduction


Liberation Circuit is a game of defending/infiltrating computer systems (although it won't teach you anything about how to do this in the real world). It has singleplayer modes and also supports play-by-email multiplayer; networked multiplayer may be implemented sometime in the future.

You don't need to read this manual to play the game - it's mostly a detailed reference for advanced players, so if you just want to play then start up the game and play the missions.

Game settings (like the size of the game window, fullscreen, sound volume etc) can be changed by editing init.txt in the game directory.

Liberation Circuit is licensed under the GPL v3 (or any later version), and source code is available online. To build it you will need a C compiler (I use gcc) with the Allegro 5 library. See the comments in m_main.c for more information about building the game.

The procedural music generator is based on Batuhan Bozkurt's Otomata.


A note on security


This game executes user-supplied bytecode inside a simple virtual machine. I've done my best to make sure everything the bytecode interpreter does is bounds-checked, heavily supervised and as far away from system calls as practicable. But I'm not a professional programmer and I can't guarantee that the virtual machine is bulletproof when running unknown code by other people, especially in its current state as an untested alpha version. Exercise discretion when running other people's processes, particularly in bytecode form!

See also the Disclaimer and Limitation clauses of the GPL (in licence.txt in the main game directory).


Contents




2. Basic things


Your task is to defend (or, perhaps, attack) a computer system, represented on your display by a large rectangular arena.

In this world your will is executed by processes running on the system; processes can move, sense their surroundings, attack, reproduce and many other things. In some environments you can control them directly, while in others they are free to make their own decisions.

2.1 Processes


You can think of processes in two ways: they are the equivalents of units and structures in Liberation Circuit's real-time strategy game, and they are also small programs running in your computer's CPU (as bytecode in a virtual machine).

Each process has a core, which might look like this:

simple core

But a core by itself can't do much; it needs objects. Objects can be attached to the core at each of its links:

basic core

This process consists of a core, two move objects (which work like little rocket engines) and a pulse object to attack other processes with.

The code to generate this process (which the game can generate for you; you don't need to type it in manually) looks like this:

process header code

As well as the core, a process can have other components attached to the core by link objects, like this:

multi-component process

Or this:

large process




2.2 Templates


template panelA template contains the source code for a process' AI routines and the structure of the process' components. Builder processes use templates to build new processes, and a process from template 0 is automatically placed for you at the start of each game.

You can open the template panel by clicking on the little "Te" button near the top right of the screen. The template panel lets you choose which template is open in the designer and editor panels.

Templates can be unlocked or locked. A template can stay unlocked, and be edited or deleted, as long as no processes based on it exist in the game. A locked template can only be modified in limited ways (you can't change the process design, although you can recompile the code - see section 4.3 below on recompiling a locked template). You can use the design panel to lock and unlock templates, but when a process is placed in the game world its template is locked and cannot be unlocked as long as any processes based on the template remain.

To load a process from disk into a template, open its source code using the File menu in the editor panel. The source code will be automatically compiled (and the template locked) when a new process is built from the template. You can also compile source code manually.

You can edit the init.txt file (in the main game directory) to tell the game to automatically load files into templates when you start the game (see instructions in init.txt).


2.3 Process designer


design panel You can open the process designer panel by clicking on the little "De" button near the top right of the screen. The process designer lets you design a process without hand-coding the process definition.

When you create a new process (by clicking the "New" button that appears on the lower part of the panel when the current template is empty), only the core will appear. You can:


Designing a process is a matter of giving it a tactically effective structure, the right objects for its role in your force and enough power to run properly, while not making it too expensive.

Some process design considerations:

2.4 Code editor


editor panel To open the code editor, click on the little "Ed" button near the top right of the screen. 

The code editor is a basic integrated development environment for the game's built-in compiler. You don't have to use it - you can use another program entirely to edit your source files - but it has some features that can be useful, like test compilation, code completion for built-in keywords and a (partly implemented) help function (right-click on a built-in keyword).

The code editor always has the source code for the currently selected template. Use the template panel to change the selected template.


The File, Edit and Find menus contain basic editor functions.

The Compile menu has the following functions:
Test compile
This attempts to compile the currently open file. If successful, the results are discarded.
Compile
This attempts to compile the currently open tab, and if successful updates the design in the design panel. 
Compile+lock
Does the same as Compile, but if successful also locks the template so it's ready to be used in the game.
If an unlocked template is used to place a process in the game, it will automatically be compiled and locked.

Some limitations of the editor:

2.5 Menu interface


start menu When you first start the game, you are in the start-up screen.

The left side of the screen has a game menu with the following options:

Missions
Start playing a single-player mission. Completing a mission unlocks the next mission.
  • Advanced missions
  • Start playing an advanced single-player mission. Advanced missions are like standard missions, but you play in autonomous mode (which means that you have no direct control over your processes).
    Custom game
    Set up a custom game (including multiplayer).
    Exit
    Exit.

    3. Playing the game


    3.1 Missions and command mode


    Missions are pre-set single-player games. The first one is a tutorial.

    Normal missions start in command mode. In command mode, processes can accept commands from the user and act on them. The commands accepted are pretty standard RTS commands (they're mostly based on Starcraft).

    To select a process to give commands to, left-click on it. Multiple processes can be selected by holding the left mouse button to drag a box-select around them. Shift-clicking adds or removes processes from the selection. Double-clicking on a process selects all visible processes of the same type (i.e. built from the same template). There is a limit on the number of processes you can have selected at the same time (currently 32).

    The commands you can give are:

    Holding shift while giving commands queues them (up to 4 commands can be queued for each process).

    Builder processes (processes with build objects) have a separate set of build commands, with a separate queue - see the buttons that appear on the left side of the screen when you click on a builder process. Each button is for a different template.

    To give a build command, left-click on the button then left-click where the new process should be built. Hold the left mouse button to choose the angle of the new process. Right-click to cancel.

    Holding shift while giving build commands also queues them. Holding control while giving a build command tells the builder to build the same thing repeatedly.

    When a new process is built by an autocoded static builder process, any commands on the builder's command queue are copied to the new process. For example, if you tell a static builder to guard itself, anything it builds will guard it.


    3.2 Autonomous mode


    In autonomous mode, you can't give commands to your processes - they need to be programmed to make their own decisions. The autocoder isn't much help here; it can't make up complex AI.

    All of the enemies in the missions are designed to work in autonomous mode, so if you want examples of autonomous processes you can look at the source code in the missions/src directory (Note that in missions 5 and above the enemy cheats a bit, by getting multiple starting spawns or extra resources).


    3.3 Multiplayer


    Currently, play-by-email multiplayer with up to four players in autonomous mode is supported (simultaneous networked command-mode multiplayer may happen one day).

    The goal of a multiplayer game is to design a set of processes that will defeat the opponent's processes, without further input from you. Here's how to set up and play a multiplayer game:

    1. Each player (let's call them Alice and Bob) starts a game (with any settings) and loads process files into as many templates as needed.

    2. Alice and Bob each save a template file (using the "save template file" button at the top of the template panel). This contains the binary code for their templates.

    3. Alice and Bob send their template files to each other, decide that Alice will be player 0 and Bob will be player 1, and agree on which map code to use (you can make map codes on the Custom Game screen).

    4. Alice clicks on "Custom Game" on the main menu, enters the agreed map code, then clicks "Start Game". Bob does the same.

    5. Before clicking on the "Click when ready" button, Alice loads her template file into the player 0 templates and Bob's template file into the Player 1 templates (using the "load template file" button on the template panel). Bob also loads Alice's file into Player 0 and his file into Player 1.

    6. Now they both click "Click when ready", and watch to see who wins!

    A game with the same starting conditions (the same template files and the same map code) *should* unfold in exactly the same way on all computers.


    4. Compiler


    Liberation Circuit is in essence a programming game.

    The programming is done in a simplified version of C. The quickest way to get started with it is probably to use the autocoder to generate code for various different kinds of process, and look at the results. You could also look at the source code for the mission processes (look in the /proc directory).

    The game's built-in compiler is available from the code editor, and is also automatically called when an unlocked template is used to place a process into the game. It is not a very good compiler. It has a range of limitations and odd features, some dictated by the environment but most resulting from the fact that I don't really know how to write a compiler.

    (What I do know about how compilers work I learned from Serge Zaitsev's CUCU project (quote: "Never, please, never do it this way!"), which is the only explanation of compiler design that has made any sense to me. See http://zserge.com/blog/cucu-part1.html.)

    Some of the compiler's "special" characteristics are:

    The programs in the /proc and /missions directories, and programs generated by the autocoder, have plenty of comments, so you could look at those if you're trying to work out how to code in this environment.


    4.1 Keywords recognised by the compiler


    The compiler recognises some, but not all, basic C keywords, and has a few special ones of its own.

    These should work as expected:

    if/else

    do

    while

    switch/case/default

    break

    continue

    goto

    enum



    Some others work, but maybe not quite as expected, and there are a few special keywords:


    int
    This is the only data type currently supported. It's a signed 16-bit integer, from -32768 to +32767. Arrays of ints, with up to 3 dimensions, are supported.

    Each process has its own separate memory (large enough to hold up to 1024 ints), and all variables are initialised to 0 when a process is created then retain their value between execution cycles. This means that you can't combine variable declarations with assignments. So you can't do this:

    int a = 1; // doesn't work
    If you need to initialise variables, use special initialisation code that only runs once. For example:

    int initialised;
    int special_number;

    if (!initialised)
    {
     special_number = 100;
     initialised = 1; // makes sure that this code will only run once, the first time the process runs.
    }


    for
    Because statements and expressions aren't interchangeable, for statements must always be in the form for (<statement>; <expression>; <statement>). The first statement can't be a combined variable declaration and initialisation.

    This shouldn't be a problem for basic uses like  for (i = 0; i < 10; i++)  but may make some tricks difficult.


    printf
    Prints formatted text to the console on the bottom left of the game screen. For example:

    printf("counter is equal to %i.", counter); // assumes counter has been declared as an int.
    The only format specifier currently accepted is %i. Line breaks (\n) can be used too.


    exit
    Since there's no stdlib, there's no exit() function. The exit keyword (just exit; without brackets) is turned directly into a stop instruction, which ends execution.


    gosub/return
    Functions are not supported, but subroutines are! Gosub works just like goto, except that when a return is found the program jumps back to the next statement after the gosub. For example:

    gosub sub_routine_code;
     <next statement 1>
    gosub sub_routine_code;
     <next statement 2>
     <other code>
    exit;

    sub_routine_code:
      <subroutine code goes here>
     return; // jumps back to <next statement 1> or <next statement 2>

    Gosubs can be nested, within reason.


    #process "name"
     <process header goes here>
    #code

    These should appear only once, at the start of a program. #process is at the start of the header setting out the structure of the process, and #code is at the start of the code. "name" is the name that will be given to the process' template.

    The process header is usually generated from the designer, but you can edit it manually if you want to.


    class
    This keyword is used in the process header to designate object classes (and can also be used to call classes of objects by class index). See the object method section below for more about classes.


    Some other keywords aren't supported:

    variable types that are not int

    static

    auto

    const

    typedef

    sizeof

    restrict

    extern

    register

    volatile


    Also, the ternary ? operator isn't supported. Most other operators should be, and I *think* operator precedence should work as expected, except that bitwise operators bind more closely than logical and comparison operators (as they should!).



    4.2 Recompiling a locked template


    Although the process design of a locked template can't be changed, you can recompile the code to change the behaviour of processes created from it. This affects all processes based on the template, including any already created.

    There are a few things to be aware of when recompiling a locked template:


    4.3 Methods


    Methods are built-in functions, a bit like C library functions, that let a process interact with the world and perform complex operations. There are a few different kinds of methods:
    The next part of the manual is a list of all methods of each type.


    5. Standard methods


    5.1 Scanning and targetting


    Scanning methods allow a process to detect other nearby processes. Scans have a range of about 900 pixels (at maximum zoom level) in a rough circle (actually an octagon) around the process. The 'fog of war' clearing effect around a process is about the size and shape of its scanning area.

    The first time a scanning method is called during a cycle is quite expensive, because the process is building a list of all targets that are in range. Each subsequent call is cheaper, because the process just queries the list (this is all done implicitly).

    Each process has a special targetting memory which can store up to 64 targets. Once a target has been stored in targetting memory, process methods can be called on it when it is visible (within scanning range of any friendly process).



    scan_for_threat(x_offset, y_offset, target_index)
    Simple scan method that finds the nearest enemy core and saves it to targetting memory at index target_index.

    x_offset and y_offset let you choose the point from which proximity is calculated (for the purpose of working out which enemy is nearest). They can be any distance away from the core, although only targets actually within scanning range of the core itself will be detected.

    target_index is the address in targetting memory that the target should be stored in. Set this to -1 to discard the target.

    Returns 1 if a target was found, 0 if not.

    Example:

    target_found = scan_for_threat(cos(get_core_angle(), 400), sin(get_core_angle(), 400), 1);

    // searches for the enemy targest nearest to a point 400 pixels in front of the core, and saves it in targetting memory address 1. Returns 1 if a target was found.



    scan_single(x_offset, y_offset, target_index, friendly, components_min, components_max, scan_bitfield)
    A more complex scan method that allows more filtering of targets.

    x_offset, y_offset and target_index work the same way as for scan_for_threat().

    friendly should be:

    0 to accept only enemy targets;
    1 to accept both friendly targets and enemy targets;
    2 to accept only friendly targets.

    components_min and components_max let you scan for only small or large processes. A process with fewer components (including the core) than components_min or more than components_max is ignored. To detect processes of any size set components_min to 0 and components_max to any very high value (100 will do).

    scan_bitfield is a bitfield that allows you to set which targets will be detected in more detail. If one of the bits is set, any target that matches that bit will be accepted. If multiple bits are set, all targets that match at least one of the bits will be accepted. The bits are:

    0b1 - process is static
    0b10 - process is mobile
    0b100 - process has at least one build object
    0b1000 - at least one allocate object
    0b10000 - at least one gather object
    0b100000 - at least one pulse object
    0b1000000 - at least one spike object
    0b10000000 - at least one stream object
    0b100000000 - at least one burst object
    0b1000000000 - at least one interface object

    For example, 0b1100 has the bits for build objects and allocate objects set, so it will detect any process that has either a build object or an allocate object (objects on destroyed components are ignored) and ignore other processes.

    Returns 1 if a target found, 0 otherwise.

    Example:

    target_found = scan_single(cos(get_core_angle(), 400), // x_offset
                               sin(get_core_angle(), 400), // y_offset
                               TARGET_MAIN, // an enum indicating an address in targetting memory
                               0, // only detect enemy processes
                               0, // components_min - 0 means no minimum
                               12, // components_max - ignores processes with more than 12 components
                               0b100100000); // scan_bitfield - processes with either a pulse or burst object




    scan_multi(x_offset, y_offset, target_index, number_of_targets, friendly, components_min, components_max, scan_bitfield)
    Just like scan_single, except the number_of_targets parameter allows scanning for up to 6 targets. The targets will be stored in successive addresses in targetting memory starting from target_index, sorted by distance from the centre of the scan.

    Returns the number of targets found.

    target_clear(target_index)
    Clears targetting memory at address target_index.

    Returns 1 on success, 0 on error (e.g. if target_index is out of bounds).

    target_compare(target_index_1, target_index_2)
    Compares the targets at targetting memory address target_index_1 and target_index_2.

    If they are the same process, returns 1. Returns 0 otherwise.

    target_copy(target_index_dest, target_index_source)
    Copies the target at target_index_source to target_index_dest. If target_index_source is empty, target_index_dest will be cleared too.

    Returns 1 on success, 0 on failure.

    target_destroyed(target_index)
    Checks whether the target has recently been destroyed.

    If the target is (or was) friendly, returns 1 if the target was destroyed within 32 ticks before present.

    If the target is (was) an enemy, returns 1 if:
    - the target was destroyed within 32 ticks before present; and
    - the location at which the target was destroyed is currently visible (to any friendly process).

    Returns 0 otherwise.

    check_point(x_offset, y_offset, target_index)
    Checks for a process at a particular point; if a process is there (i.e. if any part of it overlaps the point) the process is stored in targetting memory address target_index.

    x_offset and y_offset are offsets from the calling core, and must be within scanning range of the calling core. However, this method does not execute a scan.

    Returns 1 if a process is detected, 0 if not, -1 on error.


    5.2 Power management


    A process generates a certain amount of power each cycle (the exact amount is determined by the core type) to use its objects and do various other things.

    Power is used by object method calls, and some objects have an ongoing cost which is applied just before each cycle starts. For example, an active interface object uses 10 power at the start of each cycle, and a pulse object uses 20 power when called to fire and also uses 20 power at the start of a cycle if it is still recycling from being fired.

    If the process runs out of power partway through executing, subsequent object method calls may fail. Failures like this show up as a red bar on top of the power use bar in the graph in the process data display.


    get_power_capacity()
    Returns the power generation capacity of the process' core (this is a constant value based on the core type).

    get_power_used()
    Returns the amount of power used so far this cycle.

    get_power_left()
    Returns the amount of power left.


    5.3 Contact and damage

    These methods are like a sense of touch, and return information about collisions and damage taken since the previous cycle.

    check_contact(target_index)
    Returns 1 if the process has collided with another process. If it has, saves the other process in targetting memory at the address indicated by target_index (set target_index to -1 if you don't want to save the target).

    If the process has collided with multiple other processes, the most recent one is saved.

    get_damage()
    Returns the amount of damage taken by the entire process since the previous cycle (including any damage taken by its interface).

    get_damage_source(target_index)
    If the process has taken damage (including to its interface) since the previous cycle, saves the source of that damage (e.g. a process that fired a pulse at it) to targetting memory at target_index then returns 1. Returns 0 if no damage taken.

    If multiple processes have caused damage, the most recent one to do so is saved.


    5.4 Data

    These methods allow a process to look for and examine nearby data wells (the source of the data that's used to build new processes).

    Some of these methods (the ones that involve interaction with data wells) can only be used by a process which has at least one harvest or build object (otherwise they will just fail and return 0 or -1).


    search_for_well()
    Searches for a well within scan range of the process. Returns 1 if a well is found, 0 otherwise.

    Requires a harvest or build object.

    get_well_x()get_well_y()
    If a well is within scan range, returns its x or y coordinate (as an absolute, not an offset from the process). Or returns -1 if no well is nearby.

    If more than one well is within scan range, the location of the closest one to the process is returned (the same is true of all of the following data well methods as well).

    Requires a harvest or build object.

    get_well_data()
    Returns the amount of data currently available (not in reserves) in the nearest well, or -1 if no well is within range.

    Requires a harvest or build object.

    get_data_stored()
    Returns the total amount of data stored in the process' data storage objects.

    get_data_capacity()
    Returns the maximum amount of data the process can store in its storage objects.

    get_available_data()
    Returns the amount of data available to the player for building new processes (this is the amount of allocated data shown in the information box at the top right of the display; it doesn't include unallocated data in storage objects on your processes).


    5.5 Messages and broadcasting

    These methods allow a process to communicate with other friendly processes by sending messages consisting of up to 8 ints.

    To send a message, a process either:

    - uses a transmit method to send the message to a friendly process in targetting memory; or
    - uses a broadcast method to send the message to all friendly processes, or all friendly processes within a specified range.

    Broadcast methods send the message on a particular channel, from 0 to 7. A process only receives a broadcast if it is listening to the channel (see the listen_channel message).

    Transmit methods do not use channels, and a process always receives a message transmitted to it specifically (unless its message buffer is full)

    To check and read its messages, a process:

    1. calls check_messages() to check whether any messages have been received
    2. if check_messages() returned at least 1, calls read_message() to read the first value sent in the first message
    3. then calls read_message() again to read the second, third etc. value in the message.
    4. calls next_message() to go to the next message. If next_message() returns 0, there are no more messages.

    A process' message received buffer can hold up to 8 messages received since the previous cycle, and is cleared at the end of each cycle. Transmissions and broadcasts use the same buffer.


    transmit(target_index, priority, <message_value_0>, <message_value_1>, ...)
    Sends a message to the process in targetting memory entry target_index. The target must be friendly, and can be any distance from the transmitter.

    priority is 0 or 1. The difference is that if the target already has 8 messages in its buffer, a priority 1 message will displace and overwrite a priority 0 message.

    The message_values are the contents of the message. There can be up to 8 of them; any that are not specified in the call are written as 0.

    Example:

    transmit(0, 1, 100, 2); // sends a message to target 0 with priority 1.   // The message is 100, then 2, then six zeroes.

    broadcast(range, channel, priority, <message_value_0>, <message_value_1>, ...)
    Like transmit, but sends the message to all friendly processes that are within range and are listening to the channel.

    range is in pixels and can be as high as needed. Set range to -1 for unlimited range.

    channel should be 0 to 7; the message will be received only by processes that have called listen_channel to listen to the correct channel.

    priority is 0 or 1.

    transmit_target(target_of_transmission, priority, target_to_transmit, <message_value_0>, <message_value_1>, ...)
    Like transmit, except an entry in targetting memory is attached to the transmission. The process that receives the transmission can copy the targetted process to its own targetting memory using the get_message_target() method.

    This can be used, for example, by processes that need to coordinate their attacks. If one process transmits an enemy target to other friendly processes, they can then target it directly as long as it is visible to any friendly process.

    target_of_transmission is the friendly process to which the transmission is being made.

    priority is 0 or 1.

    target_to_transmit is the index of the target in the transmitting process' targetting memory.

    The message_values are the same as for transmit(), and there can also be up to 8 of them;.

    Example:

    transmit(TARGET_FRIENDLY, 1, TARGET_ENEMY, 100, 2);
      // sends a message to target TARGET_FRIENDLY with priority 1.
      // the message is 100, then 2, then six zeroes.
      // target TARGET_FRIENDLY will also receive targetting information about TARGET_ENEMY.

    broadcast_target(range, channel, priority, target_to_broadcast, <message_value_0>, <message_value_1>, ...)
    Like broadcast(), but sends a target in the same way as transmit_target().

    check_messages()
    Returns the number of messages received by the process since the last time it executed, other than messages discarded by next_message().

    If there's more than one message, the messages will be in a queue in order of time received (although a priority 1 message that displaces a priority 0 message may be out of order).

    read_message()
    Returns the contents of the message at the front of the queue. Calling it more than once returns the second, third, etc value. Returns 0 if no message, or if the end of the message has been reached.

    next_message()
    Discards the message at the front of the queue and moves to the next message (which can then be read by read_message()). Returns 1 if there is a next message, or 0 if there are no more messages.

    get_message_type()
    Returns the type of the message at the front of the queue.

    Returns 1 for a message sent by transmit(), 2 for a message sent by transmit_target(), 3 for a broadcast() message and 4 if the message was sent by broadcast_target().

    Returns 0 if there is no message on the queue.

    get_message_channel()
    Returns the channel the message at the front of the queue was sent on.

    Returns 0 if the message was transmitted directly. Returns -1 if there is no message on the queue.

    get_message_priority()
    Returns the priority the message at the front of the queue was sent on. Returns -1 if there is no message on the queue.

    get_message_source(target_index)
    Saves the process that sent the message at the front of the queue to targetting memory at address target_index.

    get_message_target(target_index)
    If the message was sent by transmit_target() or broadcast_target(), this saves the target attached to the message to the process' own targetting memory, at target_index.

    Returns 1 on success, 0 if no target was attached or -1 on error.

    Note that this method returns 1 if a target was attached, even if the targetted process no longer exists.

    get_message_x()get_message_y()
    Returns the absolute x/y position of the process that sent the message at the front of the queue, at the time the message was sent. Returns -1 if there is no message on the queue.

    listen_channel(channel)
    Makes the process listen to a particular channel from 0 to 7. The process will listen to the channel permanently (or until it is told to ignore the channel). A process can listen to as many channels as needed.

    All channels default to being ignored, so if you want a process to receive messages it must use this method.

    ignore_channel(channel)ignore_all_channels()
    Makes the process ignore a particular channel, or all channels.

    5.6 Building new processes

    Standard methods (rather than object methods) are used to operate build objects, because a single build call activates all of a process' build objects at once (multiple build objects reduce the recycle period after which the process can build something again).

    check_build_range(x, y)
    A build object can build a new process anywhere within its scan range (only the core of the new process must be within range). This method checks whether the absolute location x,y is currently within range, and returns 1 if it is or 0 if not.

    build_process(template_index, x_offset, y_offset, angle, target_index)
    Tries to build a new process from template template_index. If the template is not locked, the compiler will automatically try to compile and lock it.

    The new process will be built at a location indicated by x_offset/y_offset (which are offsets from the builder process' core).

    There must be space at that location for the new process to be placed without overlapping another process. If there is not, the build call will fail but any mobile friendly process at the target location will be nudged away.

    If the new process is static, it must also be placed on the hexagonal tiles that cover most of the map (this just means that static processes can't be built right on top of data wells).

    The new process will be pointing in the direction indicated by angle.

    The builder can save the new process to its targetting memory at target_index (set target_index to -1 if this isn't needed).

    (The new process will automatically have the builder process at address 0 of its targetting memory.)

    This method has the following return values (errors will generally print an error message if the builder is selected, or if debug mode is on):

    1 - success
    0 - cannot build - process has no build objects
    -1 - template empty
    -2 - invalid template index
    -3 - (not currently used)
    -4 - the template couldn't be locked (e.g. because of a compiler error)
    -5 - player already has too many cores
    -6 - player already has too many components
    -7 - process would collide with an existing process
    -8 - build objects are recycling
    -9 - at least one component of the new process would be outside the map
    -10 - build location out of range of builder
    -11 - not enough data to build
    -12 - tried to build a static process too close to a data well
    -13 - builder doesn't have enough power to be able to build


    build_as_commanded(target_index)
    If the user has issued a build command to the process, this tries to build according to the command.

    Template index, location and angle are set by the command. Otherwise, this method works just like build_process().

    build_repeat(target_index)
    Tries to repeat the process' most recent attempt at building something. For example, if a process tries to build something but there isn't enough data, in the next cycle it can call build_repeat to try again without needing to repeat the build_process parameters.

    Otherwise, works just like build_process().

    get_template_cost(template_index)
    Returns the data cost of building from a template.


    5.7 Interface

    Since a process' interface is usually shared among several components, most of the methods used to control the interface are standard methods rather than object methods.

    The exception is set_interface(), which allows a process to activate and deactivate the interface on a single component (see object methods below).

    charge_interface(charge_amount)
    Charges the process' interface by charge_amount. Each cycle, a process can charge up to 2 points for each interface_depth object it has. If charge_amount is set to more than this, no more than the maximum value is used.

    Each point of interface strength costs 4 power.

    A process can only call this (or charge_interface_max()) once per cycle. Subsequent calls do nothing.

    Returns the amount charged.
     
    charge_interface_max()
    Like charge_interface, but without a set charge_amount; it just tries to put as much power as possible into the interface. Calling it is the equivalent of calling charge_interface with a very high charge_amount, but saves a few instructions.

    set_interface_general(setting)
    Call with a zero value to lower the interface for the entire process. While lowered the interface does not protect the process and interface objects do not consume power, but the interface retains its strength and can be charged. Calling with a zero overrides the state of individual interface objects set to on by the set_interface() object method.

    Call with 1 or any other non-zero value to raise the interface for the entire process, unless it's broken. Unlike calling with a zero value, this doesn't override the state of individual interface objects set to off by the set_interface() object method.

    The default state of an interface is raised, and while raised it will come back as soon as possible after being broken, so there's no reason to explicitly raise a process' interface unless it's previously been explicitly lowered by this method.

    5.8 Repairing and restoring

    Like the standard interface methods, the standard repair methods use all repair objects at once (it's not possible to call them separately).

    Having multiple repair objects increases the amount of repairing that can be done each cycle, and reduces the recycle period after restoring a destroyed component. They also increase the power cost.

    Having a repair_other object allows the process to repair other processes within scan range. A single repair_other object effectively turns all other repair objects into repair_other objects as well, and a repair_other object also functions as an ordinary repair object.


    repair_self()
    Looks for a damaged component of the process and, if found, repairs it. Searches in the order the components appear in the process header in the process' source code (so the core is always first).

    Each repair object repairs 1 integrity per cycle, and the power cost is 16 per point repaired.

    Returns the amount repaired, or -1 on error.

    restore_self()
    Looks for a destroyed component and, if found, tries to restore it. Searches in the same order as repair_self(). Will not work on a component that would collide with another process if restored.

    Costs 24 power, and has a recycle time based on the data cost of the component and any objects on it (although there is no actual data cost for restoring a component). While recycling, the object can still be used to repair.

    The new component is restored with only partial integrity, and can easily be destroyed again if not repaired or protected by an interface.

    Returns 1 if a component was restored, 0 otherwise.

    repair_other(target_index)restore_other(target_index)
    Like repair_self() or restore_self(), but repairs or restores the process in targetting memory indicated by target_index. The target must be within scan range.

    Requires at least one repair_other object.

    repair_other() returns the amount repaired, or -1 on error.
    restore_other() returns 1 on success, 0 on failure (if nothing was restored), -1 on error.

    repair_scan(x_offset, y_offset)restore_scan(x_offset, y_offset)
    These methods scan for a friendly process with a damaged or destroyed component, and try to repair or restore it. The closest suitable target to the position indicated by x_offset,y_offset (the offsets are from the repairing process' core) will be affected.

    These methods perform a scan if the process has not already scanned this cycle.

    Each requires at least one repair_other object.

    repair_scan() returns the amount repaired, or -1 on error.
    restore_scan() returns 1 on success, 0 on failure, -1 on error.


    5.9 Selection

    These only work in command mode, and return information about whether the user has selected the process (by clicking on it).

    check_selected()
    Returns 1 if the process is selected by the user, 0 if not.

    check_selected_single()
    Returns 1 if the process is selected by the user and no other process is selected by the user, 0 otherwise.


    5.10 Commands

    These methods only work in command mode, and allow the process to read commands (move, attack etc.) given by the user using mouse or keyboard controls.

    Each process has a queue that holds up to 4 commands (the user can queue commands by pressing shift). The command at the front of the queue is available for reading.

    Build commands are dealt with separately (see standard methods: build commands below).

    check_new_command()
    Returns 1 if the user has just, since the previous cycle, given a new command to the process (unless the new command went to the back of the queue).

    Also returns 1 if the process has called clear_command() to clear the current command, and there is another command on the queue.

    Returns 0 if there's no new command.

    get_commands()
    Returns the number of commands in the queue.

    get_command_type()
    Returns the type of the command at the front of the queue.

    Possible values are:

    0 - no command
    1 - location (user right-clicked on a location on the main display or the map; probably means a move command)
    2 - target (user right-clicked on an enemy process; probably means an attack command)
    3 - friend (user right-clicked on a friendly process)
    4 - data well (user right-clicked on a data well)
    5 - number (user pressed a number key)

    The source code for an autocoded process should contain an enum declaration setting out all of these.

    get_command_x()get_command_y()
    These return the location of the command at the front of the queue. If the target is a friendly process, returns the current location of the process.

    If the target is an enemy process that is within scan range of any friendly process, returns its location.

    Return -1 if no command, or if the target is an enemy process that is out of range, or if the target no longer exists.

    get_command_target(target_index)
    Saves the target of the command at the front of the queue to targetting memory at the address indicated by target_index.

    Only works if the command type is 2 (target) or 3 (friend).

    Returns 1 on success, 0 on failure.

    get_command_target_component()
    If the current command has a target process, returns the index of the component that was targetted (or 0 if that component doesn't exist). The result can, if needed, be fed into targetting methods (some of which allow a target component to be designated).

    Only works if the command type is 2 (target) or 3 (friend).

    Returns -1 on failure.

    get_command_number()
    A user issues a number command by pressing a number key (on the keyboard) while one or more processes are selected.

    This method returns the number (0 to 9) of a number command, or 0 on failure.

    get_command_ctrl()
    Returns 1 if control was pressed when the command at the front of the queue was given. Otherwise, returns 0.

    clear_command()
    Clears the command at the front of the queue. If another command is waiting on the queue, it moves to the front of the queue. The check_new_command() method can be used to check whether there is another command waiting.

    Returns 1.

    clear_all_commands()
    Clears the whole command queue.

    Returns 1.

    copy_commands(target_index)
    Copies all commands in the process' command buffer (not including build commands) to a target in its targetting memory.

    The main use for this method is to allow the user to give movement commands to a static builder process so that the builder can copy them to any processes it builds (autocoded static builder processes do this).

    5.11 Build commands

    Processes with build objects also have a separate build command queue (when a builder process is selected, the queue is displayed on the left of the display, above the buttons that allow build commands to be given).

    As with normal commands, build commands are queued by pressing shift. You may also be able to request a repeat build by pressing control (but the builder process must be coded to recognise this - autocoded builders will recognise it).

    You may be able to avoid using most of these methods by just using the build_as_commanded() method, which tries to comply with the build command at the front of the queue.


    check_new_build_command()
    Works just like check_new_command(), but for build commands.

    check_build_command()
    Returns 1 if there is at least one build command on the build command queue, or 0 otherwise.

    get_build_command_x()get_build_command_y()
    Returns the location of the build command at the front of the build command queue.

    The command isn't guaranteed to be within build range, and a mobile builder may choose to interpret an out-of-range build command as a command to move near the target location and build there.

    Returns 0 if no build command.

    get_build_command_angle()
    Returns the angle of the build command at the front of the build command queue (this is the direction the newly build process will point when it is first built, and can be set by the user dragging the mouse while placing the new process).

    Returns 0 if no build command.

    get_build_command_template()
    Returns the template index of the build command at the front of the build command queue.

    Returns 0 if no build command.

    get_build_command_ctrl()
    Returns 1 if the user pressed control when issuing the build command at the front of the queue. Otherwise, returns 0.

    Autocoded builders interpret this as a repeat-build command (the build command will be repeated until cancelled).

    clear_build_command()
    Clears the build command at the front of the queue, and moves the next one (if any) to the front.

    Returns 1.

    clear_all_build_commands()
    Clears all build commands.

    Returns 1.

    5.12 Mathematics


    Your processes will probably need basic trigonometry to get around their 2-dimensional world. Since processes can only use integer values, the maths methods use a 0-8192 integer angle format where 0 is right, 2048 is down, 4096 is left and 6144 is up. Values outside this range wrap around (so 8300 is the same as 108, and -2048 is the same as 6144).


    sin(angle, multiplier)cos(angle, multiplier)
    Returns the sine or cosine of angle, multiplied by the multiplier (the multiplier is needed because the return value would otherwise be a fraction that would just round to 0, 1 or -1).

    The main use of these methods is to get the y and x components of a vector of (multiplier) length pointing in (angle) direction. For example:

    int point_x, point_y, core_angle;core_angle = get_core_angle(); // this is the angle the process is pointing inpoint_x = get_core_x() + cos(core_angle, 400);point_y = get_core_y() + sin(core_angle, 400);  // point_x,point_y is now a point 400 pixels in front of the process

    atan2(y_component, x_component)
    Returns the arctangent of y_component/x_component, in integer angle format. This is an expensive operation, with an overhead of 16 instructions.

    Note the order of the y and x components, which is different from most other methods but is consistent with the C atan2 function.

    The main use of this method is to work out the angle between two points. For example:

    int target_x, target_y, target_direction;int core_x, core_y;core_x = get_core_x();core_y = get_core_y();target_x = process[1].get_core_x();target_y = process[1].get_core_y();target_direction = atan2(target_y - core_y, target_x - core_x);  // target_direction now holds the angle from the core of this process  //  to the core of the process in targetting memory address 1.

    Although atan2 is very useful, many of the object methods that deal with movement and targetting call it implicitly, so you may not need to actually use it very much. The target_angle() process method also often replaces the need for atan2.

    hypot(y_component, x_component)
    Returns the hypotenuse of y_component,x_component (that is, the square root of (y_component*y_component) + (x_component*x_component)). Use this for relatively precise calculations of distance (it uses an integer-based approximation of sqrt). Like atan2 it has an overhead of 16 instructions.

    distance_xy(x_component, y_component)
    Returns the distance from (0,0) to (x_component,y_component). Uses an octagonal distance algorithm that should be accurate enough for most purposes (and is much cheaper than hypot).

    distance_from_xy(x_target, y_target)
    Returns the distance between the process and the location at coordinates (x_target, y_target). Uses the same distance algorithm as distance_xy.

    distance_from_xy_less(x_target, y_target, distance)distance_from_xy_more(x_target, y_target, distance)
    Returns 1 if the distance between the process and the coordinates (x_target, y_target) is less (or more) than distance, 0 otherwise. Uses the same distance algorithm as distance_xy.

    abs(number)
    Returns the absolute value of number.

    arc_length(angle_1, angle_2)
    Returns the length (in absolute integer angle units) of the shortest arc between angle_1 and angle_2. Should deal correctly with arcs that wrap around 0 or 8192. The order of the parameters doesn't matter.

    One use for this method is to work out whether a process is pointing in more or less the right direction, e.g.:

    if (arc_length(core_angle, angle_to_target) < 600) // arc_length() always returns a positive number (or 0){ // do something

    angle_difference(angle_1, angle_2)
    Like arc_length, but returns the signed length of the shortest arc starting at angle_1 and ending at angle_2. Unlike arc_length, the order of the parameters matters. For example:

    int a;a = arc_length(1000, 700); // a = 300a = angle_difference(1000, 700); // a = -300a = angle_difference(700, 1000); // a = 300

    random(maximum)
    Returns a random number between 0 and (maximum - 1). Returns 0 if maximum is 0 or negative.

    The random number generator gets its entropy from a variety of sources, but should always produce the same series of numbers across multiple games with identical starting conditions (so the use of this method doesn't prevent multiplayer games unfolding in exactly the same way when run on different computers. I hope.)

    5.13 Miscellaneous



    attack_mode(setting)
    Controls the way classes of attacking objects will fire when called as a class. When attack mode is set it remains set (across cycles) until this method is used to change it again. Applies to all classes.

    Settings can be:
    0 - all objects will fire when called, if ready.
    1 - only the first object that is ready to fire will fire. Remaining objects will hold fire (but will continue to aim at the target if they can).
    2 - like 1, but two objects will fire.
    3 - three will fire.

    0 is the default. Returns 1.

    world_x()world_y()
    Return the size of the entire map, in pixels. Includes a buffer of about 255 pixels on each edge, which processes cannot enter.

    set_debug_mode(setting)
    Sets debug mode on (if setting = 1) or off (if setting = 0) until the end of the current process' execution.

    Debug mode currently just prints some error messages to the console, if certain kinds of errors are encountered (for example, it will print the results of a failed build method call). Autocoded processes set it to 1 if they are the only selected process.

    You can also set debug mode on for all processes by pressing F1.


    6. Process and component methods


    Process and component methods give a process useful information about itself, like its location and levels of damage.

    Unlike standard methods, a process can use the process keyword to call them on other processes as well. The other process must be in targetting memory and must be within scan range of a friendly process (not necessarily the process calling the method).

    For example, here is how to call the get_core_x() method, which returns the x coordinate of the location of a process' core:

    int x;
    x = get_core_x(); // returns the x coordinate of the calling process' core
    x = process[1].get_core_x(); // returns the x coordinate of the process
     // in targetting memory address 1

    Component methods use an additional component reference to get information about a specific component of a process. The automatically generated #process header of a process should have comments indicating the numbering of the process' components.

    int x;
    x = component[1].get_component_x(); // returns the x coordinate of component 1 of the process
    x = process[1].component[2].get_component_x(); // returns the x coordinate of component 2 of
     // the process in targetting memory address 1

    If the target is not visible or does not exist, a process or component method returns 0. The standard method target_destroyed() can be used to check whether a process has been destroyed.

    6.1 Process methods



    visible()
    Returns 1 if the target process is currently visible, 0 if the target is invisible or does not exist (or on error).

    Friendly targets are always visible. Enemy targets are visible if they are within scanning range of any friendly process.

    If a target is visible, other process methods can be called on it.

    get_core_x()get_core_y()
    Returns the x/y coordinates of the centre of the process core.

    If you look at a process in the design panel, the centre of the core is exactly in the middle of the window (where the vertical and horizontal lines meet).

    get_process_x()get_process_y()
    Returns the x/y coordinates of the centre of mass of the process. Probably not too useful, and should probably be renamed.

    get_core_angle()
    Returns the angle that the front of the process is pointing in, in integer angle units. The angle should always be between 0 and 8191.

    If you look at a process in the design panel, the front points to the right.

    get_core_spin()
    Returns the process' spin, in signed integer angle units. The return value is multiplied by 16 (making it spin per cycle rather than spin per tick) to make it more precise.

    get_core_speed_x()get_core_speed_y()
    Returns the x and y components of the process' speed. Like spin, speed is multiplied by 16.

    get_interface_strength()
    Returns the current strength of the process' interface. This can be a negative number (if the interface was broken by an attack that took its strength below zero).

    get_interface_capacity()
    Returns the capacity (maximum strength) of the process' interface. Returns 0 if the process has no interface.

    get_user()
    Returns the index of the user controlling the process (0 for player 0, 1 for player 1 etc.).

    get_template()
    Returns the index of the template the process was generated from.

    distance()
    Returns the distance between the core calling the method and the core on which the method is called (which will be 0 if they are the same). Uses an octagonal distance algorithm.

    distance_less(distance)distance_more(distance)
    Returns 1 if the distance between the core calling the method and the core on which the method is called is less (or more) than distance. Returns 0 otherwise.

    target_angle()
    Returns the angle between the core calling the method and the core on which the method is called.

    Costs 16 instructions (as it involves calculating an arctangent).

    get_components()
    Returns the number of components the process has, including the core but not including any destroyed components.

    get_components_max()
    Returns the number of components the process has, including any destroyed components.

    get_total_integrity()
    Returns the total current integrity of the process (the sum of the current integrity of all components).

    get_total_integrity_max()
    Returns the maximum integrity of the process, not counting any destroyed components.

    get_unharmed_integrity_max()
    Returns the maximum integrity of the process, counting any destroyed components.

    6.2 Component methods

    There are currently only a few component methods, but more are planned.

    component exists()
    Returns 1 if the component exists, 0 otherwise.

    get_component_x()get_component_y()
    Returns the x and y coordinates of the component.

    get_integrity()
    Returns the integrity of the component.

    get_integrity_max()
    Returns the maximum integrity of the component.


    7. Objects, object methods and classes


    Some kinds of objects can be invoked by calling standard methods, which affect all objects of the same type on the process. For example, when a build method is called it operates all of the process' build objects at the same time.

    Other kinds of objects, such as attacking objects, need to be operated individually or in groups. These call be called with specific reference to the object, or through a class.

    An object can be called directly like this:

    component[0].object[2].fire(1); // calls the fire method on object 2 of component 0 (the core)
    But this is awkward. It's usually much easier to assign objects to classes and call the classes instead.

    7.1 Classes

    A class is a set of objects that can all be called together.

    Each process can have up to 16 classes.
    Each class can have up to 16 objects as members.
    Each object can be a member of up to 4 classes.

    Objects are assigned to classes in a process header. First, the class is declared using the class keyword. Then, objects are assigned to classes with the : operator.

    Here's what a process header for a simple process generated by the autocoder might look like:


    #process "simple"



    class auto_att_main; // a class for forward-facing fixed attack objects
    class auto_move; // a class for movement objects
     // all auto-coded classes start with "auto"

    core_quad_A, 0, // core type and angle
      {object_burst:auto_att_main, 0},
      {object_move:auto_move, 2048},
      {object_none, 0},
      {object_move:auto_move, -2048},



    #code


    The burst object at the front is assigned to a class for the main attacking object or objects (a class doesn't need to have more than one member). The move objects are assigned to a single move class (the move object methods work out which ones need to be called to make the process turn left or right).

    To call a class, give the name of the class, followed by a full stop, following by the object method. For example:

    auto_att_main.fire(1); // fires all objects in class auto_att_main, with a 1 tick delay

    auto_move.move_to(1000, 1000); // all move objects in class auto_move will cooperate in
     // turning the process towards 1000,1000 then accelerating

    You can also call a class by number (classes will be numbered in the order they're declared in, from 0 to 15):

      class[1].fire(1); // fires all objects in class 1

    An object can be assigned to multiple classes like this:

      {object_burst:auto_att_main:burst_class, 0}, // this object is a member of both
        // the auto_att_main class and the burst_class class


    7.2 Objects and object methods


    This section lists the different types of objects and the particular objects in each type, and also the methods that can be used to operate the objects.

    7.2.1 Link objects


    Link objects connect the components of a process together.

    Link objects don't have methods.

    Downlink (object_downlink)

    Downlinks connect the core to components, and then components to other components. In the process designer, adding a downlink automatically creates a new component connected to the downlink.

    If a component is destroyed, all other components downlinked from it are also destroyed.

    Uplink (object_uplink)

    Each component (other than the core) has an uplink that connects to a downlink on its parent component.

    A component can have only one uplink; in the process designer, adding an uplink to a component swaps the component's uplink to the new position.

    7.2.2 Data objects

    harvestingData objects are needed to run your economy. They gather, generate and store data, and use it to create new processes.


    Harvest (object_harvest)

    Harvest objects gather data from nearby data wells. To harvest data, a process must also have a storage object to keep the data in until it can be allocated.

    Harvest objects can also transfer data to other processes.

    gather_data()
    This method gathers data from the nearest data well within scanning range (if there is one). It automatically detects the nearest well.

    Each harvest object can gather up to 4 data each time it is used, with a 4-cycle recycle time.

    Returns the amount of data harvested, or -1 on failure.

    Power cost: 40

    give_data(target_index, data_amount)
    Transfers data to a friendly process, which must be within scanning range.

    target_index is the address of the target in targetting memory. data_amount is the amount of data to transfer. Each harvest object can transfer up to 32 data each cycle (although if data_amount is too high it will just use the maximum value). If the method fails, or the target doesn't have enough storage, no data is lost.

    Returns the amount of data transferred, or -1 on failure.

    Power cost: 20

    Storage (object_storage)


    Each storage object stores up to 64 data. If a component with a storage object is destroyed, its data is spread among any remaining storage objects on the same process (or is lost if there are no others with enough spare capacity to store the data).

    Storage objects have no methods and use no power.

    Allocate (object_allocate)

    Data stored by a process can't be used to build anything directly. To make it available for building (and make it count in your overall data amount, shown in the box at the top right of the game screen) it needs to be allocated. An allocate object can allocate 4 data each cycle, from storage objects on the same process.

    Allocate objects are expensive, and can only be placed on static (immobile) processes.

    allocate_data(data_amount)
    Allocates up to 4 data.

    Returns the amount of data allocated.

    Power cost: 10 per data allocated.


    Build (object_build)



    Build objects can build new processes, using data that has been harvested and allocated.

    A new process can be built anywhere within scan range (the core of the new process needs to be within range; components can be out of range).

    Build objects aren't called individually with object methods. Instead, a process uses the build standard methods to call all build objects it has at once.

    After being used, a build object stops working for a period of time depending on the data cost of the new process. A process with multiple build objects splits the recycle time (so two build objects halve it, etc), and also splits the time the new process takes to be ready.

    7.2.3 Movement objects

    There is currently only one kind of movement object:

    Move (object_move)

    move objectsMove objects are like little rocket engines, generating both acceleration and torque (turning force).

    Move objects must not be obstructed by other components of the same process. In the designer, each move object has a line extending from it and each component has a square around it; if any of the lines are blocked by any of the squares, the process can't be built.

    Move objects also can't be placed on the same component as an interface object (although interface_depth objects are fine).

    Although you can set the power of each move object individually (using the set_power() method), you don't have to. The other move methods will calculate the angle the process needs to move in and set the power of each move object called to turn in that angle and accelerate towards the target. This means that you can call all move objects as a single class.

    The power cost of each method depends on the power that the move objects are set to (up to 10 per object). This power is used when the method is called, although if for some reason you call multiple methods on the same move object in a single cycle any unused power is freed for other use (so if you call set_power(10) 10 units of power will be used, then if you call set_power(4) later in the same cycle 6 units will be freed).

    Each method returns 1 on success, or 0 on failure.

    set_power(power)
    Sets the power level of each move object called, from 0 (off) to 10 (maximum).

    move_to(destination_x, destination_y)
    Sets the power level of each move object called so that the process turns towards the destination and then moves towards it.

    turn_to_xy(destination_x, destination_y)
    Turns the process towards the destination.

    Depending on how the move objects are arranged on the process, this method (and the other methods that turn without deliberately moving) may also make the process move as it turns.

    turn_to_angle(angle)
    Turns the process so that it points in the direction indicated by angle.

    turn_to_target(target_index, component)
    Turns the process towards the process in the targetting memory address indicated by target_index. Component indicates which component should be targetted (usually this is 0, for the core).

    track_target(target_index, component, attack_class)
    Like turn_to_target, but aims a fixed attacking process at the target (using a basic target-leading algorithm).

    attack_class should be the name of a class containing fixed attacking objects (burst, stream etc., but not spike or pulse). track_target() will try to aim the first member of the class (ignoring any that would be on components that have been destroyed) at the target.

    For example:

    #processclass attack_class; class move_objects;// the rest of the process header goes here#codemove_objects.track_target(1, 0, attack_class); // tries to aim the first object in class attack_class
     // at component 0 (the core) of target 1


    intercept(target_index, component, attack_class)
    Like track_target, but moves the process to intercept the target as well as turning it.

    approach_xy(destination_x, destination_y, approach_distance)
    Like move_to(), but the process will try to stay at least approach_distance (in pixels) away from the destination.

    All of the approach methods work best if called on a class of move objects including some that face forwards (so that they can move the process backwards if needed).

    approach_target(target_index, component, approach_distance)
    Approaches the target in the targetting memory address indicated by target_index. A particular component of the target can be given as well. approach_distance works in the same way as for approach_xy.

    approach_track(target_index, component, attacking_class, approach_distance)
    A combination of approach_target() and intercept(). Tries to aim an attack object in class attacking_class at the target, while moving so as to remain approach_distance pixels away from the target.


    7.2.4 Attacking objects


    attacking objectsAttacking objects are used to destroy enemy processes.

    You can control the way attacking objects fire when called as a class using the attack_mode standard method.

    Burst  (object_burst)

    Large burst (object_burst_l)

    X-Large burst (object_burst_xl)

    Burst objects fire malicious packets to try and damage enemy processes (they won't hit friendly processes). Each object can fire once each 4 cycles (64 ticks).

    Large burst objects are like burst objects, but fire more powerful and slower packets and cost more data to build and more power to use.

    XL burst objects each do more damage but are slower etc.

    Burst objects use power when set to fire by a method call, and then use the same amount of power each cycle until they are ready to fire again.

    Calling the same object multiple times in the same cycle incurs the calling cost each time, so avoid that! (This only happens for methods that actually set the object to fire - calling no_target() or aim_at() does not cost power.)

    Burst objects have only one object method, fire(). They are also affected by the attack_mode() standard method.

    fire(firing_delay)
    Instructs the object to fire. Firing_delay is the number of ticks (frames) the object is to wait before firing; it should be 0 to 15 (as there are 16 ticks between each cycle). Values below 0 will be set to 0 (which means fire immediately) and above 15 set to 15.

    Pulse (object_pulse)

    Large pulse (object_pulse_l)

    X-Large pulse (object_pulse_xl)

    Pulse objects are like burst objects, but can rotate in a 180 degree arc to aim at targets around the process.

    Pulse objects also cost more data to build than burst objects, and are slightly less powerful.

    Pulse objects can use the same methods as burst objects, but also have special methods that aim them at their target:

    rotate(angle_offset)
    Rotates the object to a specified angle offset (the offset is from the zero angle, which for most links points directly away from the centre of the component the object is on). angle_offset should be -2048 to 2048.

    aim_at(target_index, component)
    Aims the object at a process in targetting memory at target_index (component is the component to aim at; 0 is the core), without firing. The target must be visible, but can be at any distance.

    This method automatically applies a simple target-leading algorithm.

    fire_at(target_index, component)
    Like aim_at(), but fires the object as well (if it is able to fire this cycle).

    attack_scan(scan_angle, scan_distance, target_index)
    Automatically scans for a target and then fires at it.

    scan_angle and scan_distance are a vector indicating the centre of the scan; the enemy process closest to the centre will be targetted. scan_angle should be an offset from the process' overall angle (so if you want to always scan behind the process, to find a target for backwards-pointing pulse objects, use something like 4096). scan_distance is the distance from the core, in pixels (a value like 400 pixels might be right for this).

    The target will be stored in target_index (which must be a valid index in targetting memory).

    Executes a scan if the process hasn't previously scanned in the present cycle.

    Returns 1 if an appropriate target is present, 0 otherwise.

    attack_scan_aim(scan_angle, scan_distance, target_index)
    Like attack_scan, but just aims the object and doesn't fire.

    no_target()
    Tells the object to return to its base angle. The base angle is the angle it is pointing in the process design (which may or may not be the zero angle that rotate(0) would rotate the object to).


    Stream (object_stream)

    A powerful and expensive beam-like attack.

    Uses the same methods as burst objects.

    Directional stream (object_stream_dir)

    A version of the stream object that can aim at targets, but is more expensive and a bit less powerful.

    Uses the same methods as pulse objects.

    Spike (object_spike)

    A long range attacking object which can hit targets outside scanning range. After being fired the spike turns and accelerates towards its target, although it has no tracking capability.

    Spike objects can't be rotated, and always point outwards at the zero angle for the link they are on.

    The autocoder currently doesn't use spike objects very effectively, and can only use spike objects that point more or less forwards.

    fire_spike(angle_offset)
    Fires a spike at angle_offset, which should be -2048 to 2048.

    fire_spike_at(target_index, component)
    Fires a spike at a specified component of a target in targetting memory. Because of the way spikes travel, this kind of attack is not particularly accurate (and is best used against large or static targets).

    fire_spike_xy(x_target, y_target)
    Fires a spike at a target location. x/y_target should be absolute values (not offsets from the firing process).

    7.2.5 Defensive/repair objects


    Interface (object_interface)

    An interface is a rechargeable shield that protects parts of a process from damage. Each component that should be covered by the process' interface needs its own interface object, and the process also needs at least one interface depth object.

    A core component can't have an interface object. Also, a component cannot have both move objects and an interface object.

    A process' interface is mostly controlled using the interface standard methods rather than object methods, but there is one object method for controlling individual interface objects, or classes of them.

    set_interface(setting)
    Turns a particular interface object on or off. The off setting overrides set_interface_general(1) (but the on setting does not override set_interface_general(0)).

    Since an interface costs 10 power per component per cycle to keep raised, it can be worthwhile switching it off for some components if it's not needed.

    Interface depth (object_interface_depth)

    To actually generate an interface, a process needs one or more interface depth objects. Each one adds a certain amount to the overall strength of the interface, which is shared by all components with interface objects. Each one also increases the rate at which the interface can be recharged.

    One or more interface depth objects can be placed on any component.

    Repair (object_repair)

    A repair object allows a process to repair damage to any of its components, and also restore destroyed components.

    Repairing damage costs quite a bit of power and is fairly slow, but is sped up by additional repair objects.

    Restoring a destroyed component costs a lot of power too, and has a recycle time. The length of the recycle depends on the data cost of the restored component, and is reduced by additional repair objects. Restoring a component doesn't cost data.

    Repairing/restoring methods are standard methods, and call all repair objects on a process at once.

    Repair other (object_repair_other)

    This is like a repair object, but allows a process to repair other nearby processes as well as itself. It is also more expensive.

    A single repair_other object allows all repair objects on the process to be used as repair_other objects as well.