Plan for input recording and playback
The input recording file format is a simple stream of binary codes. I was tempted to use a RELOAD file, but decided against it. Input recording and playback is always be one way linear progression through a stream, so it doesn't make sense to have a random-access structured file, especially with whatever overhead that involves. I still might have tried it, but allmodex does not already depend on reload, and I didn't want to add that dependency just for this.
Also, whenever we change the format and that breaks playback of old input files, that is no disaster, since input recordings are fragile by nature and will break for a ton of other reasons too.
I know this format isn't super efficient in terms of storage space, but that isn't what I care about. I am focusing on implementation simplicity and clarity.
Contents
.ohrkey file format[edit]
byte size | data type | description |
---|---|---|
12 | string | "OHRRPGCEkeys" header |
4 | int32 | version code, currently 4. Playback should refuse files if this this indicates a format that won't be understood. |
8 | double | random number generator seed used for this recording session |
? | tickblocks | a stream of 0 or more variable-sized tickblocks |
tickblock[edit]
byte size | data type | description |
---|---|---|
4 | int32 | tick number. Ticks will always be in order, and ticks where no keys changed will be skipped. |
1 | byte | milliseconds in the previous tick (setkeys-setkeys interval) (capped at 255ms). This is needed for determining key-repeat events. |
1 | byte | number of keys stored in this tickblock |
? | keyblocks | a stream of 0 or more keyblocks, of the number specified above. |
1 | byte | number of characters of text input during this tick |
? | string | text input during this tick, Latin-1 encoding (one byte per character). Number of characters specified above. |
keyblock[edit]
A keyblock is only stored for each keybd() element that has changed since the last tick
byte size | data type | description |
---|---|---|
1 | byte | key index |
1 | byte | keybd value (never bigger than 7) |
Proposal for Version 5 changes[edit]
This proposal is for adding a 'command stream' to the file format, which can be used both to store results of script commands which are nondeterministic, as well being able to store other arbitrary data (of arbitrary type) in the file without having to modify (and bloat) the file format. Every recorded value is prefixed with the ID of the script command that generated it, both as a sanity check and to allow producing meaningful dumps.
For example, the "sound is playing" script command could be implemented like this:
CASE 236'--sound is playing IF play_input ANDALSO replay_cmdstream_int(236, scriptret) THEN 'nothing to do ELSE DIM sfxid as integer = backcompat_sound_id(retvals(0)) IF sfxid >= 0 AND sfxid <= gen(genMaxSFX) THEN scriptret = sfxisplaying(sfxid) END IF IF recinput THEN record_cmdstream_int 236, scriptret END IF
We could also consider switching from 4 byte ints to RELOAD variable length integers, to save two bytes per tickblock for the first 7 minutes of the recording.
tickblock[edit]
As above, but appended with:
byte size | data type | description |
---|---|---|
1 | byte | Number of cmdblocks |
? | cmdblocks | Variable number; each as below |
cmdblock[edit]
byte size | data type | description |
---|---|---|
2 | bit * 13 (signed) | (Least significant bits) Command id. Positive values refer to script commands, negative values contain other things. Used to check that the data being read is from the expected source and of the expected type, though this is not strictly required. |
bit * 3 | (Most significant bits) Type of data following: 0: integer, encoded using RELOAD VLI scheme | |
? | ? | As encoded above |
For example, one likely candidate for a negative command ID is
cmd id | data type | description |
---|---|---|
-1 | double | TIMER. Written and read by a function wrapping use of FB's TIMER function for use in places where it affects the state of the game, eg playtimer |
Possible additional features[edit]
- hotkeys for Record/playback of macros.
- should game/custom automatically record keys internally for including with crash reports? (what about passwords in custom?)
- TAS features
- Automatically switch from playback to record mode on the same file when a key is pressed.
- Playback at max speed, but switch to normal speed X seconds before the end of the ohrkey file
- Record mouse input
- After Plan for improved joystick support is finished, joystick input should be recorded automatically, with no extra work.
Known determinism problems[edit]
- Mouse and joystick input
- Script commands like "system seconds", "seconds of play" and "milliseconds"
- We could write a wrapper for the TIMER command that dumps the timer into the ohrkey stream when recording, and fetches the timer from the stream when playing back.
- Audio functions like "sound is playing"
- Global and function-local STATIC variables which cause differences if multiple games are played without quitting Game:
- random_formation
- targ and spread in menu_attack_targ_picker?