Fractal Softworks Forum

Please login or register.

Login with username, password and session length
Pages: 1 ... 5 6 [7] 8 9 ... 32

Author Topic: Optimizing the Conquest: a Mathematical Model of Space Combat  (Read 25143 times)

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #90 on: November 14, 2022, 12:35:15 PM »

Actually we would always run into this issue regardless of scale, because of the method I used to solve the number of shots over time, which was treating it as boxes of height shots per second and width firing duration and then geometrically solving the definite integral of shot number from timepoint-1 to timepoint.

The way to avoid this problem and still get the same result would be simply to calculate the timepoint of each shot of the weapon (it goes chargeup-shot-burstdelay-shot-burstdelay...chargedown-chargeup-shot-burstdelay so by no means that hard) and then calculate how many of these fall between t-1 and t.
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

intrinsic_parity

  • Admiral
  • *****
  • Posts: 3071
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #91 on: November 14, 2022, 01:12:22 PM »

My point was that if you use the exact discretization that game uses, then all of the shots will happen exactly at one of the times you are simulating, and you won't have to be checking intervals or dealing with continuous time at all. At least, I think that's how it should work.

edit: I just realized all the shots would be fired perfectly at the simulation time intervals but I suppose they wouldn't necessarily land at the perfect times if you accounted for travel time, which IDK if you are trying to do that. But, at the end of the day, you are kind of trying to reconstruct the games combat engine, so it feels like emulating that will give best results.
« Last Edit: November 14, 2022, 01:19:52 PM by intrinsic_parity »
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #92 on: November 14, 2022, 10:40:20 PM »

Well, the thinking is we want to use discrete time with larger intervals to reduce the number of computations we have to perform. But that got me thinking.

This method is going to make the original one using calculus and whatnot look like one of those jokes about mathematicians changing a lightbulb, but if we give up on the idea of constructing a cycle to iterate over, and instead just go to the time limit, then we can very easily compute all the timepoints at which shots from a weapon hit the enemy ship. Then we can define that, if H is the set of all such timepoints, s(t) = n({x in H | t-1 < x <= t}) with a special case for the first interval being [0,1] (which we avoid in the code below by adding a very small number to the time, though). We have direct access to n(set) in R using length(). So,

Code
#1. general constants
#the interval of discrete time (time lattice parameter) we are using in the model, in seconds
time_interval <- 1
#how long 1 tick of a beam lasts, in seconds
beam_tick_time <- 1/10
#maximum duration of combat in the model, in seconds
time_limit <- 500
#operating modes
UNLIMITED <- -1
GUN <- 0
BEAM <- 1

#times in seconds, ammoregen is in ammo / second
hits <- function(chargeup, chargedown, burstsize, burstdelay, ammo=UNLIMITED, ammoregen=0, reloadsize=0, traveltime=0, mode=GUN){
  #this vector will store all the hit time coordinates
  Hits <- vector(mode="double", length = 0)
  #current time
  #insert a very small fraction here to make time round correctly
  time <- 0
  #maximum ammo count is ammo given at start
  maxammo <- ammo
  #this is used to do ammo regeneration, 0 = not regenerating ammo, 1 = regenerating ammo
  regeneratingammo <- 0
  ammoregentimecoordinate <- 0
  ammoregenerated <- 0
 
  #we are firing a gun
  if (mode == GUN) {
    while(time < time_limit){
      time <- time + chargeup
      if(time - ammoregentimecoordinate > 1/ammoregen){
        ammoregenerated <- ammoregenerated + floor((time - ammoregentimecoordinate)/(1/ammoregen))
        ammoregentimecoordinate <- ammoregentimecoordinate + 1/ammoregen*floor((time - ammoregentimecoordinate)/(1/ammoregen))
        if(ammoregenerated >= reloadsize){
          ammo <- ammo+ ammoregenerated
          ammoregenerated <- 0
        }
        if(ammo >= maxammo){
          ammo <- maxammo
          regeneratingammo <- 0
        }
      }
     
      if (burstdelay == 0) {
        for (i in 1:burstsize) {
          if (ammo != 0){
            Hits <- c(Hits, time + traveltime)
            ammo <- ammo - 1
            if (regeneratingammo == 0) {
              ammoregentimecoordinate <- time
              regeneratingammo <- 1
            }
          }
        }
      }
      if (burstdelay > 0) {
        for (i in 1:burstsize) {
          if (ammo != 0){
            Hits <- c(Hits, time + traveltime)
            time <- time + burstdelay
            ammo <- ammo -1
            if (regeneratingammo == 0) {
              ammoregentimecoordinate <- time
              regeneratingammo <- 1
            }
            if(time - ammoregentimecoordinate > 1/ammoregen){
              ammoregenerated <- ammoregenerated + floor((time - ammoregentimecoordinate)/(1/ammoregen))
              ammoregentimecoordinate <- ammoregentimecoordinate + 1/ammoregen*floor((time - ammoregentimecoordinate)/(1/ammoregen))
              if(ammoregenerated >= reloadsize){
                ammo <- ammo+ ammoregenerated
                ammoregenerated <- 0
              }
              if(ammo >= maxammo){
                ammo <- maxammo
                regeneratingammo <- 0
              }
            }
           
          }
        }
      }
      time <- time+chargedown
      if(time - ammoregentimecoordinate > 1/ammoregen){
        ammoregenerated <- ammoregenerated + floor((time - ammoregentimecoordinate)/(1/ammoregen))
        ammoregentimecoordinate <- ammoregentimecoordinate + 1/ammoregen*floor((time - ammoregentimecoordinate)/(1/ammoregen))
        if(ammoregenerated >= reloadsize){
          ammo <- ammo+ ammoregenerated
          ammoregenerated <- 0
        }
        if(ammo >= maxammo){
          ammo <- maxammo
          regeneratingammo <- 0
        }
      }
    }
  }
 
  timeseries <- vector(mode="integer", length = time_limit/time_interval)
  timeseries[1] <- length(Hits[Hits >= 0 & Hits <= 1*time_interval])
  for (i in 2:time_limit/time_interval) timeseries[i] <- length(Hits[Hits > (i-1)*time_interval & Hits <= i*time_interval])
  return(timeseries)
}


#weird locust, from previous
> hits(0,5.44,56,0.1)
  [1] 10 10 10 10 10  6  0  0  0  0  0 10 10 10 10 10  6  0  0  0  0  0 10 10 10 10 10  6  0  0  0  0  0  9
 [35] 10 10 10 10  7  0  0  0  0  0  9 10 10 10 10  7  0  0  0  0  0  8 10 10 10 10  8  0  0  0  0  0  8 10
 [69] 10 10 10  8  0  0  0  0  0  8 10 10 10 10  8  0  0  0  0  0  7 10 10 10 10  9  0  0  0  0  0  7 10 10
[103] 10 10  9  0  0  0  0  0  6 10 10 10 10 10  0  0  0  0  0  6 10 10 10 10 10  0  0  0  0  0  6 10 10 10
[137] 10 10  0  0  0  0  0  5 10 10 10 10 10  1  0  0  0  0  5 10 10 10 10 10  1  0  0  0  0  4 10 10 10 10
[171] 10  2  0  0  0  0  4 10 10 10 10 10  2  0  0  0  0  4 10 10 10 10 10  2  0  0  0  0  3 10 10 10 10 10
[205]  3  0  0  0  0  3 10 10 10 10 10  3  0  0  0  0  2 10 10 10 10 10  4  0  0  0  0  2 10 10 10 10 10  4
[239]  0  0  0  0  2 10 10 10 10 10  4  0  0  0  0  1 10 10 10 10 10  5  0  0  0  0  1 10 10 10 10 10  5  0
[273]  0  0  0  0 10 10 10 10 10  6  0  0  0  0  0 10 10 10 10 10  6  0  0  0  0  0 10 10 10 10 10  6  0  0
[307]  0  0  0  9 10 10 10 10  7  0  0  0  0  0  9 10 10 10 10  7  0  0  0  0  0  8 10 10 10 10  8  0  0  0
[341]  0  0  8 10 10 10 10  8  0  0  0  0  0  8 10 10 10 10  8  0  0  0  0  0  7 10 10 10 10  9  0  0  0  0
[375]  0  7 10 10 10 10  9  0  0  0  0  0  6 10 10 10 10 10  0  0  0  0  0  6 10 10 10 10 10  0  0  0  0  0
[409]  6 10 10 10 10 10  0  0  0  0  0  5 10 10 10 10 10  1  0  0  0  0  5 10 10 10 10 10  1  0  0  0  0  4
[443] 10 10 10 10 10  2  0  0  0  0  4 10 10 10 10 10  2  0  0  0  0  4 10 10 10 10 10  2  0  0  0  0  3 10
[477] 10 10 10 10  3  0  0  0  0  3 10 10 10 10 10  3  0  0  0  0  2 10 10 10

That's what we know to be correct, so that's good. Let's try a more difficult one

> #ion pulser, with a travel time of 1 second
> hits(chargeup = 0.05,chargedown=0.05, burstsize = 3, burstdelay = 0.1, ammo = 20, ammoregen = 2, reloadsize = 3, traveltime = 1)
  [1] 0 8 7 8 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0
 [53] 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3
[105] 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3
[157] 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0
[209] 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3
[261] 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3
[313] 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0
[365] 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3
[417] 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3
[469] 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3 3 0 3


> #autopulse laser, with travel time 0.5 seconds
> hits(chargeup = 0.05,chargedown=0.05, burstsize = 1, burstdelay = 0, ammo = 30, ammoregen = 2, reloadsize = 0, traveltime = 0.5)
  [1]  5 10 10 10  3  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2
 [35]  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2
 [69]  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2
[103]  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2
[137]  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2
[171]  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2
[205]  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2
[239]  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2
[273]  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2
[307]  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2
[341]  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2
[375]  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2
[409]  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2
[443]  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2
[477]  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2


To-do: beam weapons. I don't understand what this paragraph in the wiki means, mathematically speaking:
"chargedown
For non-beam weapons, this determines how long it takes for the weapon to be ready to fire again--i.e. cooldown. For beam weapons, this determines how long it takes for the beam to dissipate after the mouse button is released, where the beam is narrowing and doing less damage."

How much less damage? Any knowers?

Edit: added more checkpoints for ammo regeneration, there should be one whenever we add time.
« Last Edit: November 14, 2022, 11:23:01 PM by CapnHector »
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

Vanshilar

  • Admiral
  • *****
  • Posts: 602
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #93 on: November 15, 2022, 02:11:46 AM »

So I've amassed quite a bit of experimental results but not sure how to present it all just yet; not sure if it's better to put the results in this thread to keep all the mathematical findings about the Conquest together, or if it's better to put it in its separate thread since I'm looking more at "experimental results of effectiveness of different weapons using the Conquest as a platform and [REDACTED] as the enemy fleet" whereas the other main thrust of the thread is "developing a mathematical simulation of the effectiveness of different weapons" etc. Both are related to each other but take opposite approaches.

Anyway, wanted to respond to a couple of things quickly:

This is an exciting run-down. Please, if you can, include 4x Railgun with Ballistic Rangefinder for the Medium slots (same cost as 4x HVD, same range, better damage / flux) since Mjolnir / Locusts / Railguns is my Conquest layout and it's a stellar performer.

I can include the Railgun, but note that then you're looking at a symmetrical loadout rather than one side as the "main guns" and the other as the "defense" (i.e. Devastators). Generally speaking I've been looking at asymmetrical layouts, with one side having the bulk of the weapons. So not sure how much the other side would realistically contribute to battle.

Or I could loop through the collision boundary points in the .ship file to find the left-and right-most ones.

It should be easy enough to loop through the "bounds" section of each .ship file to determine when the weapon hits armor or hull. However, don't forget that shields are a simple circle so the shield width would be 2 times "shieldRadius" for calculating whether or not the weapon hits shields.

Of course in terms of the real game we might rather set SD=ship top speed. Or might consider this: give each weapon and ship its own SD, given by projectile travel time * enemy top speed / 2. Then we're essentially saying that 95% of the time, the enemy's position relative to the original location has changed at most by its maximum travel under its own propulsion during that time (why it might "change more" is differences in our own position and facing compared to the ideal).

The weapon does do a certain amount of target leading however. I haven't really tested autofire accuracy directly, but I would think that the SD might actually be based more on the target's acceleration than the target's top speed, since the target leading would already account for the target's speed to a certain extent. I virtually always run my fleet at 100% CR which I think means that autofire is always 100% accurate (meaning, it fully takes the target's speed into account). Accounting for maneuverability might be tricky though, because it depends on whether or not the AI decides to maneuver out of the path of an incoming projectile, and I have no idea how that's handled.

Doing this with integers over discrete time is going to be exactly the strength of this program, as it's very difficult analytically (how do you calculate the armor damage reduction?) and the computing time would be soul crushingly long using something like increments of 0.01s for what this program is supposed to do

I don't know how often the game actually computes the game state. However, settings.json has "minRefireDelay" of 0.05 seconds meaning a weapon can only fire up to 20 times per second. (It can fire multiple projectiles shotgun-style each time though.) If I remember right, beam damage is applied 10 times a second (...or at least, the damage displayed is updated 10 times a second). So yeah, realistically, the smallest time increment you would need to worry about is 0.05 seconds.

Hm, what if we somehow went shot-by-shot instead of increment-by-increment of time?

Seems like then you're looking at a Discrete Event Simulation, and it looks like there is readily-available Python code for that and the concept is pretty easy to look up. Basically the code would be looking at how long before each weapon fires, select the one with the shortest cooldown remaining, advance time by that amount (decreases all weapons' cooldown by that amount), fire that weapon (processes whether or not it hits the target, etc.), adds that weapon's refire delay to its cooldown remaining, then repeat. That way the code wouldn't be calculating over a bunch of time increments where nothing happens. Should be easy to process ammo and ammo regen by the same logic as well.

To-do: beam weapons. I don't understand what this paragraph in the wiki means, mathematically speaking:
"chargedown
For non-beam weapons, this determines how long it takes for the weapon to be ready to fire again--i.e. cooldown. For beam weapons, this determines how long it takes for the beam to dissipate after the mouse button is released, where the beam is narrowing and doing less damage."

How much less damage? Any knowers?

The damage for both the chargeup and chargedown for beams are quadratic, i.e. starting at 0, then going up to the "damage/second" value at the end of the chargeup, and the opposite for the chargedown. Conceptually this is easy in that the total damage done during that time is 1/3 of the damage/second times the chargeup or chargedown, which makes it easy to calculate the total damage per burst, for example (the total damage done during chargeup and chargedown is damage/second * (chargeup + chargedown) / 3). The beam's hit strength is unaffected by this; it's always half of the full-strength DPS (the "dpsToHitStrengthMult" value in settings.json), regardless of how much damage it's doing per tick. So for example, a Tachyon Lance does 1500 damage/second while it's active, so its hit strength is always 750 even when it's charging up or charging down.

Some experimental data of how much damage a Tachyon Lance did to shields is below. Note that Starsector displays cumulative damage, whereas this is the damage per display tick from that cumulative damage displayed:

Spoiler
Code
Test 1
18
46
87
136
152
152
152
152
152
152
152
151
152
149
125
99
76
55
39
24
14
6
3

Test 2
1
13
35
71
120
152
152
152
152
152
152
152
152
152
152
134
108
83
61
44
29
17
8
6

Test 3
5
24
53
96
144
152
152
152
152
152
152
152
152
152
144
116
90
68
50
33
21
11
4
1
[close]

You can see that in all cases, the damage gradually ramps up quadratically to the max, then ramps down quadratically afterward.
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #94 on: November 15, 2022, 05:13:15 AM »

All right, well then just need to add a switch to always use full hit strength for beam weapons to the damage code, but I think we want to return beam ticks multiplied by beam intensity here and actually return beam ticks as a real number rather than an integer. The damage code should later convert this to 1 hit with hit strength of full beam and damage of the fraction presented here. I also added a sanity check so even if a modder has specified lower values it will use the game's global minimum cooldown.

Code
#1. general constants
#the interval of discrete time (time lattice parameter) we are using in the model, in seconds
time_interval <- 1
#how long 1 tick of a beam lasts, in seconds
beam_tick <- 1/10
#minimum interval that exists in the game, in case a modder has somehow specified a lower value for something
global_minimum_time <- 0.05
#maximum duration of combat in the model, in seconds
time_limit <- 500
#operating modes
UNLIMITED <- -1
GUN <- 0
BEAM <- 1

#times in seconds, ammoregen is in ammo / second
hits <- function(chargeup, chargedown, burstsize, burstdelay, ammo=UNLIMITED, ammoregen=0, reloadsize=0, traveltime=0, mode=GUN){
  #specify sane minimum delays, since the game enforces weapons can only fire once every 0.05 sec
  #for beams, refiring delay is given by burstdelay, for guns it is burstdelay in case burstdelay is > 0 (==0 is shotgun) and chargedown
  if(burstdelay > 0 | mode == BEAM) burstdelay <- max(burstdelay, global_minimum_time)
  if(mode == GUN) chargedown <- max(chargedown, global_minimum_time)
  #this vector will store all the hit time coordinates
  #current time
  #insert a very small fraction here to make time round correctly
  time <- 0.001
  #maximum ammo count is ammo given at start
  maxammo <- ammo
  #this is used to do ammo regeneration, 0 = not regenerating ammo, 1 = regenerating ammo
  regeneratingammo <- 0
  ammoregentimecoordinate <- 0
  ammoregenerated <- 0
 
  #we are firing a gun
  if (mode == GUN) {
    Hits <- vector(mode="double", length = 0)
    while(time < time_limit){
      time <- time + chargeup
      if(time - ammoregentimecoordinate > 1/ammoregen){
        ammoregenerated <- ammoregenerated + floor((time - ammoregentimecoordinate)/(1/ammoregen))
        ammoregentimecoordinate <- ammoregentimecoordinate + 1/ammoregen*floor((time - ammoregentimecoordinate)/(1/ammoregen))
        if(ammoregenerated >= reloadsize){
          ammo <- ammo+ ammoregenerated
          ammoregenerated <- 0
        }
        if(ammo >= maxammo){
          ammo <- maxammo
          regeneratingammo <- 0
        }
      }
     
      if (burstdelay == 0) {
        for (i in 1:burstsize) {
          if (ammo != 0){
            Hits <- c(Hits, time + traveltime)
            ammo <- ammo - 1
            if (regeneratingammo == 0) {
              ammoregentimecoordinate <- time
              regeneratingammo <- 1
            }
          }
        }
      }
      if (burstdelay > 0) {
        for (i in 1:burstsize) {
          if (ammo != 0){
            Hits <- c(Hits, time + traveltime)
            time <- time + burstdelay
            ammo <- ammo -1
            if (regeneratingammo == 0) {
              ammoregentimecoordinate <- time
              regeneratingammo <- 1
            }
            if(time - ammoregentimecoordinate > 1/ammoregen){
              ammoregenerated <- ammoregenerated + floor((time - ammoregentimecoordinate)/(1/ammoregen))
              ammoregentimecoordinate <- ammoregentimecoordinate + 1/ammoregen*floor((time - ammoregentimecoordinate)/(1/ammoregen))
              if(ammoregenerated >= reloadsize){
                ammo <- ammo+ ammoregenerated
                ammoregenerated <- 0
              }
              if(ammo >= maxammo){
                ammo <- maxammo
                regeneratingammo <- 0
              }
            }
           
          }
        }
      }
      time <- time+chargedown
      if(time - ammoregentimecoordinate > 1/ammoregen){
        ammoregenerated <- ammoregenerated + floor((time - ammoregentimecoordinate)/(1/ammoregen))
        ammoregentimecoordinate <- ammoregentimecoordinate + 1/ammoregen*floor((time - ammoregentimecoordinate)/(1/ammoregen))
        if(ammoregenerated >= reloadsize){
          ammo <- ammo+ ammoregenerated
          ammoregenerated <- 0
        }
        if(ammo >= maxammo){
          ammo <- maxammo
          regeneratingammo <- 0
        }
      }
    }
    timeseries <- vector(mode="integer", length = time_limit/time_interval)
    timeseries[1] <- length(Hits[Hits >= 0 & Hits <= 1*time_interval])
    for (i in 2:time_limit/time_interval) timeseries[i] <- length(Hits[Hits > (i-1)*time_interval & Hits <= i*time_interval])
    return(timeseries)
  }
  #we are firing a beam
  if (mode == BEAM) {
    chargeup_ticks <- chargeup/beam_tick
    chargedown_ticks <- chargedown/beam_tick
    burst_ticks <- burstsize/beam_tick
    #for a beam we will instead use a matrix to store timepoint and beam intensity at timepoint
    beam_matrix <- matrix(nrow=0,ncol=2)
    #burst size 0 <- the beam never stops firing
    if(burstsize == 0){
      for (i in 1:chargeup_ticks) {
        #beam intensity scales quadratically during chargeup, so
      }
      while ( time < time_limit) {
        beam_matrix <- rbind(beam_matrix,c(time, 1))
        time <- time+beam_tick
      }
    } else {
      while (time < time_limit) {
        if (ammo != 0){
          ammo <- ammo - 1
          if (chargeup_ticks > 0){
          for (i in 1:chargeup_ticks) {
            beam_matrix <- rbind(beam_matrix,c(time, (i*beam_tick)^2))
            time <- time+beam_tick
            if (regeneratingammo == 0) {
              ammoregentimecoordinate <- time
              regeneratingammo <- 1
            }
            if(time - ammoregentimecoordinate > 1/ammoregen){
              ammoregenerated <- ammoregenerated + floor((time - ammoregentimecoordinate)/(1/ammoregen))
              ammoregentimecoordinate <- ammoregentimecoordinate + 1/ammoregen*floor((time - ammoregentimecoordinate)/(1/ammoregen))
              if(ammoregenerated >= reloadsize){
                ammo <- ammo+ ammoregenerated
                ammoregenerated <- 0
              }
              if(ammo >= maxammo){
                ammo <- maxammo
                regeneratingammo <- 0
              }
            }
          }
          }
          for (i in 1:burst_ticks){
            beam_matrix <- rbind(beam_matrix,c(time, 1))
            time <- time+beam_tick
            if(time - ammoregentimecoordinate > 1/ammoregen){
               ammoregenerated <- ammoregenerated + floor((time - ammoregentimecoordinate)/(1/ammoregen))
               ammoregentimecoordinate <- ammoregentimecoordinate + 1/ammoregen*floor((time - ammoregentimecoordinate)/(1/ammoregen))
               if(ammoregenerated >= reloadsize){
                 ammo <- ammo+ ammoregenerated
                 ammoregenerated <- 0
               }
               if(ammo >= maxammo){
                 ammo <- maxammo
                 regeneratingammo <- 0
               }
            }
          }
         
          if (chargedown_ticks > 0){
          for (i in 1:chargedown_ticks){
            beam_matrix <- rbind(beam_matrix,c(time, ((chargedown_ticks-i)*beam_tick)^2))
            time <- time+beam_tick
          }
          if(time - ammoregentimecoordinate > 1/ammoregen){
            ammoregenerated <- ammoregenerated + floor((time - ammoregentimecoordinate)/(1/ammoregen))
            ammoregentimecoordinate <- ammoregentimecoordinate + 1/ammoregen*floor((time - ammoregentimecoordinate)/(1/ammoregen))
            if(ammoregenerated >= reloadsize){
              ammo <- ammo+ ammoregenerated
              ammoregenerated <- 0
            }
            if(ammo >= maxammo){
              ammo <- maxammo
              regeneratingammo <- 0
            }
          }
          }
          time <- time + burstdelay
          if(time - ammoregentimecoordinate > 1/ammoregen){
            ammoregenerated <- ammoregenerated + floor((time - ammoregentimecoordinate)/(1/ammoregen))
            ammoregentimecoordinate <- ammoregentimecoordinate + 1/ammoregen*floor((time - ammoregentimecoordinate)/(1/ammoregen))
            if(ammoregenerated >= reloadsize){
              ammo <- ammo+ ammoregenerated
              ammoregenerated <- 0
            }
            if(ammo >= maxammo){
              ammo <- maxammo
              regeneratingammo <- 0
            }
          }
        }
        time <- time + global_minimum_time
        if(time - ammoregentimecoordinate > 1/ammoregen){
          ammoregenerated <- ammoregenerated + floor((time - ammoregentimecoordinate)/(1/ammoregen))
          ammoregentimecoordinate <- ammoregentimecoordinate + 1/ammoregen*floor((time - ammoregentimecoordinate)/(1/ammoregen))
          if(ammoregenerated >= reloadsize){
            ammo <- ammo+ ammoregenerated
            ammoregenerated <- 0
          }
          if(ammo >= maxammo){
            ammo <- maxammo
            regeneratingammo <- 0
          }
        }
      }
    }
    timeseries <- vector(mode="double", length = time_limit/time_interval)
    for (i in 1:length(timeseries)) {
      timeseries[i] <- sum(beam_matrix[beam_matrix[,1] < i & beam_matrix[,1] > i-1,2])
    }
    return(timeseries)
  }
}


#tachyon lance
> hits(chargeup = 0.5,chargedown=1, burstsize = 1, burstdelay = 4, ammo = UNLIMITED, ammoregen = 0, reloadsize = 0, traveltime = 0, mode=BEAM)
  [1]  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00
 [18]  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00
 [35]  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00
 [52]  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30
 [69]  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55
 [86] 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00
[103]  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55
[120]  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00
[137]  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00
[154]  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55
[171]  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00
[188]  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85
[205]  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00
[222]  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00
[239]  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00
[256]  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00
[273]  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30
[290]  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55
[307] 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00
[324]  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55
[341]  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00
[358]  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00
[375]  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55
[392]  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00
[409]  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85
[426]  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00
[443]  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00
[460]  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00
[477]  2.85  0.00  0.00  0.00  0.00  5.55  7.55  0.30  0.00  0.00  0.00  0.55 10.00  2.85  0.00  0.00  0.00
[494]  0.00  5.55  7.55  0.30  0.00  0.00  0.0



> #Paladin PD system
> hits(chargeup = 0,chargedown=0, burstsize = 0.2, burstdelay = 0.1, ammo = 20, ammoregen = 1, reloadsize = 0, traveltime = 0, mode=BEAM)
  [1] 6 6 6 6 5 6 5 6 6 6 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 [53] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[105] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[157] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[209] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[261] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[313] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[365] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[417] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[469] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2


If anybody wants to do the shot by shot instead, to skip empty cycles completely, then that sounds pretty smart. Although real time savings may be limited because some weapon is probably going to be firing much of the time. If we keep going with this code, we should definitely include a switch where it checks if any weapons are firing at that timepoint before doing anything (other than dissipate flux) in the damage calculation to skip empty cycles. Possibly could also look ahead to find the next timepoint at which some of the vectors are not empty, and jump there, if that is actually faster (I'm not sure, not a programmer, if it still has to check all the elements then it might just be the same thing).

Edit: fixed hangup when no ammo, added minimum progress of time in that case. Edit 2: the global minimum should only apply to burst delay and chargedown, and for guns, only if burst delay is > 0 since burst delay=0 means shotgun style, and only to chargedown of guns, per what Vanshilar said, so changed that, which also slightly changed the results for the Paladin so reposted.
« Last Edit: November 15, 2022, 06:17:58 AM by CapnHector »
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

Vanshilar

  • Admiral
  • *****
  • Posts: 602
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #95 on: November 15, 2022, 09:09:46 AM »

Oh yeah for the Paladin, even though its "damage/second" is 1000, its .wpn file (which in this case is guardian.wpn) has 5 turrets defined, so that tells the game to split it up into 5 beams of 200 DPS. Thus in this case, it's a shotgun of 5 beams, each at 100 hit strength.
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #96 on: November 15, 2022, 10:13:34 PM »

Alright next part of the research program: how does gun turn rate affect accuracy?

Suggested program:
1. Ignore acceleration and facing
2. Enemy ship moves in Brownian motion such that x_1=x_0+s_x and y_1=y_0+s_y, where s_x is 1 random value drawn from normal distribution N(0, (top speed/2)^2)-cos(angle of enemy ship after movement)*our top speed
3. Our previous turret facing was 0 for first iteration / saved value for previous, and our new turret facing is alpha=max(0,arcsin(delta x of enemy ship movement/range)-previousangle-turret rotation speed per second), with appropriate signs figured out and used
4. Simulate model 1 000 times, plot a generalized linear model for enemy ship speed and turret rotation rate to predict hitrate/hitratewithnomovement

Gonna be a moment before can do this, so figured would ask anybody see problems?
« Last Edit: November 15, 2022, 10:20:40 PM by CapnHector »
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

intrinsic_parity

  • Admiral
  • *****
  • Posts: 3071
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #97 on: November 16, 2022, 12:17:03 AM »

I think the only way turret turn rate would directly result in shots being off target is if the turret can't turn fast enough to track the target.

I think movement based inaccuracy is actually a question of the target leading performance and the turret turn rate is more of a constraint on target leading performance, rather than a source of random inaccuracy.

The question of if the turn rate is fast enough to track the target moving at top speed is deterministic based on relative motion, turn rate, and range. You could also consider your own ship turn rate as well. I think it should be possible to identify some practical conditions under which the weapon is incapable of tracking the target (stuff like x weapon cannot track y ship moving at top speed at z range or something).

The question of how much acceleration of the target displaces the impact point from the aim point (assuming perfect tracking), is another matter. The idea here is that the aim-leading is (I think, someone correct me if I am wrong) just using the targets velocity at the moment of firing to determine where it needs to aim for the shot to land center-mass (well, really a shot aligned with the center of the aim cone), but acceleration will result in the ship being in a different location, and so there should be a relationship between the acceleration and how off-target the shot is. I'm also not sure if the motion of your own ship is accounted for in the target leading. I'm pretty sure acceleration in the game is constant and binary (on or off in each direction, unless it ramps up, but I don't think that's the case), so it should be pretty tractable analytically.

Also, you've got some dimension issues in your equations. In particular, angles added with angular rates. Probably need to multiply angular rates by a time to get an angle.

Also, for what it's worth, using brownian motion is not ignoring acceleration, it's using random acceleration at each time step, which I don't think is a particularly realistic model.

Hopefully I will have time to write up some equations for what I am thinking tomorrow.
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #98 on: November 16, 2022, 12:56:17 AM »

Let's hear those equations. You can just assume time interval = 1 second because that's the thing we're going for.

Anyway, if we have that aim leading target = f(enemy velocity, enemy acceleration, enemy location), and f(0,0,0) = 0, and f is linear, and E(enemy velocity)=0, E(enemy acceleration)=0, E(enemy location)=0, then isn't E(f(...)) =0?

What we expect to find is that for some enemy top speed, and for some combination of enemy top speed and turret turn rate, there is a proportion of enemies that will escape tracking during simulation, although this proportion would be 0 due to enemy movement being random and our ship's not being so, if the simulation were to go on infinitely. Also, below a certain enemy top speed, our ship is able to always catch up to them and keep them at a hair's breadth's distance, making any tracking irrelevant.

Because of these weird limits that don't have much to do with the real combat situation, it could also make sense to fix the enemy's range and only consider motion along a circle of radius range around our ship. But non-random motion would be extremely weird then, because that would just make all ships capable of doing so escape from the gun's firing arc asap and stay away, and not affect ships that can't escape at all. This problem would be worse with non-random free motion though, since then all ships that can do so would escape in two dimensions. Unless the AI can be distilled into some equation of motion that doesn't escape, and is realistic, and isn't random?

Essentially it seems like we want random motion as that matches the experience in the game more than the enemy ship just maneuvering away if it can escape or getting caught forever if it can't, unless there is something better to use. Brownian motion kind of fits because you might imagine the enemy ship being bounced around by random forces - enemy ships, enemy fire, and the AI's own rapidly changing tendencies - but more realistic would probably be to also have a tendency to a particular range. However any complexity is also always open to criticism about realism so this should be based on some kind of understanding of the AI and explicable assumptions. And random motion with tendency will just result in ships that can staying at that range and others falling towards our ship like rocks from the pursuit motion.  Puzzle what to do. Possibly tendency for our ship to keep distance... But won't it just degenerate into the circle model? With two orbits for two preferred distances. Leading to a weird threshold in ship top speed changing range discontinuously at some point. Just thinking out loud here.
« Last Edit: November 16, 2022, 06:59:51 AM by CapnHector »
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

intrinsic_parity

  • Admiral
  • *****
  • Posts: 3071
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #99 on: November 16, 2022, 05:19:16 PM »

Ships escaping/evading is exactly what happens in combat though? The typical engagement cycle for the AI, is approach (into weapons range) and try to flank until someones flux is too high, then that ship tries to retreat to vent. Small fast ships flanking at relatively close range will definitely completely avoid slow turret shots. The only part that could look like random movement is when small fast ships are evading shots, but they are still reacting to your shots, so it's not really random.

Also, like I said before, target leading is the real source of inaccuracy, while turn rate is just a constraint on what target leading you can achieve. That's because you have to predict where the target will go to hit them with a non-hitscan projectile, and your prediction can't account for what happens between firing and impact. A simple model target leading would just assume constant velocity. Something like this:
https://gamedev.net/forums/topic/457840-calculating-target-lead/457840/

I had some ideas about trying to simulate evasion (basically assuming a worst-case of the enemy accelerating in the opposite direction as soon as you fire and seeing how far off target the shot is), but after messing around for a bit, I'm not even sure if that is worth doing. It feels too situation-specific to create any meaningful generalizations, but I guess that's how pretty much all of this movement stuff feels. You might be able to get some results about the relationship between a ships maneuverability and what projectile velocities it can reliably evade, which could be interesting.

I guess you can do whatever you want, but I feel like there are much more valuable ways to improve the simulation besides worrying about this. I think that modeling movement and target-leading wrong could make the simulation considerably less realistic too.
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #100 on: November 17, 2022, 03:21:46 AM »

Well, let's just try the random movement thing and see how it goes. The nice thing about random movement is since enemy acceleration and movement is random with an expected value of 0, it is all the same whether you are leading the target or not, making things simpler.

Spoiler
Code
x <- 0
y <- 1000
alpha <- 0
xour <- 0
your <- 0
beta <- pi/2
enemytopspeed <- 300
ourtopspeed <- 50
turretrotationrate <- 30*pi/180
mindistance <- 50

locationmatrix <- data.frame()

for (t in 1:100) {
  locationmatrix <- rbind(locationmatrix, c(t,x,y,xour,your,gamma))
  #the enemy is not allowed to come within mindistance pixels of our ship in either direction
  deltax <- rnorm(1,0,enemytopspeed)
  deltay <- rnorm(1,0,ourtopspeed)
  while(abs(x+deltax-xour) < mindistance) deltax <- deltax <- rnorm(1,0,enemytopspeed)
  while(abs(y+deltay-xour) < mindistance) deltay <- rnorm(1,0,ourtopspeed)
  #alpha is the angle of directional vector from our ship to enemy
 
 

  x <- x+deltax
  y <- y+deltay
  alpha <- atan2(y-your,x-xour)
 
  xour=xour+cos(alpha)*ourtopspeed
  your=your+sin(alpha)*ourtopspeed 
 
 
  #beta is the angle of our turret
  #rotate beta towards alpha by a maximum of rate turretrotationrate
  beta <- beta + sign(alpha-beta)*min(turretrotationrate,abs(alpha-beta))
 
  #gamma is the angle from our turret facing to the enemy ship
  gamma <- alpha-beta
  print(gamma)
}
names(locationmatrix) <- c("t","x","y","xour","your","gamma")
library(ggplot2)

ggplot(data=locationmatrix)+
  geom_path(aes(x=x,y=y,col="ENEMY"))+
  geom_point(aes(x=x,y=y,col="ENEMY"))+
  geom_path(aes(x=xour,y=your))+
  geom_point(aes(x=xour,y=your))

ggplot(data=locationmatrix)+
  geom_path(aes(x=gamma*180/pi,y=t))+
  geom_point(aes(x=gamma*180/pi,y=t))
[close]
Here are some random paths (enemy speed 300, our speed 50)


I have yet to see it escape once. Seems like the randomness is simply too much.

Here is a plot with also the enemy's apparent position in degrees from the point of view of our turret which is trying to rotate in pursuit (max turret rotation rate 30 deg / sec, assume infinite turret arc, assume ship does not rotate)


Now it seems like it's actually a pretty bad idea for our ship to close in to point blank range because then the apparent angle changes faster than the turret can follow. So let's make it keep a minimum distance of 1000.

Code
  if(sqrt((x-xour)^2+(y-your)^2)>1000){
  xour=xour+cos(alpha)*ourtopspeed
  your=your+sin(alpha)*ourtopspeed
  }



Most of the time we are going to be hitting this speed 300 ship with no problem. What about with a turret rotation speed of 3 (Gauss cannon) rather than 30?



This certainly seems like it would affect damage output a little.

(Edit: change starting turret angle to pi/2 from -pi/2, was latter in graphs)


Con't

Now that we have a model, we can also integrate probability to hit into it. Let's say that the enemy ship is a 100 px wide sphere. Then previously in this thread we found probability to hit a coordinate less than Z for a weapon to be


G <- function(y) return(y*pnorm(y) + dnorm(y))
fEz <- function(z, a, b) return((1/2/b)*(pnorm(z/a+b/a)-pnorm(z/a-b/a)))
PrEltZ <- function(z, a, b) return(a/2/b*(G(z/a+b/a)-G(z/a-b/a)))
(note: if b=0, use normal distribution instead)


where b is the weapon spread and a is the SD of the normal distribution (presumably 50 px). Now if coordinate 0 is right in front of us then probability to hit enemy ship is PrEltZ(enemyshipleftbound)-PrEltZ(enemyshiprightbound). The enemy ship's left bound is approximately given by arc length to enemy ship middle -50 px, so (gamma*r). So we have the probability to hit is approx PrEltZ(range*gamma+50, SD, spread)-PrEltZ(range*gamma-50, SD, spread).

Putting it all together we get this code
Spoiler
Code
x <- 0
y <- 1000
alpha <- 0
xour <- 0
your <- 0
beta <- pi/2
enemytopspeed <- 300
ourtopspeed <- 50
turretrotationrate <- 3*pi/180
mindistance <- 50
SD <- 50
spread <- 0

G <- function(y) return(y*pnorm(y) + dnorm(y))
fEz <- function(z, a, b) return((1/2/b)*(pnorm(z/a+b/a)-pnorm(z/a-b/a)))
PrEltZ <- function(z, a, b){
  if(b==0) return(pnorm(z,0,a)) else return(a/2/b*(G(z/a+b/a)-G(z/a-b/a)))
}

locationmatrix <- data.frame()

for (t in 1:100) {
  locationmatrix <- rbind(locationmatrix, c(t,x,y,xour,your,prob))
  #the enemy is not allowed to come within mindistance pixels of our ship in either direction
  deltax <- rnorm(1,0,enemytopspeed)
  deltay <- rnorm(1,0,ourtopspeed)
  while(abs(x+deltax-xour) < mindistance) deltax <- deltax <- rnorm(1,0,enemytopspeed)
  while(abs(y+deltay-xour) < mindistance) deltay <- rnorm(1,0,ourtopspeed)
  #alpha is the angle of directional vector from our ship to enemy
 
 

  x <- x+deltax
  y <- y+deltay
  alpha <- atan2(y-your,x-xour)
 
  range <-sqrt((x-xour)^2+(y-your)^2)
 
  if(range>1000){
  xour=xour+cos(alpha)*ourtopspeed
  your=your+sin(alpha)*ourtopspeed
  }
 
 
 
  #beta is the angle of our turret
  #rotate beta towards alpha by a maximum of rate turretrotationrate
  beta <- beta + sign(alpha-beta)*min(turretrotationrate,abs(alpha-beta))
 
  #gamma is the angle from our turret facing to the enemy ship
  gamma <- alpha-beta
  prob <- PrEltZ(gamma*range+50, SD, spread)-PrEltZ(gamma*range-50,SD,spread)
  print(prob)
}
names(locationmatrix) <- c("t","x","y","xour","your","hitprobability")
library(ggplot2)

ggplot(data=locationmatrix)+
  geom_path(aes(x=x,y=y,col="ENEMY"))+
  geom_point(aes(x=x,y=y,col="ENEMY"))+
  geom_path(aes(x=xour,y=your))+
  geom_point(aes(x=xour,y=your))

#ggplot(data=locationmatrix)+
#  geom_path(aes(x=gamma*180/pi,y=t))+
#  geom_point(aes(x=gamma*180/pi,y=t))

ggplot(data=locationmatrix)+
    geom_path(aes(y=hitprobability,x=t))
 
[close]

And these somewhat unflattering graphs for the Gauss (spread 0, turn rate 3)



Using these assumptions, what kinds of hit rates will we get for various turret rotation speeds? Let's stick with the Gauss (spread 0) for now, but try turn rate 0, 3, 5, 10, 15, 20 and 30. Run 1000 combats and take the average.

Turn rate  Hit rate
0          0.4%
3          23.4%
5          32.4%
10         42.5%
15         49.1%
20         52.9%
30         57.5%


That is versus a small speed 300 ship so it's no wonder turn rate is important. But it also suggests that this is important enough that it should be included in any comprehensive model of space combat (and also suggests that you should put advanced turret gyros on your Gauss Conquests, incidentally). There also appears to be such a thing as "enough turn rate" with diminishing returns as the turret is mostly pointed in the right direction.
« Last Edit: November 17, 2022, 04:33:20 AM by CapnHector »
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

intrinsic_parity

  • Admiral
  • *****
  • Posts: 3071
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #101 on: November 17, 2022, 09:23:21 AM »

Just to be clear, I wasn't saying that there is not significant affect on accuracy due to movement, but rather that having an inaccurate model won't generate meaningful results. I think a broad conclusion like "you should put turret gyros on a ship with gauss" is fine (although you could have come up with that without these simulations), but doing something like taking these hit%'s and using them in a simulation to account for inaccuracy due to movement I think is a bad idea.

A simple approximation of required turret speed to track a target is w > v/r (based on the definition of angular velocity, it's more complicated when considering shot velocity) where w is the angular rate, v is the perpendicular portion of the relative velocity, and r is the range. It feels like this entire analysis can be boiled down to saying that sometimes, at certain ranges and relative motions, a ship with 300 speed can exceed the conditions that a turret with a certain turn rate can track (which is basically the analysis I was talking about earlier), and that happens more often when the turret turn rate is slower. How often does that happen in real combat? I don't think this simulation really reflects the answer to that, all it shows is how often that happens for this specific type of random motion.

The AI and the player are both actively managing range and relative velocity, so having random movement really alters the results in that sense compared to real combat IMO. I think you have to actually reproduce the AI behavior to try an answer the question of how often shots will miss due to movement in practice, which just doesn't seem like a worthwhile pursuit.

Also, 300 speed is super fast (like the fastest frigate with SO fast, no ship in the game has a base speed over 200). I'm pretty sure in the game, the hit rate would be very close to zero for a gauss against something that fast, except for maybe one or two lucky shots. In fact, most projectile weapons would be incapable of reliably hitting a 300 speed ship at longer ranges due to shot travel time/velocity. There's actually a trade off where longer range means more time between firing and impact for the target to dodge reactively, but shorter range means harder to track. Usually, I think the shot speed makes more of a difference though in terms of accuracy in-practice.
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #102 on: November 17, 2022, 09:55:58 AM »

Open to ideas about how to take it further, show me the math!

I don't personally think the random motion is that bad. Consider that we're going to be taking the average of 100 000 movements per enemy ship per weapon if using just this code. While it's not the same as the AI, if you just consider that it's a bunch of numbers reflecting current turret position, enemy ship movement and turret's ability to follow, it may not be that bad (technically since each enemy movement is random and independent you could consider that they happen in any order, including more reasonable sequences). Not claiming that one single run of this model is saying much. But the aggregate may? At the very least may be better than nothing. And it neatly solves the AI simulation problem as otherwise we'd need to model not only AI but also target leading.

The idea here would be to do that, ie take the average result of running this 1000 times, over a variety of ship sizes and parameters of weapon (obviously not a crazily jinking speed 300 frigate used for the demo) and then try to fit some kind of equation that we could use in the main model. As this does seem to be a thing that should be represented somehow. Probably come with a switch to turn it off so people can see results without if they're not comfortable with the assumptions.
« Last Edit: November 17, 2022, 10:03:52 AM by CapnHector »
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

intrinsic_parity

  • Admiral
  • *****
  • Posts: 3071
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #103 on: November 17, 2022, 10:12:05 AM »

At the very least may be better than nothing.

It's possible to be worse than nothing too. If it makes guns seems worse or better than they actually are and you reach misleading conclusions.

What results do you get if you use a reasonable top speed like 100 or 150 or something?
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Optimizing the Conquest: a Mathematical Model of Space Combat
« Reply #104 on: November 17, 2022, 10:20:44 AM »

True. Hence should have an off switch so if it seems untrue to player experience or, say, player feels comfortable compensating for poor turn rate, can just see results vs immobile objects. Stay tuned for results vs more ordinary opponents, currently away from computer but will likely have more time for this tomorrow.
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge
Pages: 1 ... 5 6 [7] 8 9 ... 32