First, an apology: I have a distinct memory of asking this before, and Dannii answering with a link to a repo with everything written down. But none of my searches can find that topic anywhere!
Anyway…
As I continue messing with the Å-machine web interpreter, it would be nice to hook it up to IFComp’s automatic transcripting features. The web interpreter actually already has a setup for this sort of logging (it’s just disabled by default); but its protocol looks different from the Parchment one.
How the Å-machine one works
It keeps track of two numbers. pos is the total number of characters sent to the server; servpos is [???].
On every input, it sends an AJAX request:
data: {
session: [game]-[date]-[time]-[random number]
text: [raw text of the transcript from `pos` to the end]
pos: `servpos`
}
Whatever the server sends back is saved as the new value of servpos. Then pos is set to the end of the current transcript.
It doesn’t seem like it should be too hard to adapt to the IFComp Parchment format; but, I don’t know what the IFComp Parchment format is. Is there documentation of this protocol somewhere?
(The Å-machine implementation will also be opt-out the same way as Parchment. Nothing will be recorded if the user doesn’t want it to be.)
session: [date][time][random]
log: {
inputcount: [total number of inputs]
outputcount: [total number of outputs...how is that different?]
input: [what the player typed]
output: [what the game printed in response]
window: [???]
styles: [???]
timestamp: [date][time]
}
Fantastic, thank you! The only thing I don’t understand from the README is the timestamp and outtimestamp fields. These are the Unix times when the input was submitted, and when the game finished producing output afterward?
All right! I have a basic implementation up and running. It’ll need more testing before it goes live, but it should take the data injected by IFComp and use it to send data in the appropriate format.
Like with Parchment, if cookies (transcript_recording_opt_out=1) or query parameters (?nofeedback) indicate that the player does not want their transcripts sent, it will be disabled. Unlike with Parchment, there will also be a checkbox in the menu that can enable or disable it at any time.
The checkbox will only appear if remote feedback is set up (i.e. parameters are supplied either by the author or by the IFComp website), and will start out unchecked if transcript_recording_opt_out=1 or ?nofeedback is supplied, otherwise checked.
Feedback is only sent on player input, so flicking the checkbox on and off experimentally will not immediately send all your past logs. Flicking it on, typing a command, and then flicking it off will send all your past logs, unless that would be more than 50kB, in which case it assumes the server doesn’t want that much data at once and gives up.
Sessions should also be maintained across closing and reopening the tab, though that needs a bit of fire-testing.
Hopefully that’s a solution that works for everyone!
Yes because echoing input is up to the storyfile. (And I guess for simplicity.) A server could use the input to identify what the command was, but it’s not needed.
Yep.
It injects ParchmentOptions, though in this case it’s just a few of the GlkOteOptions that are relevant.
Note though that the IFComp won’t inject anything if it doesn’t know about the format. So if you’re wanting to make auto transcripting work for Dialog the IFComp site will need to be taught the format and what to do to it.
sub _enable_recording {
my ($self) = @_;
my $play_file = $self->content_directory->file('play.html');
my $game_file = $self->inform_game_file->basename;
unless ( ( -e $play_file ) && $game_file ) {
# Look for index.html as well
$play_file = $self->content_directory->file('index.html');
unless ( ( -e $play_file ) && $game_file ) {
# No play.html or index.html? OK, this isn't a standard I7 "with interpreter" arrangement,
# so we won't do anything.
return;
}
}
my $play_html = $play_file->slurp;
my $options_js_object;
if ( $play_html =~ m{game_options.*</head>}s ) {
# It's Quixe
$options_js_object = 'game_options';
}
else {
# It's Parchment
$options_js_object = 'parchment_options';
}
# Activate transcription, aiming it at the local transcription action.
# (Via injecting additional values into the game_options config object.)
my $entry_id = $self->id;
my $transcription_code = <<EOF;
<script>
if ($options_js_object) {
$options_js_object.recording_url = '/play/$entry_id/transcribe'
$options_js_object.recording_format = 'simple'
}
</script>
EOF
$play_html =~ s{</head>}{$transcription_code</head>};
$play_file->spew($play_html);
}
Which looks like it should inject two values into either game_options (if the word game_options appears literally in the document head) or parchment_options (if it doesn’t).
So I’ve currently added this block to the document head:
<script>
game_options = {}; // The IFComp server injects some data into here
</script>
And at setup time, if the script finds a recording_url key in that object, it copies it into options.aaLogServerPath and sets options.aaLogFormat to 'ifcomp'.
Is that the right way to go about it? Unfortunately I don’t know how exactly IFComp recognizes different formats, and I have no real way to test this until IFComp season starts (by which point the deadline will be very tight).
(In particular, it seems like this code also looks for inform_game_file to decide whether to inject the code, but I can’t figure out how it actually makes that determination…)