extends GutTest ## Which bullets are drawn, and the promise that bounds it: no enemy bullet can ## hit you without having been on screen, at full strength, for at least ## BulletRenderer.WARNING_TIME first. Fading distant bullets is only worth doing ## if that holds for every bullet the game can fire. const RENDERER := preload("res://src/view/bullet_renderer.gd") const SIGHT := SimConfig.FOG_VIEW_RADIUS ## The worst case for warning: the bullet coming straight at you and you running ## straight at it. Stepped the way BulletPool integrates, from far enough out ## that it starts undrawn. Returns seconds it was fully drawn before it hit. func _warning(speed: float, accel: float) -> float: var dt := SimConfig.TICK_DELTA var gap := 6000.0 var v := speed var t := 0 var first_full := -1 var reach := SimConfig.PLAYER_RADIUS + 7.0 while gap > reach and t < 60 * 120: if first_full < 0 and RENDERER.visibility(gap, v, accel, SimConfig.TEAM_ENEMY, SIGHT) >= 1.0: first_full = t v = maxf(v + accel * dt, 0.0) gap -= (v + SimConfig.PLAYER_SPEED) * dt t += 1 assert_gte(first_full, 0, "the bullet was drawn at all before it arrived") return float(t - first_full) * dt func test_every_enemy_bullet_in_the_game_is_seen_in_time() -> void: var emitters: Array[BulletEmitter] = [] for id in Content.ALL_ENEMIES: emitters.append_array(Content.enemy(id).emitters) for id in Content.ALL_BOSSES: for phase in Content.boss(id).phases: emitters.append_array(phase.emitters) assert_gt(emitters.size(), 0) for e in emitters: var warned := _warning(e.speed, e.accel) assert_gte(warned, RENDERER.WARNING_TIME - SimConfig.TICK_DELTA, "a %.0f u/s bullet was on screen %.2fs before it could land" % [e.speed, warned]) func test_a_bullet_far_faster_than_anything_today_is_still_seen_in_time() -> void: # The sight radius covers today's content on its own. This is the case the # speed term exists for: content added later, faster than the radius allows. for case in [[1200.0, 0.0], [600.0, 900.0], [2000.0, 0.0]]: var warned := _warning(case[0], case[1]) assert_gte(warned, RENDERER.WARNING_TIME - SimConfig.TICK_DELTA, "%.0f u/s, accelerating %.0f: on screen %.2fs" % [case[0], case[1], warned]) func test_inside_sight_everything_is_drawn_fully() -> void: for team in [SimConfig.TEAM_ENEMY, SimConfig.TEAM_PLAYER]: assert_eq(RENDERER.visibility(SIGHT - 1.0, 130.0, 0.0, team, SIGHT), 1.0) func test_a_slow_shot_far_beyond_sight_is_not_drawn() -> void: assert_eq(RENDERER.visibility(SIGHT + 400.0, 130.0, 0.0, SimConfig.TEAM_ENEMY, SIGHT), 0.0, "out in the black and nowhere near you") func test_the_edge_fades_rather_than_cuts() -> void: var mid := RENDERER.visibility(SIGHT + RENDERER.FADE_OUT * 0.5, 130.0, 0.0, SimConfig.TEAM_ENEMY, SIGHT) assert_between(mid, 0.3, 0.7) func test_player_shots_fade_at_the_edge_of_sight_however_fast() -> void: # They cannot hurt you, so there is nothing to warn about -- and a Sniper's # shot streaking across the dark was the commonest case of all. assert_eq(RENDERER.visibility(SIGHT + 200.0, SimConfig.MAX_BULLET_SPEED, 0.0, SimConfig.TEAM_PLAYER, SIGHT), 0.0) assert_gt(RENDERER.visibility(SIGHT + 200.0, SimConfig.MAX_BULLET_SPEED, 0.0, SimConfig.TEAM_ENEMY, SIGHT), 0.0, "an enemy bullet that fast is drawn further out, because it could reach you") # --- The renderer actually applies it --------------------------------------- func _drawn(r: Node2D) -> int: var n := 0 for layer in r._layers: n += (layer as MultiMeshInstance2D).multimesh.visible_instance_count return n func test_the_renderer_leaves_out_what_is_too_far_to_matter() -> void: var r: Node2D = RENDERER.new() add_child_autofree(r) var pool := BulletPool.new() var eye := Vector2(100.0, 100.0) pool.spawn(eye + Vector2(200.0, 0.0), Vector2(-130.0, 0.0), 7.0, 300, 10, SimConfig.TEAM_ENEMY, SimConfig.KIND_ORB) pool.spawn(eye + Vector2(SIGHT + 500.0, 0.0), Vector2(-130.0, 0.0), 7.0, 300, 10, SimConfig.TEAM_ENEMY, SimConfig.KIND_ORB) r.render_pool(pool, eye, SIGHT) assert_eq(_drawn(r), 1, "the near bullet only") func test_with_no_eye_everything_is_drawn() -> void: # The connect screen's backdrop has no player and wants the whole pattern. var r: Node2D = RENDERER.new() add_child_autofree(r) var pool := BulletPool.new() pool.spawn(Vector2.ZERO, Vector2.RIGHT, 7.0, 300, 10, SimConfig.TEAM_ENEMY, SimConfig.KIND_ORB) pool.spawn(Vector2(5000.0, 0.0), Vector2.RIGHT, 7.0, 300, 10, SimConfig.TEAM_ENEMY, SimConfig.KIND_ORB) r.render_pool(pool) assert_eq(_drawn(r), 2)