Normal field operations.
The maximum number of field operations per group is 8 a frame, as long as the instruction is not told to wait (for example, some Move operations must complete before the next instruction is allowed to execute, like an NPC walking from A to B.).
8 field operations that have no delay (i.e., the instruction doesn't have to wait for something like a Move operation to complete) will execute for one group, each frame, where possible. This is not affected by how many total groups there are. This means that each group will execute 30 * 8 instructions a second at a maximum (240), meaning 48 * 240 (11520) instructions a second, at the most, for all groups. Each group is given control, one to the next, starting at ID 0. If it has already reached a return, the return will simply be executed on the group's every turn from that point on. All groups are therefore given control once a frame, whether they end up doing 8 instructions or 1. There will always be at least one instruction per group, each frame, even if it's the final return of group's Main. A return is therefore seen by the engine as nothing more than a "no operation - go to next group".
If the instruction is waiting for an action to complete, it is still executed again, but the opcode's function (in the exe) works out what to do. Only when the function itself is satisfied that the operation has been completed will it mark the instruction as completed. It does this simply by advancing the script's progress by a number of bytes (the number of bytes that the opcode + arguments required).
Jumps: When a jump backwards instruction is executed, the current group is skipped and the next group is automatically given its turn. For example, if you have 8 instructions in Group 0, but the 4th one is a jump backwards, it will execute the 4th instruction for that frame, move to the next group (Group 1), and only continue with the 5th instruction for Group 0 on the next frame. There are other instructions that also have this effect.
Init and Main both have their own functions to execute instructions in field. Inits of all groups are executed once (when you enter a field), starting from Group ID 0, at 0060C78D. Placing instructions here that require a delay will also cause a delay in the field loading (which is why these types of instructions should be avoided in the init sections). Using loops inside an init section will hang the field indefinitely. Unlike the Main function, Init will execute all instructions of a group's init section before moving to the next group. In other words, it doesn't execute 8 instructions a frame, then move to the next group. Assuming it is still executing 8 instructions a frame in total, this does mean that a very large list of instructions in the init section will also cause a delay in the field opening (240 instructions would be 1 second delay - IF the Init function is working like Main in terms of execution time). This many total instructions for the groups' inits is very unlikely to happen (and the original game didn't come close to it), so it's not a worry regardless. I'd like to think that Init executes as fast as your processor will allow. It very likely does.
Main's function is at 0060CDAE.
Instruction Progress
Each of the 48 groups must keep their current instruction location saved, so that when focus moves to other groups and returns, the game can continue where it left off. Each group is assigned a 2 byte value to store the instruction offset, starting at 00CC0CF8. The first instruction will not be at value 0 (byte 0), because the instruction progress value includes the offset to the actual instruction list, which begins after other data (such as the field header). The current group's instruction progress value is retrieved at 60CD98.
mov dl,[ecx+eax]
Where ecx is the address of the field data itself, eax is the offset to the instructions of the field from that address (The instruction progress value), and dl is the opcode retrieved (FF7 field only has one byte opcodes) from that location. The asm code there is a little dumb(?), but that's probably because optimization was turned off for the executable:
xor edx,edx
mov dl,[ecx+eax]
and edx,000000FF <<< redundant.
The parameters for the operations are retrieved from inside the opcode's function itself (which is easy, since it's just the addresses after the opcode) . For example, the 8 bit jump forward opcode (0x10) retrieves the one-byte jump value at 61311B.
Because the instruction progress value is only 2 bytes, there is a limit to how big a field script can be. Even if the headers and other data were discounted, the limit would still be 65535 bytes for ALL instruction data (for all groups). As it is, it seems to me that 65535 bytes is actually the maximum size an uncompressed field DATA section can be (not to be confused with the uncompressed field size total), and so the number of operations available per field is even more limited. It's pretty difficult to exhaust this limit, even with 48 large groups. Even 100 addition (arithmetic) operations, for 48 groups (5 * 100 * 48 [24000] bytes of data) is not a problem. The game will crash / behave incorrectly if it is not able to properly advance the instruction progress value, as it just did when I deliberately added 5 * 1000 * 48 (240000 bytes) of addition operations.
Execute a Script (Async Wait, Async NoWait, Sync Wait, Priority)
This is probably the most confusing part of field programming for modders. That's because no-one has really written anything about how the engine actually deals with these functions.
Regardless of whether a script is called or not, the field engine still loops from ID 0 to the last ID. If a script has been called for, say, ID 6, then ID 6 will try to execute 8 instructions from that script. This mean that its MAIN script is superseded. So, the following:
Execute script #2 in external group Untitled (No6) (priority 1/6) - Only if the script is not already running
means that Group 6 (ID 6) will begin running through its Script 2 list of instructions INSTEAD OF its Main instructions when ID 6 gets its turn (remember, each ID/Group gets one turn every frame). Once all the instructions in Script 2 have been completed, it automatically switches back to its own Main. This is a difficult thing to explain with words, so, at a later time, I will make a video.
The 'Execute a Script' instruction has 3 modes:
Sync Wait, Async Wait, Async No Wait.
Sync Wait:
Execute script #2 in group Untitled (No6) (priority 1/6) - Waiting for end of execution to continue
Each loop (each frame) will execute up to 8 instructions in ID 6. Until this is done, the calling script (for example, ID 0 > Main) will not continue when it gets its turn.
Async Wait:
Execute script #2 in external group Untitled (No6) (priority 1/6)
Each loop (each frame) will execute up to 8 instructions in ID 6. If ID 6 is already busy running through one of its scripts from a previous 'Execute a Script' instruction, the calling script (for example, ID 0 > Main) will not continue when it gets its turn. So, this instruction differs with Sync Wait only because it will wait if an ID is busy running through a called script, and not wait otherwise. This means that Async Wait, like Sync Wait, is guaranteed to eventually run.
Async No Wait:
Execute script #2 in external group Untitled (No6) (priority 1/6) - Only if the script is not already running
This is the same as Async Wait, except... it doesn't wait. The next instruction (of the script that is calling the 'Execute a Script' instruction) will always be executed as soon as possible. If the request for ID 6 (in my example) to run through Script 2 cannot be granted, it will never be initiated. This instruction should only be used when you require a script to be skipped if already running.
Some things to consider:
- If a target ID's script is looped / does not end, Async Wait and Sync Wait will cause the calling ID's execution to be put on hold permanently.
- If an ID has one of its scripts (Script 0-31) looped / unable to complete, all calls to execute any of its other scripts from any other location will fail. An ID can only have one of its scripts (Script 0-31) executing at a time.
- Every time an ID has one it its scripts (Script 0-31) executing, it will be in place of its Main script (Script 0). Only when all script call operations are completed will the Main script resume for that ID.
The game queues any scripts to be run at 612B5F
mov [ecx+eax*8+00CBF9E8],dl
Where ecx is the priority (0-6), eax is the ID (0-47), and dl is the script (0-31) that has been told to execute. This means that each ID can queue up to 7 "Execute a Script" instructions. Priorities are, basically, queued scripts to be run - with a priority order. Although parts of the engine support 0-7 queued items, only 0-6 will work. If priority 7 is called, it will be completely ignored.
Note: Calling Script 0 of an ID is supported, but it will be the "init" part of Script 0 (since it always ends with a return). It is not recommended nor essential to ever do this.