From 64aff1893d2c5adce024ae2e05e63f3a26a6b7b3 Mon Sep 17 00:00:00 2001 From: jacob Date: Mon, 19 Jan 2026 02:33:07 -0600 Subject: [PATCH] more prediction testing --- src/pp/pp.c | 383 ++++++++++++++++++------------------ src/pp/pp_vis/pp_vis_core.c | 116 ++++++++++- 2 files changed, 303 insertions(+), 196 deletions(-) diff --git a/src/pp/pp.c b/src/pp/pp.c index ce9d5a40..6907adf1 100644 --- a/src/pp/pp.c +++ b/src/pp/pp.c @@ -1351,9 +1351,6 @@ void P_ClearFrames(P_World *world, i64 tick_min, i64 tick_max) P_Frame *P_PushFrame(P_World *world, P_Frame *src_frame, i64 tick) { - Assert(!(src_frame->world == world && tick <= src_frame->tick)); // Can't read from tick that is being overwritten by new tick - P_ClearFrames(world, tick, I64Max); - P_Frame *frame = world->first_free_frame; if (frame) { @@ -1395,12 +1392,6 @@ P_Frame *P_PushFrame(P_World *world, P_Frame *src_frame, i64 tick) frame->constraints_count = 0; frame->constraints = PushStructsNoZero(world->frames_arena, P_Constraint, frame->max_constraints); } - - u64 hash = MixU64(tick); - P_FrameBin *bin = &world->frame_bins[hash % world->frame_bins_count]; - - DllQueuePushNPZ(&P_NilFrame, world->first_frame, world->last_frame, frame, next, prev); - DllQueuePushNPZ(0, bin->first, bin->last, frame, next_in_bin, prev_in_bin); } // Copy ents @@ -1422,6 +1413,17 @@ P_Frame *P_PushFrame(P_World *world, P_Frame *src_frame, i64 tick) ++frame->ents_count; } + // Clear frames + P_ClearFrames(world, tick, I64Max); + + // Insert frame + { + u64 hash = MixU64(tick); + P_FrameBin *bin = &world->frame_bins[hash % world->frame_bins_count]; + DllQueuePushNPZ(&P_NilFrame, world->first_frame, world->last_frame, frame, next, prev); + DllQueuePushNPZ(0, bin->first, bin->last, frame, next_in_bin, prev_in_bin); + } + return frame; } @@ -1525,8 +1527,7 @@ void P_StepFrame(P_Frame *frame) for (P_Ent *guy = P_FirstEnt(frame); !P_IsEntNil(guy); guy = P_NextEnt(guy)) { - // if (guy->is_guy && (!is_predicting || guy == local_guy)) - if (guy->is_guy) + if (guy->is_guy && (!is_predicting || guy == local_guy)) { P_Control control = guy->control; @@ -1603,8 +1604,176 @@ void P_StepFrame(P_Frame *frame) + ////////////////////////////// + //- Generate guy-on-guy constraints + + + + // TODO: Not like this + + for (P_Ent *ent0 = P_FirstEnt(frame); !P_IsEntNil(ent0); ent0 = P_NextEnt(ent0)) + { + if (ent0->is_guy) + { + P_Shape shape0 = P_WorldShapeFromEnt(ent0); + for (P_Ent *ent1 = P_FirstEnt(frame); !P_IsEntNil(ent1); ent1 = P_NextEnt(ent1)) + { + if (ent1->is_guy && ent1->key.v > ent0->key.v) + { + P_Shape shape1 = P_WorldShapeFromEnt(ent1); + + // TODO: World query + P_CollisionResult collision = P_CollisionResultFromShapes(shape0, shape1); + if (collision.collision_points_count > 0) + { + // FIXME: Key lookup + P_Constraint *constraint = 0; + { + b32 match = 0; + for (i64 constraint_idx = 0; constraint_idx < frame->constraints_count; ++constraint_idx) + { + constraint = &frame->constraints[constraint_idx]; + if (P_MatchKey(constraint->ent0, ent0->key) && P_MatchKey(constraint->ent1, ent1->key)) + { + match = 1; + break; + } + } + if (!match) + { + if (frame->constraints_count < frame->max_constraints) + { + constraint = &frame->constraints[frame->constraints_count]; + frame->constraints_count += 1; + ZeroStruct(constraint); + } + } + } + if (constraint) + { + constraint->flags = P_ConstraintFlag_Gentle | P_ConstraintFlag_NoWarmStart; + constraint->last_touched_tick = frame->tick; + constraint->normal = collision.collision_normal; + // constraint->friction = SqrtF32(ent0->friction * ent1->friction); + constraint->friction = 0; + + // TODO: Real masses + f32 inv_m0 = 10; + f32 inv_m1 = 10; + f32 inv_i0 = 0; + f32 inv_i1 = 0; + + // Treat non-predicted guys as infinite-mass + if (is_predicting && ent0 != local_guy) + { + inv_m0 = 0; + } + if (is_predicting && ent1 != local_guy) + { + inv_m1 = 0; + } + + constraint->ent0 = ent0->key; + constraint->ent1 = ent1->key; + // constraint->static_center1 = shape1.center_of_mass; + + constraint->inv_m0 = inv_m0; + constraint->inv_m1 = inv_m1; + constraint->inv_i0 = inv_i0; + constraint->inv_i1 = inv_i1; + + // Delete old contacts that are no longer present + for (i32 contact_point_idx = 0; contact_point_idx < constraint->points_count; ++contact_point_idx) + { + P_ContactPoint *contact = &constraint->points[contact_point_idx]; + u32 id = contact->id; + b32 match = 0; + for (i32 collision_point_idx = 0; collision_point_idx < collision.collision_points_count; ++collision_point_idx) + { + if (collision.collision_points[collision_point_idx].id == id) + { + match = 1; + break; + } + } + if (!match) + { + // Delete contact by replacing with last in array + *contact = constraint->points[constraint->points_count - 1]; + constraint->points_count -= 1; + contact_point_idx -= 1; + } + } + + // Create / update contacts from collision + for (i32 collision_point_idx = 0; collision_point_idx < collision.collision_points_count; ++collision_point_idx) + { + P_CollisionPoint collision_point = collision.collision_points[collision_point_idx]; + + u32 id = collision_point.id; + P_ContactPoint *contact = 0; + { + for (i32 contact_point_idx = 0; contact_point_idx < constraint->points_count; ++contact_point_idx) + { + P_ContactPoint *tmp = &constraint->points[contact_point_idx]; + if (tmp->id == id) + { + contact = tmp; + break; + } + } + if (!contact) + { + contact = &constraint->points[constraint->points_count]; + constraint->points_count += 1; + ZeroStruct(contact); + } + } + contact->id = id; + + Vec2 vcp0 = SubVec2(collision_point.p, shape0.center_of_mass); + Vec2 vcp1 = SubVec2(collision_point.p, shape1.center_of_mass); + + contact->vcp0 = vcp0; + contact->vcp1 = vcp1; + contact->starting_separation = collision_point.separation; + + // Debug draw + // { + // // P_Ent *ent0 = P_EntFromKey(frame, constraint->ent0); + // // P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1); + // Vec2 normal = constraint->normal; + // Vec2 center0 = Zi; + // Vec2 center1 = Zi; + // if (!P_IsEntNil(ent0)) center0 = P_WorldShapeFromEnt(ent0).center_of_mass; + // if (!P_IsEntNil(ent1)) center1 = P_WorldShapeFromEnt(ent1).center_of_mass; + // Vec2 p0 = AddVec2(center0, vcp0); + // Vec2 p1 = AddVec2(center1, vcp1); + // P_DebugDrawPoint(p0, Color_Cyan); + // P_DebugDrawLine(p0, AddVec2(p0, normal), Color_White); + // } + } + } + } + } + } + } + } + + + + + + + + + + + + + // ////////////////////////////// - // //- Generate guy-on-guy constraints + // //- Generate solid constraints @@ -1650,7 +1819,7 @@ void P_StepFrame(P_Frame *frame) // } // if (constraint) // { - // constraint->flags = P_ConstraintFlag_Gentle | P_ConstraintFlag_NoWarmStart; + // constraint->flags = P_ConstraintFlag_Solid; // constraint->last_touched_tick = frame->tick; // constraint->normal = collision.collision_normal; // // constraint->friction = SqrtF32(ent0->friction * ent1->friction); @@ -1663,14 +1832,15 @@ void P_StepFrame(P_Frame *frame) // f32 inv_i1 = 0; // // Treat non-predicted guys as infinite-mass - // // if (is_predicting && ent0 != local_guy) - // // { - // // inv_m0 = 0; - // // } - // // if (is_predicting && ent1 != local_guy) - // // { - // // inv_m1 = 0; - // // } + // if (is_predicting && ent0 != local_guy) + // { + // inv_m0 = 0; + // } + // if (is_predicting && ent1 != local_guy) + // { + // inv_m1 = 0; + // } + // constraint->ent0 = ent0->key; // constraint->ent1 = ent1->key; @@ -1762,175 +1932,6 @@ void P_StepFrame(P_Frame *frame) - - - - - - - - - - ////////////////////////////// - //- Generate solid constraints - - - - // TODO: Not like this - - for (P_Ent *ent0 = P_FirstEnt(frame); !P_IsEntNil(ent0); ent0 = P_NextEnt(ent0)) - { - if (ent0->is_guy) - { - P_Shape shape0 = P_WorldShapeFromEnt(ent0); - for (P_Ent *ent1 = P_FirstEnt(frame); !P_IsEntNil(ent1); ent1 = P_NextEnt(ent1)) - { - if (ent1->is_guy && ent1->key.v > ent0->key.v) - { - P_Shape shape1 = P_WorldShapeFromEnt(ent1); - - // TODO: World query - P_CollisionResult collision = P_CollisionResultFromShapes(shape0, shape1); - if (collision.collision_points_count > 0) - { - // FIXME: Key lookup - P_Constraint *constraint = 0; - { - b32 match = 0; - for (i64 constraint_idx = 0; constraint_idx < frame->constraints_count; ++constraint_idx) - { - constraint = &frame->constraints[constraint_idx]; - if (P_MatchKey(constraint->ent0, ent0->key) && P_MatchKey(constraint->ent1, ent1->key)) - { - match = 1; - break; - } - } - if (!match) - { - if (frame->constraints_count < frame->max_constraints) - { - constraint = &frame->constraints[frame->constraints_count]; - frame->constraints_count += 1; - ZeroStruct(constraint); - } - } - } - if (constraint) - { - constraint->flags = P_ConstraintFlag_Solid; - constraint->last_touched_tick = frame->tick; - constraint->normal = collision.collision_normal; - // constraint->friction = SqrtF32(ent0->friction * ent1->friction); - constraint->friction = 0; - - // TODO: Real masses - f32 inv_m0 = 10; - f32 inv_m1 = 10; - f32 inv_i0 = 0; - f32 inv_i1 = 0; - - // Treat non-predicted guys as infinite-mass - // if (is_predicting && ent0 != local_guy) - // { - // inv_m0 = 0; - // } - // if (is_predicting && ent1 != local_guy) - // { - // inv_m1 = 0; - // } - - - constraint->ent0 = ent0->key; - constraint->ent1 = ent1->key; - // constraint->static_center1 = shape1.center_of_mass; - - constraint->inv_m0 = inv_m0; - constraint->inv_m1 = inv_m1; - constraint->inv_i0 = inv_i0; - constraint->inv_i1 = inv_i1; - - // Delete old contacts that are no longer present - for (i32 contact_point_idx = 0; contact_point_idx < constraint->points_count; ++contact_point_idx) - { - P_ContactPoint *contact = &constraint->points[contact_point_idx]; - u32 id = contact->id; - b32 match = 0; - for (i32 collision_point_idx = 0; collision_point_idx < collision.collision_points_count; ++collision_point_idx) - { - if (collision.collision_points[collision_point_idx].id == id) - { - match = 1; - break; - } - } - if (!match) - { - // Delete contact by replacing with last in array - *contact = constraint->points[constraint->points_count - 1]; - constraint->points_count -= 1; - contact_point_idx -= 1; - } - } - - // Create / update contacts from collision - for (i32 collision_point_idx = 0; collision_point_idx < collision.collision_points_count; ++collision_point_idx) - { - P_CollisionPoint collision_point = collision.collision_points[collision_point_idx]; - - u32 id = collision_point.id; - P_ContactPoint *contact = 0; - { - for (i32 contact_point_idx = 0; contact_point_idx < constraint->points_count; ++contact_point_idx) - { - P_ContactPoint *tmp = &constraint->points[contact_point_idx]; - if (tmp->id == id) - { - contact = tmp; - break; - } - } - if (!contact) - { - contact = &constraint->points[constraint->points_count]; - constraint->points_count += 1; - ZeroStruct(contact); - } - } - contact->id = id; - - Vec2 vcp0 = SubVec2(collision_point.p, shape0.center_of_mass); - Vec2 vcp1 = SubVec2(collision_point.p, shape1.center_of_mass); - - contact->vcp0 = vcp0; - contact->vcp1 = vcp1; - contact->starting_separation = collision_point.separation; - - // Debug draw - { - // P_Ent *ent0 = P_EntFromKey(frame, constraint->ent0); - // P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1); - Vec2 normal = constraint->normal; - Vec2 center0 = Zi; - Vec2 center1 = Zi; - if (!P_IsEntNil(ent0)) center0 = P_WorldShapeFromEnt(ent0).center_of_mass; - if (!P_IsEntNil(ent1)) center1 = P_WorldShapeFromEnt(ent1).center_of_mass; - Vec2 p0 = AddVec2(center0, vcp0); - Vec2 p1 = AddVec2(center1, vcp1); - P_DebugDrawPoint(p0, Color_Cyan); - P_DebugDrawLine(p0, AddVec2(p0, normal), Color_White); - } - } - } - } - } - } - } - } - - - - ////////////////////////////// //- Prune constraints @@ -2230,7 +2231,7 @@ void P_StepFrame(P_Frame *frame) for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) { - // if (!is_predicting || ent == local_guy) + if (!is_predicting || ent == local_guy) { Xform xf = ent->xf; xf.og = AddVec2(xf.og, MulVec2(ent->solved_v, solver_dt)); diff --git a/src/pp/pp_vis/pp_vis_core.c b/src/pp/pp_vis/pp_vis_core.c index f4db2532..35dcc5f4 100644 --- a/src/pp/pp_vis/pp_vis_core.c +++ b/src/pp/pp_vis/pp_vis_core.c @@ -3238,7 +3238,8 @@ void V_TickForever(WaveLaneCtx *lane) } // Predict - P_Frame *predict_frame = &P_NilFrame; + P_Frame *predict_frame = predict_world->last_frame; + if (frame->predict_to != prev_frame->predict_to) { if (predict_world->tiles_hash != sim_world->tiles_hash) { @@ -3253,7 +3254,7 @@ void V_TickForever(WaveLaneCtx *lane) for (i64 predict_tick = first_predict_tick + 1; predict_tick <= last_predict_tick; ++predict_tick) { predict_frame = P_PushFrame(predict_world, predict_world->last_frame, predict_tick); - P_Ent *predict_player = P_EntFromKey(predict_frame, local_player->key); + P_Ent *predict_player = P_EntFromKey(predict_frame, V.player_key); if (!P_IsEntNil(predict_player)) { P_Control *predict_control = &local_controls[predict_tick % max_local_controls]; @@ -3271,13 +3272,18 @@ void V_TickForever(WaveLaneCtx *lane) // } b32 old_debug_draw = P_tl.debug_draw_enabled; - P_tl.debug_draw_enabled = TweakBool("Debug draw intermediate prediction steps", 0); - P_StepFrame(predict_frame); + Vec4 old_debug_tint = P_tl.debug_tint; + { + P_tl.debug_draw_enabled = TweakBool("Debug draw intermediate prediction steps", 0); + P_tl.debug_tint = VEC4(0.8, 0, 0, 0.25); + P_StepFrame(predict_frame); + } P_tl.debug_draw_enabled = old_debug_draw; + P_tl.debug_tint = old_debug_tint; } predict_frame = predict_world->last_frame; - P_DebugDrawFrame(predict_frame); + // P_DebugDrawFrame(predict_frame); // TODO: Extract information that occurred between first & last prediction, like bullet hits etc? } @@ -3344,6 +3350,104 @@ void V_TickForever(WaveLaneCtx *lane) + + + // ////////////////////////////// + // //- Update corrected world + + + + // P_Frame *correct_frame = &P_NilFrame; + // { + // if (correct_world->tiles_hash != predict_world->tiles_hash) + // { + // correct_world->tiles_hash = predict_world->tiles_hash; + // CopyStructs(correct_world->tiles, predict_world->tiles, P_TilesCount); + // tiles_dirty = 1; + // } + // correct_world->seed = predict_world->seed; + + // P_ClearFrames(correct_world, I64Min, I64Max); + // correct_frame = P_PushFrame(correct_world, predict_world->last_frame, predict_world->last_frame->tick); + // } + + + + + + // // P_Frame *correct_frame = correct_world->last_frame; + // // { + // // if (correct_world->tiles_hash != predict_world->tiles_hash) + // // { + // // correct_world->tiles_hash = predict_world->tiles_hash; + // // CopyStructs(correct_world->tiles, predict_world->tiles, P_TilesCount); + // // } + // // correct_world->seed = predict_world->seed; + + // // if (correct_world->last_frame->tick != predict_frame->tick) + // // { + // // correct_frame = P_PushFrame(correct_world, correct_world->last_frame, predict_frame->tick); + // // } + + // // // P_ClearFrames(correct_world, I64Min, correct_world->last_frame->tick - 1); + // // // P_Frame *prev_correct_frame = correct_world->last_frame; + + // // { + // // f32 correction_rate = 1; + // // // f32 correction_rate = 30 * frame->dt; + // // P_EntList spawn_ents = Zi; + // // for (P_Ent *predict_ent = P_FirstEnt(predict_frame); !P_IsEntNil(predict_ent); predict_ent = P_NextEnt(predict_ent)) + // // { + // // P_Ent *correct_ent = P_EntFromKey(correct_frame, predict_ent->key); + // // if (P_IsEntNil(correct_ent)) + // // { + // // correct_ent = P_PushTempEnt(frame->arena, &spawn_ents); + // // *correct_ent = *predict_ent; + // // } + // // else + // // { + // // // TODO: Unified blend logic between local world & correct world + // // correct_ent->xf = LerpXform(correct_ent->xf, predict_ent->xf, correction_rate); + // // correct_ent->solved_v = LerpVec2(correct_ent->solved_v, predict_ent->solved_v, correction_rate); + // // correct_ent->solved_w = LerpF32(correct_ent->solved_w, predict_ent->solved_w, correction_rate); + // // } + // // } + // // P_SpawnEntsFromList(correct_frame, spawn_ents); + // // } + + // // // Prune ents + // // { + // // i64 ents_to_prune_count = 0; + // // P_Ent **ents_to_prune = PushStructsNoZero(frame->arena, P_Ent *, correct_frame->ents_count); + // // for (P_Ent *ent = P_FirstEnt(correct_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) + // // { + // // if (ent->exists <= 0 || P_IsEntNil(P_EntFromKey(predict_frame, ent->key))) + // // { + // // ents_to_prune[ents_to_prune_count] = ent; + // // ents_to_prune_count += 1; + // // } + // // } + // // for (i64 prune_idx = 0; prune_idx < ents_to_prune_count; ++prune_idx) + // // { + // // P_Ent *ent = ents_to_prune[prune_idx]; + // // P_EntBin *bin = &correct_frame->ent_bins[ent->key.v % correct_frame->ent_bins_count]; + // // DllQueueRemoveNP(bin->first, bin->last, ent, next_in_bin, prev_in_bin); + // // DllQueueRemoveNPZ(&P_NilEnt, correct_frame->first_ent, correct_frame->last_ent, ent, next, prev); + // // correct_frame->ents_count -= 1; + // // SllStackPush(correct_world->first_free_ent, ent); + // // } + // // } + + + // // // P_Frame *prev_correct_frame = P_PushFrame(correct_world, predict_world->last_frame, predict_world->last_frame->tick); + + // // P_DebugDrawFrame(correct_frame); + // // } + + + + + ////////////////////////////// //- Update local world @@ -3360,6 +3464,8 @@ void V_TickForever(WaveLaneCtx *lane) P_ClearFrames(local_world, I64Min, I64Max); local_frame = P_PushFrame(local_world, predict_world->last_frame, predict_world->last_frame->tick); + + P_DebugDrawFrame(local_frame); }