diff --git a/port/linux/game/network_distributed.c b/port/linux/game/network_distributed.c index 864faa83..0dff3601 100644 --- a/port/linux/game/network_distributed.c +++ b/port/linux/game/network_distributed.c @@ -256,6 +256,11 @@ enum _distributed_unit_part_killer_bit, }; +/* world units: how far apart the host's run of a client's player and the +client's may be, a tick from the same place, and agree (distributed_divergence: +well past the last bits' rounding, well short of a player running 1% faster) */ +#define DIVERGENCE_APART_DISTANCE 0.0001f + /* world units: how far a client's own player's unit may be from the host's before the host takes it no longer, and before the client is put where the host has it (no further than that: between the two they would disagree for @@ -707,6 +712,35 @@ static struct long received; long corrections; } distributed_statistics; +/* ... and (the host) how far apart a client's player moved in a tick on the +client and on the host, where the host took the client's last prediction +the tick before, that was of the client's tick before, and the host's tick +ran the player with the client's input of that tick: both machines then +moved the player one tick with the same input from much the same place, so +builds that simulate alike move it alike to the last bit (but where the +player touched something the two have in different places), and a build +that moves a player otherwise moves it otherwise, every tick the player +moves (tools/net_compare.py). The move, not where it ends: the host's +copy is put only halfway to a prediction a little off, and would be as far +off the next for ticks after. Since network_distributed_divergence last +read them: how many such ticks, how many of them further apart than +DIVERGENCE_APART_DISTANCE, and the mean and the largest distance. */ +static struct +{ + long samples; + long apart; + real total; + real largest; + /* the tick of the client's and of the host's last prediction taken */ + long taken_time[MAXIMUM_TRACKED_PLAYERS]; + long taken_host_time[MAXIMUM_TRACKED_PLAYERS]; + /* where that prediction had the player, and where it left the host's */ + real_point3d taken_position[MAXIMUM_TRACKED_PLAYERS]; + real_point3d taken_host_position[MAXIMUM_TRACKED_PLAYERS]; + /* the client's tick of the latest input the host has of each player + (what the host's tick ran it with) */ + long input_time[MAXIMUM_TRACKED_PLAYERS]; +} distributed_divergence; /* ---------- shared (network_distributed.h) */ @@ -726,6 +760,22 @@ void distributed_count_correction( distributed_statistics.corrections++; } +void network_distributed_divergence( + long *samples, + long *apart, + real *mean, + real *largest) +{ + *samples = distributed_divergence.samples; + *apart = distributed_divergence.apart; + *mean = distributed_divergence.samples ? distributed_divergence.total / (real)distributed_divergence.samples : 0.0f; + *largest = distributed_divergence.largest; + distributed_divergence.samples = 0; + distributed_divergence.apart = 0; + distributed_divergence.total = 0.0f; + distributed_divergence.largest = 0.0f; +} + boolean distributed_real_valid( real value) { @@ -2131,6 +2181,28 @@ static void distributed_take_prediction( dx = position.x - object->object.position.x; dy = position.y - object->object.position.y; dz = position.z - object->object.position.z; + /* (for the tests' reports: a tick on from the last taken, on both, with + the input the client's tick ran: an input's tick is the client's time + before it ran, a prediction's after) */ + if (distributed_divergence.taken_host_time[player_index] == now - 1 && + distributed_divergence.taken_time[player_index] == distributed_predictions[player_index].time - 1 && + distributed_divergence.input_time[player_index] == distributed_predictions[player_index].time - 1) + { + real_point3d const *client_from = &distributed_divergence.taken_position[player_index]; + real_point3d const *host_from = &distributed_divergence.taken_host_position[player_index]; + real mx = (position.x - client_from->x) - (object->object.position.x - host_from->x); + real my = (position.y - client_from->y) - (object->object.position.y - host_from->y); + real mz = (position.z - client_from->z) - (object->object.position.z - host_from->z); + real distance = (real)sqrt(mx * mx + my * my + mz * mz); + + distributed_divergence.samples++; + if (distance > DIVERGENCE_APART_DISTANCE) + distributed_divergence.apart++; + distributed_divergence.total += distance; + if (distance > distributed_divergence.largest) + distributed_divergence.largest = distance; + } + distributed_divergence.taken_host_time[player_index] = NONE; if (!(dx * dx + dy * dy + dz * dz <= HOST_ACCEPT_TOLERANCE * HOST_ACCEPT_TOLERANCE)) return; if (distributed_accepted[player_index].valid) @@ -2169,6 +2241,10 @@ static void distributed_take_prediction( { distributed_predictions[player_index].taken_time = distributed_predictions[player_index].time; distributed_predictions[player_index].taken_host_time = now; + distributed_divergence.taken_time[player_index] = distributed_predictions[player_index].time; + distributed_divergence.taken_host_time[player_index] = now; + distributed_divergence.taken_position[player_index] = position; + distributed_divergence.taken_host_position[player_index] = object->object.position; /* (a new anchor: the first, one a second on, or after a jump) */ if (!distributed_accepted[player_index].valid || now - distributed_accepted[player_index].host_time >= PREDICTION_ANCHOR_TICKS) @@ -2507,6 +2583,12 @@ static void distributed_handle_inputs( network_coop_note_player_structure_bsp(input->player_index, input->structure_bsp_index); update_server_handle_distributed_input(DATUM_INDEX_NEW(input->player_index, player->identifier), input->tick, &action, input->control_flags, DISTRIBUTED_INPUT_HISTORY); + /* (for the tests' reports: the newest, as the input taken is) */ + if (input->tick > distributed_divergence.input_time[input->player_index] || + input->tick < distributed_divergence.input_time[input->player_index] - TICKS_PER_SECOND) + { + distributed_divergence.input_time[input->player_index] = input->tick; + } if (input->host_time != NONE && (host_time == NONE || input->host_time > host_time)) host_time = input->host_time; } diff --git a/port/linux/game/network_test.c b/port/linux/game/network_test.c index 50c9a4ad..6bf8f705 100644 --- a/port/linux/game/network_test.c +++ b/port/linux/game/network_test.c @@ -23,7 +23,10 @@ Automated system link sessions for testing the netcode without the menus plays one without the menus) is logged, as below. Once the game runs, every second each machine logs where every player's -unit is, so the machines' views of the game can be compared. +unit is, so the machines' views of the game can be compared, and the host +how far its own run of each client's player came from the client's +(network_distributed_divergence): tools/net_compare.py compares two builds +so (port/linux/NETCODE.md, "Comparing two builds", has the line's format). Scripted play for the netcode's parts the bots' wandering does not reach: debug.network_test_kill (the host kills the last player every so often), @@ -91,6 +94,7 @@ void platform_log(char const *format, ...); void damage_kill_object_for_player(long object_index, long player_index); /* network_distributed.c's */ void network_distributed_statistics(long *sent, long *received, long *corrections); +void network_distributed_divergence(long *samples, long *apart, real *mean, real *largest); void network_distributed_item_statistics(long *creates, long *deletes, long *failures, long *removed); void network_damage_statistics(long *sent_reports, long *dealt_reports, long *rejected_reports, long *replayed_events); /* xinput_sdl.c's */ @@ -407,9 +411,14 @@ static void network_test_log_players( spawns leaves it where it began) */ long local_player_index = local_player_get_player_index(0); struct observer_result const *camera = observer_get_camera(0); + long divergence_samples, divergence_apart; + real divergence_mean, divergence_largest; + + network_distributed_divergence(&divergence_samples, &divergence_apart, &divergence_mean, &divergence_largest); platform_log("network test: tick %ld%s | items %ld (+%ld -%ld !%ld x%ld) | %s to %ld | sent %ld received %ld corrected %ld" - " | hits %ld dealt %ld rejected %ld replayed %ld | local %ld camera (%.1f %.1f %.1f) respawn %ld", + " | hits %ld dealt %ld rejected %ld replayed %ld | local %ld camera (%.1f %.1f %.1f) respawn %ld" + " | drift %ld %ld %.6f %.6f", game_time_get(), line, ground_items, creates, deletes, failures, removed, game_engine_can_score() ? "playing" : "game over", game_engine_running() ? (long)game_engine_get_variant()->universal_variant.score_to_win : 0L, @@ -417,7 +426,8 @@ static void network_test_log_players( sent_reports, dealt_reports, rejected_reports, replayed_events, local_player_index == NONE ? -1L : (long)DATUM_INDEX_TO_ABSOLUTE_INDEX(local_player_index), camera ? camera->position.x : 0.0f, camera ? camera->position.y : 0.0f, camera ? camera->position.z : 0.0f, - local_player_index == NONE ? 0L : (long)player_get(local_player_index)->respawn_timer); + local_player_index == NONE ? 0L : (long)player_get(local_player_index)->respawn_timer, + divergence_samples, divergence_apart, divergence_mean, divergence_largest); } }