more prediction testing

This commit is contained in:
jacob 2026-01-19 02:33:07 -06:00
parent cdc876e71f
commit 64aff1893d
2 changed files with 303 additions and 196 deletions

View File

@ -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) 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; P_Frame *frame = world->first_free_frame;
if (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_count = 0;
frame->constraints = PushStructsNoZero(world->frames_arena, P_Constraint, frame->max_constraints); 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 // Copy ents
@ -1422,6 +1413,17 @@ P_Frame *P_PushFrame(P_World *world, P_Frame *src_frame, i64 tick)
++frame->ents_count; ++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; 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)) 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 && (!is_predicting || guy == local_guy))
if (guy->is_guy)
{ {
P_Control control = guy->control; 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) // if (constraint)
// { // {
// constraint->flags = P_ConstraintFlag_Gentle | P_ConstraintFlag_NoWarmStart; // constraint->flags = P_ConstraintFlag_Solid;
// constraint->last_touched_tick = frame->tick; // constraint->last_touched_tick = frame->tick;
// constraint->normal = collision.collision_normal; // constraint->normal = collision.collision_normal;
// // constraint->friction = SqrtF32(ent0->friction * ent1->friction); // // constraint->friction = SqrtF32(ent0->friction * ent1->friction);
@ -1663,14 +1832,15 @@ void P_StepFrame(P_Frame *frame)
// f32 inv_i1 = 0; // f32 inv_i1 = 0;
// // Treat non-predicted guys as infinite-mass // // Treat non-predicted guys as infinite-mass
// // if (is_predicting && ent0 != local_guy) // if (is_predicting && ent0 != local_guy)
// // { // {
// // inv_m0 = 0; // inv_m0 = 0;
// // } // }
// // if (is_predicting && ent1 != local_guy) // if (is_predicting && ent1 != local_guy)
// // { // {
// // inv_m1 = 0; // inv_m1 = 0;
// // } // }
// constraint->ent0 = ent0->key; // constraint->ent0 = ent0->key;
// constraint->ent1 = ent1->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 //- 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)) 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; Xform xf = ent->xf;
xf.og = AddVec2(xf.og, MulVec2(ent->solved_v, solver_dt)); xf.og = AddVec2(xf.og, MulVec2(ent->solved_v, solver_dt));

View File

@ -3238,7 +3238,8 @@ void V_TickForever(WaveLaneCtx *lane)
} }
// Predict // 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) 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) 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); 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)) if (!P_IsEntNil(predict_player))
{ {
P_Control *predict_control = &local_controls[predict_tick % max_local_controls]; 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; b32 old_debug_draw = P_tl.debug_draw_enabled;
P_tl.debug_draw_enabled = TweakBool("Debug draw intermediate prediction steps", 0); Vec4 old_debug_tint = P_tl.debug_tint;
P_StepFrame(predict_frame); {
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_draw_enabled = old_debug_draw;
P_tl.debug_tint = old_debug_tint;
} }
predict_frame = predict_world->last_frame; 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? // 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 //- Update local world
@ -3360,6 +3464,8 @@ void V_TickForever(WaveLaneCtx *lane)
P_ClearFrames(local_world, I64Min, I64Max); P_ClearFrames(local_world, I64Min, I64Max);
local_frame = P_PushFrame(local_world, predict_world->last_frame, predict_world->last_frame->tick); local_frame = P_PushFrame(local_world, predict_world->last_frame, predict_world->last_frame->tick);
P_DebugDrawFrame(local_frame);
} }